diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index abb10cd243..c762c13b82 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -136,12 +136,16 @@ macro (oiio_add_all_tests) iinfo igrep nonwhole-tiles oiiotool - oiiotool-composite oiiotool-control oiiotool-copy + oiiotool-composite + oiiotool-control + oiiotool-copy + oiiotool-demosaic oiiotool-fixnan oiiotool-pattern oiiotool-readerror - oiiotool-subimage oiiotool-text oiiotool-xform - oiiotool-demosaic + oiiotool-subimage + oiiotool-text + oiiotool-xform diff dither dup-channels jpeg-corrupt jpeg-metadata diff --git a/src/doc/imagebufalgo.rst b/src/doc/imagebufalgo.rst index 1e5ed39313..dc355f2b8a 100644 --- a/src/doc/imagebufalgo.rst +++ b/src/doc/imagebufalgo.rst @@ -1163,22 +1163,27 @@ Shuffling channels .. code-tab:: c++ - ImageSpec hint; - hint["raw:Demosaic"] = "none"; - ImageBuf Src ("test.cr3", 0, 0, nullptr, &hint); - ParamValue options[] = { { "layout", "GRBG" } }; - ImageBuf Dst = ImageBufAlgo::demosaic (Src, options); + .. tab:: C++ + + .. literalinclude:: ../../testsuite/docs-examples-cpp/src/docs-examples-imagebufalgo.cpp + :language: c++ + :start-after: BEGIN-imagebufalgo-demosaic + :end-before: END-imagebufalgo-demosaic + :dedent: 4 .. code-tab:: py - hint = ImageSpec() - hint["raw:Demosaic"] = "none" - Src = ImageBuf("test.cr3", 0, 0, hint) - Dst = OpenImageIO.ImageBufAlgo.demosaic(Src, layout="GRBG") + .. tab:: Python + + .. literalinclude:: ../../testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py + :language: py + :start-after: BEGIN-imagebufalgo-demosaic + :end-before: END-imagebufalgo-demosaic + :dedent: 4 .. code-tab:: bash oiiotool - oiiotool -iconfig raw:Demosaic none -i test.cr3 --demosaic:layout=GRBG -o out.exr + oiiotool -iconfig raw:Demosaic none -i test.cr3 --demosaic:layout=GRBG:white_balance=2.0,0.8,1.2,1.5 -o out.exr | diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index 20341f6fc8..8c30a25b52 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -4184,6 +4184,36 @@ current top image. +.. option:: --demosaic + + Demosaic a raw digital camera image. + + Optional appended modifiers include: + + `pattern=` *name* + sensor pattern. Currently supported patterns: "bayer" + `layout=` *name* + photosite order of the specified pattern. For layouts of the Bayer + pattern supported: "RGGB", "GRBG", "GBRG", "BGGR". + `algorithm=` *name* + the name of the algorithm to use: "linear"(simple bilinear demosaicing), + "MHC"(Malvar-He-Cutler algorithm) + `white-balance=` *v1,v2,v3...* + optional white balance weights, can contain either three (R,G,B) or four + (R,G1,B,G2) values. The order of the white balance multipliers is as + specified, it does not depend on the matrix layout. + + Examples:: + + oiiotool --iconfig raw:Demosaic none --input test.cr3 --demosaic \ + --output out.exr + + oiiotool --iconfig raw:Demosaic none --input test.cr3 \ + --demosaic:pattern=bayer:layout=GRBG:algorithm=MHC:white_balance=2.0,0.8,1.2,1.5 \ + --output out.exr + + + :program:`oiiotool` commands for color management ================================================= diff --git a/src/doc/pythonbindings.rst b/src/doc/pythonbindings.rst index 8a06bc537d..70924aba2b 100644 --- a/src/doc/pythonbindings.rst +++ b/src/doc/pythonbindings.rst @@ -3680,6 +3680,27 @@ Color manipulation ImageBufAlgo.unpremult (A, A) +.. py:method:: ImageBuf ImageBufAlgo.demosaic (src, pattern="", algorithm="", layout="", white_balance=py::none(), roi=ROI.All, nthreads=0) + bool ImageBufAlgo.demosaic (dst, src, pattern="", algorithm="", layout="", white_balance=py::none(), roi=ROI.All, nthreads=0) + Demosaic a raw digital camera image. + + `demosaic` can currently process Bayer pattern images (pattern="bayer") + using two algorithms: "linear" (simple bilinear demosaicing), and "MHC" + (Malvar-He-Cutler algorithm). The optional white_balance parameter can take + a tuple of three (R,G,B), or four (R,G1,B,G2) values. The order of the + white balance multipliers is as specified, it does not depend on the matrix + layout. + + Example: + + .. code-block:: python + + Src = ImageBuf("test.cr3", 0, 0, hint) + WB_RGBG = (2.0, 0.8, 1.5, 1.2) + Dst = OpenImageIO.ImageBufAlgo.demosaic(Src, layout="GRBG", + white_balance = WB_RGBG) + + .. _sec-iba-py-importexport: diff --git a/src/include/OpenImageIO/imagebufalgo.h b/src/include/OpenImageIO/imagebufalgo.h index 685ea7946d..99930b3271 100644 --- a/src/include/OpenImageIO/imagebufalgo.h +++ b/src/include/OpenImageIO/imagebufalgo.h @@ -2163,6 +2163,10 @@ bool OIIO_API repremult (ImageBuf &dst, const ImageBuf &src, /// /// The order the color filter array elements are arranged in, pattern-specific. /// +/// - "white-balance" : float[3] or float[4] (default: {1.0, 1.0, 1.0, 1.0}) +/// +/// Optional white-balancing weights. Can contain either three (R,G,B), or four (R,G1,B,G2) values. +/// The order of the white balance multipliers does not depend on the matrix layout. ImageBuf OIIO_API demosaic (const ImageBuf& src, KWArgs options = {}, ROI roi = {}, int nthreads = 0); diff --git a/src/libOpenImageIO/imagebufalgo_demosaic.cpp b/src/libOpenImageIO/imagebufalgo_demosaic.cpp index e525072c1a..d499b4bfb2 100644 --- a/src/libOpenImageIO/imagebufalgo_demosaic.cpp +++ b/src/libOpenImageIO/imagebufalgo_demosaic.cpp @@ -18,6 +18,7 @@ namespace { static const ustring pattern_us("pattern"); static const ustring algorithm_us("algorithm"); static const ustring layout_us("layout"); +static const ustring white_balance_us("white_balance"); } // namespace @@ -31,7 +32,17 @@ template class BayerDemosaicing { /// the source iterator. struct Row { ImageBuf::ConstIterator iterator; + float white_balance[2]; + int x_offset; float data[size]; + + float fetch() + { + float result = iterator[0] * white_balance[x_offset]; + iterator++; + x_offset = 1 - x_offset; + return result; + } }; std::vector rows; @@ -46,7 +57,8 @@ template class BayerDemosaicing { int src_yend; int x; - Window(int y, int xbegin, const ImageBuf& src) + Window(int y, int xbegin, const ImageBuf& src, int x_offset, + int y_offset, const float white_balance[4]) { assert(size >= 3); assert(size % 2 == 1); @@ -78,12 +90,15 @@ template class BayerDemosaicing { ystart = src_yend - 1 - (ystart - src_yend + 1) % 2; } - Row row - = { ImageBuf::ConstIterator(src, xstart, ystart) }; + int x_off = (xstart + x_offset) % 2; + int y_off = ((ystart + y_offset) % 2) * 2; + + Row row = { ImageBuf::ConstIterator(src, xstart, ystart), + { white_balance[y_off], white_balance[y_off + 1] }, + x_off }; for (int j = skip; j < size; j++) { - row.data[j] = row.iterator[0]; - row.iterator++; + row.data[j] = row.fetch(); } for (int j = 0; j < skip; j++) { @@ -113,8 +128,7 @@ template class BayerDemosaicing { if (x + size / 2 < src_xend) { for (int i = 0; i < size; i++) { Row& row = rows[i]; - row.data[curr] = row.iterator[0]; - row.iterator++; + row.data[curr] = row.fetch(); } } else { int src = column_mapping[size - 3]; @@ -141,7 +155,7 @@ template class BayerDemosaicing { public: bool process(ImageBuf& dst, const ImageBuf& src, const std::string& layout, - ROI roi, int nthreads) + const float white_balance[4], ROI roi, int nthreads) { int x_offset, y_offset; @@ -167,7 +181,7 @@ template class BayerDemosaicing { for (int y = roi.ybegin; y < roi.yend; y++) { typename BayerDemosaicing::Window window( - y, roi.xbegin, src); + y, roi.xbegin, src, x_offset, y_offset, white_balance); int r = (y_offset + y) % 2; Decoder calc_0 = decoders[r][(x_offset + roi.xbegin + 0) % 2]; @@ -360,20 +374,21 @@ class MHCBayerDemosaicing : public BayerDemosaicing { template static bool bayer_demosaic_linear_impl(ImageBuf& dst, const ImageBuf& src, - const std::string& bayer_pattern, ROI roi, - int nthreads) + const std::string& bayer_pattern, + const float white_balance[4], ROI roi, int nthreads) { LinearBayerDemosaicing obj; - return obj.process(dst, src, bayer_pattern, roi, nthreads); + return obj.process(dst, src, bayer_pattern, white_balance, roi, nthreads); } template static bool bayer_demosaic_MHC_impl(ImageBuf& dst, const ImageBuf& src, - const std::string& bayer_pattern, ROI roi, int nthreads) + const std::string& bayer_pattern, + const float white_balance[4], ROI roi, int nthreads) { MHCBayerDemosaicing obj; - return obj.process(dst, src, bayer_pattern, roi, nthreads); + return obj.process(dst, src, bayer_pattern, white_balance, roi, nthreads); return true; } @@ -389,6 +404,7 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, std::string pattern; std::string algorithm; std::string layout; + float white_balance_RGGB[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; for (auto&& pv : options) { if (pv.name() == pattern_us) { @@ -409,6 +425,25 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, } else { dst.errorfmt("ImageBufAlgo::demosaic() invalid layout"); } + } else if (pv.name() == white_balance_us) { + if (pv.type() == TypeFloat && pv.nvalues() == 4) { + // The order in the options is always (R,G1,B,G2) + white_balance_RGGB[0] = pv.get_float_indexed(0); + white_balance_RGGB[1] = pv.get_float_indexed(1); + white_balance_RGGB[2] = pv.get_float_indexed(3); + white_balance_RGGB[3] = pv.get_float_indexed(2); + + if (white_balance_RGGB[2] == 0) + white_balance_RGGB[2] = white_balance_RGGB[1]; + } else if (pv.type() == TypeFloat && pv.nvalues() == 3) { + // The order in the options is always (R,G,B) + white_balance_RGGB[0] = pv.get_float_indexed(0); + white_balance_RGGB[1] = pv.get_float_indexed(1); + white_balance_RGGB[2] = white_balance_RGGB[1]; + white_balance_RGGB[3] = pv.get_float_indexed(2); + } else { + dst.errorfmt("ImageBufAlgo::demosaic() invalid white balance"); + } } else { dst.errorfmt("ImageBufAlgo::demosaic() unknown parameter {}", pv.name()); @@ -418,7 +453,7 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, ROI dst_roi = roi; if (!dst_roi.defined()) { - dst_roi = src.roi_full(); + dst_roi = src.roi(); } dst_roi.chbegin = 0; @@ -449,12 +484,14 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, OIIO_DISPATCH_COMMON_TYPES2(ok, "bayer_demosaic_linear", bayer_demosaic_linear_impl, dst.spec().format, src.spec().format, - dst, src, layout, dst_roi, nthreads); + dst, src, layout, white_balance_RGGB, + dst_roi, nthreads); } else if (algorithm == "MHC") { OIIO_DISPATCH_COMMON_TYPES2(ok, "bayer_demosaic_MHC", bayer_demosaic_MHC_impl, dst.spec().format, src.spec().format, - dst, src, layout, dst_roi, nthreads); + dst, src, layout, white_balance_RGGB, + dst_roi, nthreads); } else { dst.errorfmt("ImageBufAlgo::demosaic() invalid algorithm"); } diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index dc9fa00162..7ff84634e6 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -3470,14 +3470,28 @@ OIIOTOOL_OP(warp, 1, [&](OiiotoolOp& op, span img) { // --demosaic OIIOTOOL_OP(demosaic, 1, [&](OiiotoolOp& op, span img) { - std::string pattern = op.options().get_string("pattern"); - std::string algorithm = op.options().get_string("algorithm"); - std::string layout = op.options().get_string("layout"); - - return ImageBufAlgo::demosaic(*img[0], *img[1], - { { "pattern", pattern }, - { "algorithm", algorithm }, - { "layout", layout } }); + ParamValueList list; + const std::vector keys = { "pattern", "algorithm", "layout" }; + for (const auto& key : keys) { + auto iter = op.options().find(key); + if (iter != op.options().cend()) { + list.push_back(iter[0]); + } + } + + std::string wb = op.options()["white_balance"]; + string_view str(wb); + float f3[3]; + float f4[4]; + if (Strutil::parse_values(str, "", f4, ",") && str.empty()) { + ParamValue pv("white_balance", TypeFloat, 4, f4); + list.push_back(pv); + } else if (Strutil::parse_values(str, "", f3, ",") && str.empty()) { + ParamValue pv("white_balance", TypeFloat, 3, f3); + list.push_back(pv); + } + + return ImageBufAlgo::demosaic(*img[0], *img[1], KWArgs(list)); }); @@ -6850,7 +6864,7 @@ Oiiotool::getargs(int argc, char* argv[]) .help("Render text into the current image (options: x=, y=, size=, color=)") .OTACTION(action_text); ap.arg("--demosaic") - .help("Demosaic (options: pattern=%s, algorithm=%s, layout=%s)") + .help("Demosaic (options: pattern=%s, algorithm=%s, layout=%s, white_balance=%f:R,G1,G2,B)") .OTACTION(action_demosaic); ap.separator("Manipulating channels or subimages:"); diff --git a/src/python/py_imagebufalgo.cpp b/src/python/py_imagebufalgo.cpp index 71e8862849..b93f928ab0 100644 --- a/src/python/py_imagebufalgo.cpp +++ b/src/python/py_imagebufalgo.cpp @@ -2377,28 +2377,38 @@ IBA_make_texture_filename(ImageBufAlgo::MakeTextureMode mode, ImageBuf IBA_demosaic_ret(const ImageBuf& src, const std::string& pattern = "", const std::string& algorithm = "", - const std::string& layout = "", ROI roi = ROI::All(), + const std::string& layout = "", + py::object white_balance = py::none(), ROI roi = ROI::All(), int nthreads = 0) { py::gil_scoped_release gil; + std::vector wb; + py_to_stdvector(wb, white_balance); return ImageBufAlgo::demosaic(src, { { "pattern", pattern }, { "algorithm", algorithm }, - { "layout", layout } }, + { "layout", layout }, + ParamValue("white_balance", TypeFloat, + wb.size(), wb.data()) }, roi, nthreads); } bool IBA_demosaic(ImageBuf& dst, const ImageBuf& src, const std::string& pattern = "", const std::string& algorithm = "", - const std::string& layout = "", ROI roi = ROI::All(), + const std::string& layout = "", + py::object white_balance = py::none(), ROI roi = ROI::All(), int nthreads = 0) { py::gil_scoped_release gil; + std::vector wb; + py_to_stdvector(wb, white_balance); return ImageBufAlgo::demosaic(dst, src, { { "pattern", pattern }, { "algorithm", algorithm }, - { "layout", layout } }, + { "layout", layout }, + ParamValue("white_balance", TypeFloat, + wb.size(), wb.data()) }, roi, nthreads); } @@ -3106,9 +3116,11 @@ declare_imagebufalgo(py::module& m) .def_static("demosaic", &IBA_demosaic, "dst"_a, "src"_a, "pattern"_a = "", "algorithm"_a = "", "layout"_a = "", - "roi"_a = ROI::All(), "nthreads"_a = 0) + "white_balance"_a = py::none(), "roi"_a = ROI::All(), + "nthreads"_a = 0) .def_static("demosaic", &IBA_demosaic_ret, "src"_a, "pattern"_a = "", - "algorithm"_a = "", "layout"_a = "", "roi"_a = ROI::All(), + "algorithm"_a = "", "layout"_a = "", + "white_balance"_a = py::none(), "roi"_a = ROI::All(), "nthreads"_a = 0); } diff --git a/testsuite/common/bayer.png b/testsuite/common/bayer.png new file mode 100644 index 0000000000..fb2e8f73d6 Binary files /dev/null and b/testsuite/common/bayer.png differ diff --git a/testsuite/docs-examples-cpp/ref/out-arm.txt b/testsuite/docs-examples-cpp/ref/out-arm.txt index 0fae4c9b02..b74e8bcec3 100644 --- a/testsuite/docs-examples-cpp/ref/out-arm.txt +++ b/testsuite/docs-examples-cpp/ref/out-arm.txt @@ -28,6 +28,7 @@ example_resize example_resample example_fit example_warp +example_demosaic example_add example_sub example_absdiff diff --git a/testsuite/docs-examples-cpp/ref/out.txt b/testsuite/docs-examples-cpp/ref/out.txt index 9805862c13..76c7941b0e 100644 --- a/testsuite/docs-examples-cpp/ref/out.txt +++ b/testsuite/docs-examples-cpp/ref/out.txt @@ -28,6 +28,7 @@ example_resize example_resample example_fit example_warp +example_demosaic example_add example_sub example_absdiff diff --git a/testsuite/docs-examples-cpp/run.py b/testsuite/docs-examples-cpp/run.py index a2b9fb2243..9a22369326 100755 --- a/testsuite/docs-examples-cpp/run.py +++ b/testsuite/docs-examples-cpp/run.py @@ -18,6 +18,7 @@ command += run_app("cmake -E copy " + test_source_dir + "/../common/with_nans.tif with_nans.tif") command += run_app("cmake -E copy " + test_source_dir + "/../common/checker_with_alpha.exr checker_with_alpha.exr") 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") # Copy the grid to both a tiled and scanline version command += oiio_app("iconvert") + "../common/grid.tif --scanline scanline.tif > out.txt ;" diff --git a/testsuite/docs-examples-cpp/src/docs-examples-imagebufalgo.cpp b/testsuite/docs-examples-cpp/src/docs-examples-imagebufalgo.cpp index 37bc5009e5..76f970ce0b 100644 --- a/testsuite/docs-examples-cpp/src/docs-examples-imagebufalgo.cpp +++ b/testsuite/docs-examples-cpp/src/docs-examples-imagebufalgo.cpp @@ -492,6 +492,21 @@ void example_warp() Dst.write("warp.exr"); } +void example_demosaic() +{ + print("example_demosaic\n"); + // BEGIN-imagebufalgo-demosaic + ImageBuf Src("bayer.png"); + float WB[3] = {2.0, 1.0, 1.5}; + ParamValue options[] = { + { "layout", "BGGR" }, + ParamValue("white_balance", TypeFloat, 3, WB) + }; + ImageBuf Dst = ImageBufAlgo::demosaic(Src, options); + // END-imagebufalgo-demosaic + Dst.write("demosaic.png"); +} + // Section: Image Arithmetic void example_add() @@ -703,6 +718,7 @@ int main(int /*argc*/, char** /*argv*/) example_resample(); example_fit(); example_warp(); + example_demosaic(); // Section: Image Arithmetic example_add(); diff --git a/testsuite/docs-examples-python/ref/out-arm.txt b/testsuite/docs-examples-python/ref/out-arm.txt index d2df876a60..9b898f1f90 100644 --- a/testsuite/docs-examples-python/ref/out-arm.txt +++ b/testsuite/docs-examples-python/ref/out-arm.txt @@ -29,6 +29,7 @@ example_resize example_resample example_fit example_warp +example_demosaic example_add example_sub example_absdiff diff --git a/testsuite/docs-examples-python/ref/out.txt b/testsuite/docs-examples-python/ref/out.txt index 0b8cdebdb9..258489c8f1 100644 --- a/testsuite/docs-examples-python/ref/out.txt +++ b/testsuite/docs-examples-python/ref/out.txt @@ -29,6 +29,7 @@ example_resize example_resample example_fit example_warp +example_demosaic example_add example_sub example_absdiff diff --git a/testsuite/docs-examples-python/run.py b/testsuite/docs-examples-python/run.py index 811eaaa3d6..59058657ef 100755 --- a/testsuite/docs-examples-python/run.py +++ b/testsuite/docs-examples-python/run.py @@ -18,6 +18,7 @@ command += run_app("cmake -E copy " + test_source_dir + "/../common/with_nans.tif with_nans.tif") command += run_app("cmake -E copy " + test_source_dir + "/../common/checker_with_alpha.exr checker_with_alpha.exr") 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") # Copy the grid to both a tiled and scanline version command += oiio_app("iconvert") + "../common/grid.tif --scanline scanline.tif > out.txt ;" diff --git a/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py b/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py index 68b48bdbae..daae8b3476 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py +++ b/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py @@ -434,6 +434,15 @@ def example_warp(): # END-imagebufalgo-warp Dst.write("warp.exr", "half") +def example_demosaic(): + print("example_demosaic") + # BEGIN-imagebufalgo-demosaic + Src = ImageBuf("bayer.png") + WB_RGBG = (2.0, 1.0, 1.5, 1.0) + Dst = ImageBufAlgo.demosaic(Src, layout="BGGR", + white_balance = WB_RGBG) + # END-imagebufalgo-demosaic + Dst.write("demosaic.png") # Section: Image Arithmetic def example_add(): @@ -610,6 +619,7 @@ def example_make_texture(): example_resample() example_fit() example_warp() + example_demosaic() # Section: Image Arithmetic example_add() diff --git a/testsuite/oiiotool-demosaic/ref/out.txt b/testsuite/oiiotool-demosaic/ref/out.txt index 95a9f7e731..1c4d27df7c 100644 --- a/testsuite/oiiotool-demosaic/ref/out.txt +++ b/testsuite/oiiotool-demosaic/ref/out.txt @@ -1,64 +1,64 @@ -Computing diff of "testimage.exr" vs "result_float_RGGB-linear.exr" +Computing diff of "testimage.exr" vs "result_float_RGGB_WB-linear.exr" PASS -Computing diff of "testimage.exr" vs "result_float_GRBG-linear.exr" +Computing diff of "testimage.exr" vs "result_float_RGGB_WB-MHC.exr" PASS -Computing diff of "testimage.exr" vs "result_float_GBRG-linear.exr" +Computing diff of "testimage.exr" vs "result_float_GRBG_WB-linear.exr" PASS -Computing diff of "testimage.exr" vs "result_float_BGGR-linear.exr" +Computing diff of "testimage.exr" vs "result_float_GRBG_WB-MHC.exr" PASS -Computing diff of "testimage.exr" vs "result_float_RGGB-MHC.exr" +Computing diff of "testimage.exr" vs "result_float_GBRG_WB-linear.exr" PASS -Computing diff of "testimage.exr" vs "result_float_GRBG-MHC.exr" +Computing diff of "testimage.exr" vs "result_float_GBRG_WB-MHC.exr" PASS -Computing diff of "testimage.exr" vs "result_float_GBRG-MHC.exr" +Computing diff of "testimage.exr" vs "result_float_BGGR_WB-linear.exr" PASS -Computing diff of "testimage.exr" vs "result_float_BGGR-MHC.exr" +Computing diff of "testimage.exr" vs "result_float_BGGR_WB-MHC.exr" PASS Computing diff of "testimage.exr" vs "result_half_RGGB-linear.exr" PASS -Computing diff of "testimage.exr" vs "result_half_GRBG-linear.exr" -PASS -Computing diff of "testimage.exr" vs "result_half_GBRG-linear.exr" +Computing diff of "testimage.exr" vs "result_half_RGGB-MHC.exr" PASS -Computing diff of "testimage.exr" vs "result_half_BGGR-linear.exr" +Computing diff of "testimage.exr" vs "result_uint16_RGGB-linear.tiff" PASS -Computing diff of "testimage.exr" vs "result_half_RGGB-MHC.exr" +Computing diff of "testimage.exr" vs "result_uint16_RGGB-MHC.tiff" PASS -Computing diff of "testimage.exr" vs "result_half_GRBG-MHC.exr" +Computing diff of "testimage.exr" vs "result_uint8_RGGB-linear.tiff" PASS -Computing diff of "testimage.exr" vs "result_half_GBRG-MHC.exr" +Computing diff of "testimage.exr" vs "result_uint8_RGGB-MHC.tiff" PASS -Computing diff of "testimage.exr" vs "result_half_BGGR-MHC.exr" +Computing diff of "testimage.exr" vs "result_half_GRBG-linear.exr" PASS -Computing diff of "testimage.exr" vs "result_uint16_RGGB-linear.tiff" +Computing diff of "testimage.exr" vs "result_half_GRBG-MHC.exr" PASS Computing diff of "testimage.exr" vs "result_uint16_GRBG-linear.tiff" PASS -Computing diff of "testimage.exr" vs "result_uint16_GBRG-linear.tiff" -PASS -Computing diff of "testimage.exr" vs "result_uint16_BGGR-linear.tiff" +Computing diff of "testimage.exr" vs "result_uint16_GRBG-MHC.tiff" PASS -Computing diff of "testimage.exr" vs "result_uint16_RGGB-MHC.tiff" +Computing diff of "testimage.exr" vs "result_uint8_GRBG-linear.tiff" PASS -Computing diff of "testimage.exr" vs "result_uint16_GRBG-MHC.tiff" +Computing diff of "testimage.exr" vs "result_uint8_GRBG-MHC.tiff" PASS -Computing diff of "testimage.exr" vs "result_uint16_GBRG-MHC.tiff" +Computing diff of "testimage.exr" vs "result_half_GBRG-linear.exr" PASS -Computing diff of "testimage.exr" vs "result_uint16_BGGR-MHC.tiff" +Computing diff of "testimage.exr" vs "result_half_GBRG-MHC.exr" PASS -Computing diff of "testimage.exr" vs "result_uint8_RGGB-linear.tiff" +Computing diff of "testimage.exr" vs "result_uint16_GBRG-linear.tiff" PASS -Computing diff of "testimage.exr" vs "result_uint8_GRBG-linear.tiff" +Computing diff of "testimage.exr" vs "result_uint16_GBRG-MHC.tiff" PASS Computing diff of "testimage.exr" vs "result_uint8_GBRG-linear.tiff" PASS -Computing diff of "testimage.exr" vs "result_uint8_BGGR-linear.tiff" +Computing diff of "testimage.exr" vs "result_uint8_GBRG-MHC.tiff" PASS -Computing diff of "testimage.exr" vs "result_uint8_RGGB-MHC.tiff" +Computing diff of "testimage.exr" vs "result_half_BGGR-linear.exr" PASS -Computing diff of "testimage.exr" vs "result_uint8_GRBG-MHC.tiff" +Computing diff of "testimage.exr" vs "result_half_BGGR-MHC.exr" PASS -Computing diff of "testimage.exr" vs "result_uint8_GBRG-MHC.tiff" +Computing diff of "testimage.exr" vs "result_uint16_BGGR-linear.tiff" +PASS +Computing diff of "testimage.exr" vs "result_uint16_BGGR-MHC.tiff" +PASS +Computing diff of "testimage.exr" vs "result_uint8_BGGR-linear.tiff" PASS Computing diff of "testimage.exr" vs "result_uint8_BGGR-MHC.tiff" PASS diff --git a/testsuite/oiiotool-demosaic/run.py b/testsuite/oiiotool-demosaic/run.py index 773d9487da..5bce2e39fe 100755 --- a/testsuite/oiiotool-demosaic/run.py +++ b/testsuite/oiiotool-demosaic/run.py @@ -10,75 +10,109 @@ width = 160 height = 120 -# oiiotool commands to make each of the Bayer patterns -make_pattern_RGGB = f"--pattern constant:color=0,1,0 {width}x{height} 3 -for y \"0,{{TOP.height}},2\" -for x \"0,{{TOP.width}},2\" -point:color=1,0,0 \"{{x}},{{y}}\" -point:color=0,0,1 \"{{x+1}},{{y+1}}\" -endfor -endfor" -make_pattern_GRBG = f"--pattern constant:color=0,1,0 {width}x{height} 3 -for y \"0,{{TOP.height}},2\" -for x \"1,{{TOP.width}},2\" -point:color=1,0,0 \"{{x}},{{y}}\" -point:color=0,0,1 \"{{x-1}},{{y+1}}\" -endfor -endfor" -make_pattern_GBRG = f"--pattern constant:color=0,1,0 {width}x{height} 3 -for y \"1,{{TOP.height}},2\" -for x \"0,{{TOP.width}},2\" -point:color=1,0,0 \"{{x}},{{y}}\" -point:color=0,0,1 \"{{x+1}},{{y-1}}\" -endfor -endfor" -make_pattern_BGGR = f"--pattern constant:color=0,1,0 {width}x{height} 3 -for y \"1,{{TOP.height}},2\" -for x \"1,{{TOP.width}},2\" -point:color=1,0,0 \"{{x}},{{y}}\" -point:color=0,0,1 \"{{x-1}},{{y-1}}\" -endfor -endfor" - -layouts = { - "RGGB": make_pattern_RGGB, - "GRBG": make_pattern_GRBG, - "GBRG": make_pattern_GBRG, - "BGGR": make_pattern_BGGR -} - -types = [ - { - "type" : "float", - "ext" : "exr", - "threshold": 0.0000003 - }, - { - "type" : "half", - "ext" : "exr", - "threshold": 0.0005 - }, +# Create a test image with color gradient +make_testimage = f"--pattern fill:topleft=0,0,1:topright=0,1,0:bottomleft=1,0,1:bottomright=1,1,0 {width}x{height} 3 " +command += oiiotool (make_testimage + f" -o:type=float testimage.exr") + +tests = [ + # Making an un-white-balanced test image is difficult, especially with + # split weights on the green sub-channels. Un-white-balancing the intermediate + # bayer images makes that step contribute to the error. The smaller data + # types don't round-trip cleanly, and that does not make a fair test anyway. + # So we only test white-balancing on the float type. { - "type" : "uint16", - "ext" : "tiff", - "threshold": 0.000016 + 'suffix' : '_WB', + 'WB' : [1.8, 0.8, 1.2, 1.5], + 'types' : [ + { + "type" : "float", + "ext" : "exr", + "threshold": 0.0000003 + } + ] }, { - "type" : "uint8", - "ext" : "tiff", - "threshold": 0.004 + 'suffix' : '', + 'WB' : [1.0, 1.0, 1.0, 1.0], + 'types' : [ + { + "type" : "half", + "ext" : "exr", + "threshold": 0.0005 + }, + { + "type" : "uint16", + "ext" : "tiff", + "threshold": 0.000016 + }, + { + "type" : "uint8", + "ext" : "tiff", + "threshold": 0.004 + } + ] } ] +def make_pattern(x_offset, y_offset): + ri = ((0 + x_offset) % 2) + 2 * ((0 + y_offset) % 2) + g1i = ((1 + x_offset) % 2) + 2 * ((0 + y_offset) % 2) + g2i = ((0 + x_offset) % 2) + 2 * ((1 + y_offset) % 2) + bi = ((1 + x_offset) % 2) + 2 * ((1 + y_offset) % 2) -# Create a test image with color gradient -make_testimage = f"--pattern fill:topleft=0,0,1:topright=0,1,0:bottomleft=1,0,1:bottomright=1,1,0 {width}x{height} 3 " -command += oiiotool (make_testimage + f" -o:type=float testimage.exr") + pattern = ' ' + pattern = pattern[:ri ] + 'R' + pattern[ri + 1:] + pattern = pattern[:g1i] + 'G' + pattern[g1i + 1:] + pattern = pattern[:g2i] + 'G' + pattern[g2i + 1:] + pattern = pattern[:bi ] + 'B' + pattern[bi + 1:] + return pattern + + +for dict in tests: + suffix = dict['suffix'] + + WB_R = dict['WB'][0] + WB_G1 = dict['WB'][1] + WB_G2 = dict['WB'][2] + WB_B = dict['WB'][3] + + # For each Bayer pattern (RGGB, RGRB, GBRG, BGGR), create an image with that + # pure pattern ({pattern}.exr), then multiply it by the test image and take + # the channel sum to get a Bayer mosaic image ({pattern}-bayer.exr). + # This is somewhat expensive, so we do it only once (for float) and then will + # convert it to the appropriate type upon input. + for yy in range(0, 2): + for xx in range(0, 2): + + pattern = make_pattern(xx, yy) + + point_r = f"-point:color={1.0 / WB_R},0,0 \"{{x+({xx}+0) % 2}},{{y+({yy}+0) % 2}}\"" + point_g1 = f"-point:color=0,{1.0 / WB_G1},0 \"{{x+({xx}+1) % 2}},{{y+({yy}+0) % 2}}\"" + point_g2 = f"-point:color=0,{1.0 / WB_G2},0 \"{{x+({xx}+0) % 2}},{{y+({yy}+1) % 2}}\"" + point_b = f"-point:color=0,0,{1.0 / WB_B} \"{{x+({xx}+1) % 2}},{{y+({yy}+1) % 2}}\"" + + maker = f"--pattern constant:color=0,0,0 {width}x{height} 3 -for y \"0,{{TOP.height}},2\" -for x \"0,{{TOP.width}},2\" {point_r} {point_g1} {point_g2} {point_b} -endfor -endfor" + command += oiiotool (f"{maker} -o:type=float pattern_{pattern}{suffix}.exr testimage.exr -mul -chsum -o:type=float bayer_{pattern}{suffix}.exr") + + for type_dict in dict["types"]: + + type = type_dict["type"] + ext = type_dict["ext"] + threshold = type_dict["threshold"] + + test = f" --fail {threshold} --hardfail {threshold} --warn {threshold} --diff " + + # For each algorithm, try demosaicing each pattern test image and compare to + # the original test image. + for algo in ['linear', 'MHC']: + + command += oiiotool (f"-i:type={type} testimage.exr -i:type={type} bayer_{pattern}{suffix}.exr --demosaic:algorithm={algo}:layout={pattern}:white_balance={WB_R},{WB_G1},{WB_B},{WB_G2} -o:type={type} result_{type}_{pattern}{suffix}-{algo}.{ext} ") + + crop = 2 + + if crop > 0: + cut_cmd = f"-cut {{TOP.width-{crop}*2}}x{{TOP.height-{crop}*2}}+{{TOP.x+{crop}}}+{{TOP.y+{crop}}} " + else: + cut_cmd = "" -# For each Bayer pattern (RGGB, RGRB, GBRG, BGGR), create an image with that -# pure pattern ({pattern}.exr), then multiply it by the test image and take -# the channel sum to get a Bayer mosaic image ({pattern}-bayer.exr). -# This is somewhat expensive, so we do it only once (for float) and then will -# convert it to the appropriate type upon input. -for pattern, maker in layouts.items(): - command += oiiotool (f"{maker} -o:type=float pattern_{pattern}.exr testimage.exr -mul -chsum -o:type=float bayer_{pattern}.exr") - - -for dict in types: - - type = dict["type"] - ext = dict["ext"] - threshold = dict["threshold"] - - test = f" --fail {threshold} --hardfail {threshold} --warn {threshold} --diff " - - # For each algorithm, try demosaicing each pattern test image and compare to - # the original test image. - for algo in ['linear', 'MHC']: - for pattern, maker in layouts.items(): - command += oiiotool (f"-i:type={type} testimage.exr -i:type={type} bayer_{pattern}.exr --demosaic:algorithm={algo}:layout={pattern} -o:type={type} result_{type}_{pattern}-{algo}.{ext} ") - - crop = 2 - - if crop > 0: - cut_cmd = f"-cut {{TOP.width-{crop}*2}}x{{TOP.height-{crop}*2}}+{{TOP.x+{crop}}}+{{TOP.y+{crop}}} " - else: - cut_cmd = "" - - command += oiiotool (f"testimage.exr " + cut_cmd + f"result_{type}_{pattern}-{algo}.{ext} " + cut_cmd + test) + command += oiiotool (f"testimage.exr " + cut_cmd + f"result_{type}_{pattern}{suffix}-{algo}.{ext} " + cut_cmd + test)