Skip to content

Commit 79e5c12

Browse files
lgritzscott-wilson
authored andcommitted
feat: ImageBuf::merge_metadata and oiiotool --mergemeta (AcademySoftwareFoundation#4672)
Clarify that IB::copy_metadata and oiiotool --pastemea fully replace all existing metadata with that of the other IB. Add a new IB::merge_metadata() and oiiotool --mergemeta that merges in metadata from the other IB, preserving metadata in `this` that was not in the other IB. Optionally, metadata that existed in both can be overridden. Optionally, a subset of metadata to be merged can be selected via a regex. Signed-off-by: Larry Gritz <[email protected]> Signed-off-by: Scott Wilson <[email protected]>
1 parent 44dc236 commit 79e5c12

File tree

14 files changed

+610
-28
lines changed

14 files changed

+610
-28
lines changed

src/doc/imagebuf.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ Copying ImageBuf's and blocks of pixels
149149
.. doxygenfunction:: OIIO::ImageBuf::operator=(ImageBuf &&src)
150150
.. doxygenfunction:: OIIO::ImageBuf::copy(const ImageBuf &src, TypeDesc format = TypeUnknown)
151151
.. doxygenfunction:: OIIO::ImageBuf::copy(TypeDesc format) const
152-
.. doxygenfunction:: OIIO::ImageBuf::copy_metadata
153152
.. doxygenfunction:: OIIO::ImageBuf::copy_pixels
153+
.. doxygenfunction:: OIIO::ImageBuf::copy_metadata
154+
.. doxygenfunction:: OIIO::ImageBuf::merge_metadata
154155
.. doxygenfunction:: OIIO::ImageBuf::swap
155156

156157

src/doc/oiiotool.rst

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2998,19 +2998,46 @@ current top image.
29982998

29992999
Takes two images -- the first will be a source of metadata only, and the
30003000
second the source of pixels -- and produces a new copy of the second
3001-
image with the metadata from the first image added.
3001+
image with the metadata from the first image.
30023002

3003-
The output image's pixels will come only from the second input. Metadata
3004-
from the second input will be preserved if no identically-named metadata
3005-
was present in the first input image.
3006-
3007-
Examples::
3003+
Example::
30083004

30093005
# Add all the metadata from meta.exr to pixels.exr and write the
30103006
# combined image to out.exr.
30113007
oiiotool meta.exr pixels.exr --pastemeta -o out.exr
30123008

30133009

3010+
.. option:: --mergemeta <location>
3011+
3012+
Takes two images -- the first will be a source of metadata only, and the
3013+
second the source of pixels -- and produces a new copy of the second
3014+
image with the metadata from the first image added added to the second
3015+
image, item by item (i.e. not wholly removing the metadata that was
3016+
there before).
3017+
3018+
Optional appended modifiers include:
3019+
3020+
- `override=` *int* : If zero (the default), no
3021+
existing metadata in the destination will be altered, i.e., the only
3022+
metadata copied from source to destination will be those that did not
3023+
previously exist in the destination. If the override value is nonzero,
3024+
then all metadata items in the source will *replace* any existing
3025+
correspondingly named items in the destination.
3026+
3027+
- `pattern=` *regex* : If supplied, only copies metadata whose name
3028+
matches has a substring matching the regular expression. The special
3029+
character `^` indicates the beginning of the string and `$` indicates
3030+
the end of the string.
3031+
3032+
Example::
3033+
3034+
# Add all of meta.exr's metadata whose name begins with "camera:"
3035+
# to pixels.exr, replacing any similarly named items, and write
3036+
# the combined image to out.exr.
3037+
oiiotool meta.exr pixels.exr --pastemeta:pattern="^camera:override=1" -o out.exr
3038+
3039+
The `--mergemeta` command was added in OIIO 3.0.4.
3040+
30143041
.. option:: --mosaic <size>
30153042

30163043
Removes :math:`w \times h` images from the stack, dictated by the *size*,

src/doc/pythonbindings.rst

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,19 +1966,6 @@ awaiting a call to `reset()` or `copy()` before it is useful.
19661966
the ImageBuf.
19671967
19681968
1969-
.. py:method:: ImageBuf.copy_metadata (other_imagebuf)
1970-
1971-
Replaces the metadata (all ImageSpec items, except for the data format
1972-
and pixel data window size) with the corresponding metadata from the
1973-
other ImageBuf.
1974-
1975-
1976-
.. py:method:: ImageBuf.copy_pixels (other_imagebuf)
1977-
1978-
Replace the pixels in this ImageBuf with the values from the other
1979-
ImageBuf.
1980-
1981-
19821969
.. py:method:: ImageBuf ImageBuf.copy (format=TypeUnknown)
19831970
19841971
Return a full copy of this ImageBuf (with optional data format
@@ -2020,6 +2007,40 @@ awaiting a call to `reset()` or `copy()` before it is useful.
20202007
20212008
20222009
2010+
.. py:method:: ImageBuf.copy_pixels (other_imagebuf)
2011+
2012+
Replace the pixels in this ImageBuf with the values from the other
2013+
ImageBuf.
2014+
2015+
2016+
.. py:method:: ImageBuf.copy_metadata (other_imagebuf)
2017+
2018+
Replace the metadata of `Self` (all ImageSpec items, except for the data
2019+
format and pixel data window size) with the metadata from the other
2020+
ImageBuf.
2021+
2022+
2023+
.. py:method:: ImageBuf.merge_metadata (src, override : bool = False, pattern : str = "")
2024+
2025+
Merge metadata from `src` into the metadata of `Self` (except for the data
2026+
format and pixel data window size). Metadata in `Self` that is not in
2027+
`src` will not be altered. Metadata in `Self` that also is in `src` will
2028+
be replaced only if `override` is True. If `pattern` is not empty, only
2029+
metadata having a substring that matches the regex pattern will be merged.
2030+
2031+
@version 3.0.5+
2032+
2033+
Example:
2034+
2035+
.. code-block:: python
2036+
2037+
A = ImageBuf("A.exr")
2038+
B = ImageBuf("B.exr")
2039+
A.merge_metadata(B, True, "^camera:")
2040+
# Now A contains all of B's metadata whose name starts with the
2041+
# substring "camera:"
2042+
2043+
20232044
.. py:method:: ImageBuf.swap (other_imagebuf)
20242045
20252046
Swaps the content of this ImageBuf and the other ImageBuf.

src/include/OpenImageIO/imagebuf.h

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,10 +669,36 @@ class OIIO_API ImageBuf {
669669
/// Move assignment.
670670
const ImageBuf& operator=(ImageBuf&& src);
671671

672-
/// Copy all the metadata from `src` to `*this` (except for pixel data
673-
/// resolution, channel types and names, and data format).
672+
/// Copy all the metadata from `src` to `*this`, replacing all named
673+
/// metadata that was previously in `*this`. The "full" size and desired
674+
/// tile size will also be replaced by the corresponding values from
675+
/// `src`, but the pixel data resolution, channel types and names, and
676+
/// data format of `*this` will not be altered.
674677
void copy_metadata(const ImageBuf& src);
675678

679+
/// Merge metadata from `src` into the metadata of `*this` (except for the
680+
/// data format and pixel data window size). Metadata in `*this` that is
681+
/// not in `src` will not be altered. Metadata in `*this` that also is in
682+
/// `src` will be replaced only if `override` is True. If `pattern` is not
683+
/// empty, only metadata having a substring that matches the regex pattern
684+
/// will be merged.
685+
///
686+
/// @param src
687+
/// The source ImageBuf supplying the metadata (but not pixel
688+
/// values).
689+
/// @param override
690+
/// If true, `src` attributes will replace any identically-named
691+
/// attributes already in `*this`. If false (the default), only
692+
/// attributes whose names are not already in this list will be
693+
/// appended.
694+
/// @param pattern
695+
/// If not empty, only copy metadata from `src` whose name contains
696+
/// a substring matching the regex `pattern`.
697+
///
698+
/// @version 3.0.5+
699+
void merge_metadata(const ImageBuf& src, bool override = false,
700+
string_view pattern = {});
701+
676702
/// Copy the pixel data from `src` to `*this`, automatically converting
677703
/// to the existing data format of `*this`. It only copies pixels in
678704
/// the overlap regions (and channels) of the two images; pixel data in

src/libOpenImageIO/imagebuf.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <iostream>
77
#include <memory>
8+
#include <regex>
89

910
#include <OpenImageIO/half.h>
1011

@@ -169,6 +170,8 @@ class ImageBufImpl {
169170
void* progress_callback_data = nullptr,
170171
DoLock do_lock = DoLock(true));
171172
void copy_metadata(const ImageBufImpl& src);
173+
void merge_metadata(const ImageBufImpl& src, bool override = false,
174+
string_view pattern = {});
172175

173176
// At least one of bufspan or buforigin is supplied. Set this->m_bufspan
174177
// and this->m_localpixels appropriately.
@@ -1774,6 +1777,55 @@ ImageBuf::copy_metadata(const ImageBuf& src)
17741777

17751778

17761779

1780+
void
1781+
ImageBufImpl::merge_metadata(const ImageBufImpl& src, bool override,
1782+
string_view pattern)
1783+
{
1784+
ImageSpec& myspec(this->specmod());
1785+
const ImageSpec& srcspec(src.spec());
1786+
std::regex re(std::string(pattern), std::regex_constants::basic);
1787+
for (const auto& a : srcspec.extra_attribs) {
1788+
if ((pattern.empty() || std::regex_search(a.name().string(), re))
1789+
&& (override || !myspec.extra_attribs.contains(a.name())))
1790+
myspec.attribute(a.name(), a.type(), a.data());
1791+
}
1792+
1793+
if (override) {
1794+
if (pattern.empty() || std::regex_search("full_geom", re)) {
1795+
m_spec.full_x = srcspec.full_x;
1796+
m_spec.full_y = srcspec.full_y;
1797+
m_spec.full_z = srcspec.full_z;
1798+
m_spec.full_width = srcspec.full_width;
1799+
m_spec.full_height = srcspec.full_height;
1800+
m_spec.full_depth = srcspec.full_depth;
1801+
}
1802+
if (pattern.empty() || std::regex_search("tile_size", re)) {
1803+
if (src.storage() == ImageBuf::IMAGECACHE) {
1804+
// If we're copying metadata from a cached image, be sure to
1805+
// get the file's tile size, not the cache's tile size.
1806+
m_spec.tile_width = src.nativespec().tile_width;
1807+
m_spec.tile_height = src.nativespec().tile_height;
1808+
m_spec.tile_depth = src.nativespec().tile_depth;
1809+
} else {
1810+
m_spec.tile_width = srcspec.tile_width;
1811+
m_spec.tile_height = srcspec.tile_height;
1812+
m_spec.tile_depth = srcspec.tile_depth;
1813+
}
1814+
}
1815+
}
1816+
}
1817+
1818+
1819+
1820+
void
1821+
ImageBuf::merge_metadata(const ImageBuf& src, bool override,
1822+
string_view pattern)
1823+
{
1824+
m_impl->merge_metadata(*src.m_impl, override, pattern);
1825+
}
1826+
1827+
1828+
17771829
const ImageSpec&
17781830
ImageBuf::spec() const
17791831
{

src/oiiotool/oiiotool.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4765,6 +4765,18 @@ OIIOTOOL_OP(pastemeta, 2, [&](OiiotoolOp& op, span<ImageBuf*> img) {
47654765

47664766

47674767

4768+
// --mergemeta
4769+
OIIOTOOL_OP(mergemeta, 2, [&](OiiotoolOp& op, span<ImageBuf*> img) {
4770+
std::string pattern = op.options("pattern");
4771+
bool override = op.options("override").get<int>();
4772+
4773+
*img[0] = *img[2];
4774+
img[0]->merge_metadata(*img[1], override, pattern);
4775+
return true;
4776+
});
4777+
4778+
4779+
47684780
// --mosaic
47694781
static void
47704782
action_mosaic(Oiiotool& ot, cspan<const char*> argv)
@@ -6787,8 +6799,11 @@ Oiiotool::getargs(int argc, char* argv[])
67876799
.help("Paste fg over bg at the given position (e.g., +100+50; '-' or 'auto' indicates using the data window position as-is; options: all=%d, mergeroi=%d)")
67886800
.OTACTION(action_paste);
67896801
ap.arg("--pastemeta")
6790-
.help("Copy the metadata from the first image to the second image and write the combined result.")
6802+
.help("Copy the metadata from the first image to the second image and keep the combined result")
67916803
.OTACTION(action_pastemeta);
6804+
ap.arg("--mergemeta")
6805+
.help("Merge the metadata from the first image into the second image and keep the combined result (options: pattern=REGEX, override=%d)")
6806+
.OTACTION(action_mergemeta);
67926807
ap.arg("--mosaic %s:WxH")
67936808
.help("Assemble images into a mosaic (arg: WxH; options: pad=0, fit=WxH)")
67946809
.OTACTION(action_mosaic);

src/python/py_imagebuf.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,6 @@ declare_imagebuf(py::module& m)
426426

427427
.def("pixelindex", &ImageBuf::pixelindex, "x"_a, "y"_a, "z"_a,
428428
"check_range"_a = false)
429-
.def("copy_metadata", &ImageBuf::copy_metadata)
430-
.def("copy_pixels", &ImageBuf::copy_pixels)
431429
.def(
432430
"copy",
433431
[](ImageBuf& self, const ImageBuf& src, TypeDesc format) {
@@ -442,6 +440,15 @@ declare_imagebuf(py::module& m)
442440
return src.copy(format);
443441
},
444442
"format"_a = TypeUnknown)
443+
.def("copy_pixels", &ImageBuf::copy_pixels)
444+
.def("copy_metadata", &ImageBuf::copy_metadata)
445+
.def(
446+
"merge_metadata",
447+
[](ImageBuf& self, const ImageBuf& src, bool override,
448+
const std::string& pattern) {
449+
self.merge_metadata(src, override, pattern);
450+
},
451+
"src"_a, "override"_a = false, "pattern"_a = "")
445452
.def("swap", &ImageBuf::swap)
446453
.def("getchannel", &ImageBuf::getchannel, "x"_a, "y"_a, "z"_a, "c"_a,
447454
"wrap"_a = "black")

testsuite/oiiotool-copy/ref/out.txt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ green.exr : 64 x 64, 3 channel, half openexr
7676
SHA-1: 8B61993247469F3C208CA894D71856727B11606A
7777
channel list: R, G, B
7878
compression: "zip"
79+
hair: "black"
7980
PixelAspectRatio: 1
8081
screenWindowCenter: 0, 0
8182
screenWindowWidth: 1
@@ -93,6 +94,54 @@ greenmeta.exr : 64 x 64, 3 channel, half openexr
9394
screenWindowCenter: 0, 0
9495
screenWindowWidth: 1
9596
weight: 20.5
97+
camera:lens: "AX30"
98+
camera:shutter: 0.0125
99+
oiio:ColorSpace: "lin_rec709"
100+
oiio:subimages: 1
101+
openexr:lineOrder: "increasingY"
102+
Reading greenmeta-merge.exr
103+
greenmeta-merge.exr : 64 x 64, 3 channel, half openexr
104+
SHA-1: 8B61993247469F3C208CA894D71856727B11606A
105+
channel list: R, G, B
106+
compression: "zip"
107+
eyes: 2
108+
hair: "black"
109+
PixelAspectRatio: 1
110+
screenWindowCenter: 0, 0
111+
screenWindowWidth: 1
112+
weight: 20.5
113+
camera:lens: "AX30"
114+
camera:shutter: 0.0125
115+
oiio:ColorSpace: "lin_rec709"
116+
oiio:subimages: 1
117+
openexr:lineOrder: "increasingY"
118+
Reading greenmeta-merge-override.exr
119+
greenmeta-merge-override.exr : 64 x 64, 3 channel, half openexr
120+
SHA-1: 8B61993247469F3C208CA894D71856727B11606A
121+
channel list: R, G, B
122+
compression: "zip"
123+
eyes: 2
124+
hair: "brown"
125+
PixelAspectRatio: 1
126+
screenWindowCenter: 0, 0
127+
screenWindowWidth: 1
128+
weight: 20.5
129+
camera:lens: "AX30"
130+
camera:shutter: 0.0125
131+
oiio:ColorSpace: "lin_rec709"
132+
oiio:subimages: 1
133+
openexr:lineOrder: "increasingY"
134+
Reading greenmeta-merge-camera.exr
135+
greenmeta-merge-camera.exr : 64 x 64, 3 channel, half openexr
136+
SHA-1: 8B61993247469F3C208CA894D71856727B11606A
137+
channel list: R, G, B
138+
compression: "zip"
139+
hair: "black"
140+
PixelAspectRatio: 1
141+
screenWindowCenter: 0, 0
142+
screenWindowWidth: 1
143+
camera:lens: "AX30"
144+
camera:shutter: 0.0125
96145
oiio:ColorSpace: "lin_rec709"
97146
oiio:subimages: 1
98147
openexr:lineOrder: "increasingY"

testsuite/oiiotool-copy/run.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,17 @@
9393
+ "--pattern checker 256x256 3 --paste +150+75 -o pasted.tif")
9494

9595
# test --pastemeta
96-
command += oiiotool ("--pattern:type=half constant:color=0,1,0 64x64 3 -o green.exr")
97-
command += oiiotool ("--pattern:type=half constant:color=1,0,0 64x64 3 -attrib hair brown -attrib eyes 2 -attrib weight 20.5 -o redmeta.exr")
98-
command += oiiotool ("redmeta.exr green.exr --pastemeta -o greenmeta.exr")
96+
command += oiiotool ("--pattern:type=half constant:color=0,1,0 64x64 3 -attrib hair black -o green.exr")
97+
command += oiiotool ("--pattern:type=half constant:color=1,0,0 64x64 3 -attrib hair brown -attrib eyes 2 -attrib weight 20.5 -attrib camera:lens AX30 --attrib camera:shutter 0.0125 -o meta.exr")
98+
command += oiiotool ("meta.exr green.exr --pastemeta -o greenmeta.exr")
99+
command += oiiotool ("meta.exr green.exr --mergemeta -o greenmeta-merge.exr")
100+
command += oiiotool ("meta.exr green.exr --mergemeta:override=1 -o greenmeta-merge-override.exr")
101+
command += oiiotool ("meta.exr green.exr --mergemeta:pattern=\"^camera:\" -o greenmeta-merge-camera.exr")
99102
command += info_command ("green.exr", safematch=True)
100103
command += info_command ("greenmeta.exr", safematch=True)
104+
command += info_command ("greenmeta-merge.exr", safematch=True)
105+
command += info_command ("greenmeta-merge-override.exr", safematch=True)
106+
command += info_command ("greenmeta-merge-camera.exr", safematch=True)
101107

102108
# test mosaic
103109
# Purposely test with fewer images than the mosaic array size

0 commit comments

Comments
 (0)