Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat]IBA:demosaic add white balancing #4499

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
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
10 changes: 7 additions & 3 deletions src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions src/doc/imagebufalgo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1166,19 +1166,24 @@ Shuffling channels
ImageSpec hint;
hint["raw:Demosaic"] = "none";
ImageBuf Src ("test.cr3", 0, 0, nullptr, &hint);
ParamValue options[] = { { "layout", "GRBG" } };
float wb[4] = {2.0, 0.8, 1.2, 1.5};
ParamValue options[] = {
{ "layout", "GRBG" },
ParamValue("white-balance", TypeFloat, 4, wb)
};
Comment on lines +1169 to +1173
Copy link
Collaborator

@lgritz lgritz Oct 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not requesting this as part of this PR, but just noting that at some point, we should do the thing where we move these actual code snippets to the appropriate parts of testsuite/docs-examples-cpp and testsuite/docs-examples-python, and then just include them by reference in the docs, so that it is confirmed that they compile and work correct with every CI run.

ImageBuf Dst = ImageBufAlgo::demosaic (Src, options);

.. code-tab:: py

hint = ImageSpec()
hint["raw:Demosaic"] = "none"
Src = ImageBuf("test.cr3", 0, 0, hint)
Dst = OpenImageIO.ImageBufAlgo.demosaic(Src, layout="GRBG")
Dst = OpenImageIO.ImageBufAlgo.demosaic(Src, layout="GRBG",
white_balance = (2.0, 0.8, 1.2, 1.5))

.. 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

|

Expand Down
29 changes: 29 additions & 0 deletions src/doc/oiiotool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4184,6 +4184,35 @@ 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 (RGB) or four
(RGGB) values.

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
=================================================

Expand Down
18 changes: 18 additions & 0 deletions src/doc/pythonbindings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3680,6 +3680,24 @@ 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 (RGB), or four (RGGB) values.

Example:

.. code-block:: python

Src = ImageBuf("test.cr3", 0, 0, hint)
Dst = OpenImageIO.ImageBufAlgo.demosaic(Src, layout="GRBG",
white_balance = (2.0, 0.8, 1.2, 1.5))




.. _sec-iba-py-importexport:
Expand Down
3 changes: 3 additions & 0 deletions src/include/OpenImageIO/imagebufalgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2163,6 +2163,9 @@ 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 (RGB), or four (RGGB) values.
Comment on lines +2166 to +2168
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a use case for specifying two green values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have seen some images recently with the greens in red and blue rows having distinctly different levels. Sony A99, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a use case for specifying two green values?

RAW_PENTAX_K200D.PEF in the test suite has 2 different weights for the green sub-channels


ImageBuf OIIO_API demosaic (const ImageBuf& src, KWArgs options = {},
ROI roi = {}, int nthreads = 0);
Expand Down
62 changes: 46 additions & 16 deletions src/libOpenImageIO/imagebufalgo_demosaic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -31,7 +32,17 @@ template<class Rtype, class Atype, int size> class BayerDemosaicing {
/// the source iterator.
struct Row {
ImageBuf::ConstIterator<Atype> 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<Row> rows;
Expand All @@ -46,7 +57,8 @@ template<class Rtype, class Atype, int size> 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);
Expand Down Expand Up @@ -78,12 +90,15 @@ template<class Rtype, class Atype, int size> class BayerDemosaicing {
ystart = src_yend - 1 - (ystart - src_yend + 1) % 2;
}

Row row
= { ImageBuf::ConstIterator<Atype>(src, xstart, ystart) };
int x_off = (xstart + x_offset) % 2;
int y_off = ((ystart + y_offset) % 2) * 2;

Row row = { ImageBuf::ConstIterator<Atype>(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++) {
Expand Down Expand Up @@ -113,8 +128,7 @@ template<class Rtype, class Atype, int size> 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];
Expand All @@ -141,7 +155,7 @@ template<class Rtype, class Atype, int size> 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;

Expand All @@ -167,7 +181,7 @@ template<class Rtype, class Atype, int size> class BayerDemosaicing {

for (int y = roi.ybegin; y < roi.yend; y++) {
typename BayerDemosaicing<Rtype, Atype, size>::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];
Expand Down Expand Up @@ -360,20 +374,21 @@ class MHCBayerDemosaicing : public BayerDemosaicing<Rtype, Atype, size> {
template<class Rtype, class Atype>
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<Rtype, Atype> obj;
return obj.process(dst, src, bayer_pattern, roi, nthreads);
return obj.process(dst, src, bayer_pattern, white_balance, roi, nthreads);
}

template<class Rtype, class Atype>
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<Rtype, Atype> obj;
return obj.process(dst, src, bayer_pattern, roi, nthreads);
return obj.process(dst, src, bayer_pattern, white_balance, roi, nthreads);

return true;
}
Expand All @@ -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[4] = { 1.0f, 1.0f, 1.0f, 1.0f };

for (auto&& pv : options) {
if (pv.name() == pattern_us) {
Expand All @@ -409,6 +425,18 @@ 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) {
for (size_t i = 0; i < 4; i++)
white_balance[i] = pv.get_float_indexed(i);
} else if (pv.type() == TypeFloat && pv.nvalues() == 3) {
white_balance[0] = pv.get_float_indexed(0);
white_balance[1] = pv.get_float_indexed(1);
white_balance[2] = white_balance[1];
white_balance[3] = pv.get_float_indexed(2);
} else {
dst.errorfmt("ImageBufAlgo::demosaic() invalid white balance");
}
} else {
dst.errorfmt("ImageBufAlgo::demosaic() unknown parameter {}",
pv.name());
Expand Down Expand Up @@ -449,12 +477,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,
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,
dst_roi, nthreads);
} else {
dst.errorfmt("ImageBufAlgo::demosaic() invalid algorithm");
}
Expand Down
32 changes: 23 additions & 9 deletions src/oiiotool/oiiotool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3458,14 +3458,28 @@ OIIOTOOL_OP(warp, 1, [&](OiiotoolOp& op, span<ImageBuf*> img) {

// --demosaic
OIIOTOOL_OP(demosaic, 1, [&](OiiotoolOp& op, span<ImageBuf*> 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<std::string> 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));
});


Expand Down Expand Up @@ -6834,7 +6848,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:");
Expand Down
24 changes: 18 additions & 6 deletions src/python/py_imagebufalgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float> 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<float> 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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
Loading
Loading