Skip to content

Commit 723b308

Browse files
lgritzscott-wilson
authored andcommitted
feat: oiiotool --mergemeta pt 2: fold into --pastemeta (AcademySoftwareFoundation#4674)
After merging AcademySoftwareFoundation#4672, we noticed some things that could be improved, so this is a further revision (and partial retraction). There was already an oiiotool flag, `--metamerge`, which in retrospect make it seem unnecessarily confusing to add --mergemeta. So this PR removes this extra new command, instead simply piggy-backing on the existing --pastemeta by adding to it optional modifiers: - `:merge=` controls the merge strategy, 0 (default) means discard the existing metadata entirely and use only the pasted, matching the original --pastemeta behavior; 1 means merge nondestructively (no existing metadata will be altered or removed); 2 means destructive merge, where pasted metadata will overwrite existing metadata having the same name, but conflict-free existing metadata will remain. - `:pattern=` regex selects only some metadata. Additionally, we noticed an interesting corner case that surprised users when testing the new metadata merging feature from the last PR: What happens if the metadata source image has fewer subimages than the pixel source images? The behavior generally for oiiotool is that when binary operations (those taking two images as input) are on images with differing numbers of subimages, the output gets the number of subimges as the FIRST input (assuming -a instructed us to consider subimages at all). But that's not what we want here! If the metadata source has one subimage and the pixel source has many, clearly the intuitive result is for that one metadata set to be pasted into all of the subimages from the pixel-supplying image. So we fix that so that for --pastemeta, it's the second input image (not the first) that takes the lead in determining the number of subimages we will process. This in turn took a bit of extra infrastructure fixing in the OiiotoolOp class that controls this logic. Signed-off-by: Larry Gritz <[email protected]> Signed-off-by: Scott Wilson <[email protected]>
1 parent 79e5c12 commit 723b308

File tree

7 files changed

+166
-65
lines changed

7 files changed

+166
-65
lines changed

src/doc/oiiotool.rst

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2996,47 +2996,40 @@ current top image.
29962996

29972997
.. option:: --pastemeta <location>
29982998

2999-
Takes two images -- the first will be a source of metadata only, and the
3000-
second the source of pixels -- and produces a new copy of the second
3001-
image with the metadata from the first image.
3002-
3003-
Example::
3004-
3005-
# Add all the metadata from meta.exr to pixels.exr and write the
3006-
# combined image to out.exr.
3007-
oiiotool meta.exr pixels.exr --pastemeta -o out.exr
3008-
3009-
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).
2999+
Takes two images -- the first image will be a source of metadata only, and the
3000+
second "destination" image will supply the pixels -- and produces a combined
3001+
image.
30173002

30183003
Optional appended modifiers include:
30193004

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.
3005+
- `merge=` *int* : Determines the metadata merging strategy. If `0` (the
3006+
default), performs a full replacement -- the metadata from the
3007+
destination (second) image will be discarded entirely and re-populated
3008+
with the metadata from the metadata source (first) image. If `1`,
3009+
metadata from the source image will be non-destructively added to that
3010+
of the existing metadata of the destination image, item by item, if it
3011+
doesn't already contain metadata with the same name. If `2`, metadata
3012+
from the source image will be destructively added to that of the
3013+
existing metadata of the destination image, item by item, and will
3014+
replace any metadata already present having the same name. (This
3015+
modifier was added in OIIO 3.0.4.)
30263016

30273017
- `pattern=` *regex* : If supplied, only copies metadata whose name
30283018
matches has a substring matching the regular expression. The special
30293019
character `^` indicates the beginning of the string and `$` indicates
3030-
the end of the string.
3020+
the end of the string. (This modifier was added in OIIO 3.0.4.)
30313021

3032-
Example::
3022+
Examples::
3023+
3024+
# Add all the metadata from meta.exr to pixels.exr, discarding any
3025+
# metadata it previously had, and write the combined image to out.exr.
3026+
oiiotool meta.exr pixels.exr --pastemeta -o out.exr
30333027

30343028
# 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
3029+
# to pixels.exr, replacing any identically named items but leaving
3030+
# others as they were, and write the combined image to out.exr.
3031+
oiiotool meta.exr pixels.exr --pastemeta:merge=2:pattern="^camera:override=1" -o out.exr
30383032

3039-
The `--mergemeta` command was added in OIIO 3.0.4.
30403033

30413034
.. option:: --mosaic <size>
30423035

src/include/OpenImageIO/paramlist.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,15 @@ make_pv(string_view name, T* val)
333333
/// methods.
334334
class OIIO_UTIL_API ParamValueList : public std::vector<ParamValue> {
335335
public:
336+
// Default constructor
336337
ParamValueList() {}
337338

339+
// Construct from a span of ParamValue items
340+
ParamValueList(cspan<ParamValue> params)
341+
{
342+
assign(params.begin(), params.end());
343+
}
344+
338345
/// Add space for one more ParamValue to the list, and return a
339346
/// reference to its slot.
340347
reference grow()

src/oiiotool/oiiotool.cpp

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4757,23 +4757,24 @@ action_paste(Oiiotool& ot, cspan<const char*> argv)
47574757

47584758

47594759
// --pastemeta
4760-
OIIOTOOL_OP(pastemeta, 2, [&](OiiotoolOp& op, span<ImageBuf*> img) {
4761-
*img[0] = *img[2];
4762-
img[0]->copy_metadata(*img[1]);
4763-
return true;
4764-
});
4765-
4766-
4767-
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>();
4760+
OIIOTOOL_OP(
4761+
pastemeta, 2,
4762+
[&](OiiotoolOp& op, span<ImageBuf*> img) {
4763+
std::string pattern = op.options("pattern");
4764+
int merge = op.options("merge").get<int>(0);
4765+
4766+
*img[0] = *img[2];
4767+
if (merge == 0) {
4768+
// merge strategy 0 completely discards the existing metadata
4769+
img[0]->specmod().extra_attribs.clear();
4770+
}
47724771

4773-
*img[0] = *img[2];
4774-
img[0]->merge_metadata(*img[1], override, pattern);
4775-
return true;
4776-
});
4772+
img[0]->merge_metadata(*img[1], merge > 1, pattern);
4773+
return true;
4774+
},
4775+
// Special policy to ensure we consider all subimages of second image
4776+
// (pixel inputs), not the default of considering only the first image.
4777+
{ { "subimage_policy", "last" } });
47774778

47784779

47794780

@@ -6799,11 +6800,8 @@ Oiiotool::getargs(int argc, char* argv[])
67996800
.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)")
68006801
.OTACTION(action_paste);
68016802
ap.arg("--pastemeta")
6802-
.help("Copy the metadata from the first image to the second image and keep the combined result")
6803+
.help("Paste the metadata from the first image into the second image (options: merge=%d, pattern=REGEX)")
68036804
.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);
68076805
ap.arg("--mosaic %s:WxH")
68086806
.help("Assemble images into a mosaic (arg: WxH; options: pad=0, fit=WxH)")
68096807
.OTACTION(action_mosaic);

src/oiiotool/oiiotool.h

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -781,12 +781,14 @@ class OiiotoolOp {
781781
// stack.
782782
OiiotoolOp(Oiiotool& ot, string_view opname, cspan<const char*> argv,
783783
int ninputs, setup_func_t setup_func = nullptr,
784-
impl_func_t impl_func = nullptr)
784+
impl_func_t impl_func = nullptr,
785+
ParamValueSpan control_options = {})
785786
: ot(ot)
786787
, m_nargs((int)argv.size())
787788
, m_nimages(ninputs + 1)
788789
, m_setup_func(setup_func)
789790
, m_impl_func(impl_func)
791+
, m_control_options(control_options)
790792
{
791793
if (Strutil::starts_with(opname, "--"))
792794
opname.remove_prefix(1); // canonicalize to one dash
@@ -803,6 +805,12 @@ class OiiotoolOp {
803805
: OiiotoolOp(ot, opname, argv, ninputs, {}, impl_func)
804806
{
805807
}
808+
OiiotoolOp(Oiiotool& ot, string_view opname, cspan<const char*> argv,
809+
int ninputs, impl_func_t impl_func,
810+
ParamValueSpan control_options)
811+
: OiiotoolOp(ot, opname, argv, ninputs, {}, impl_func, control_options)
812+
{
813+
}
806814
virtual ~OiiotoolOp() {}
807815

808816
// The operator(), function-call mode, does most of the work. Although
@@ -891,10 +899,12 @@ class OiiotoolOp {
891899
m_img.resize(nimages());
892900
for (int m = 0, nmip = ir(0)->miplevels(); m < nmip; ++m) {
893901
m_current_miplevel = m;
894-
for (int i = 0; i < nimages(); ++i)
895-
m_img[i] = &((*ir(i))(std::min(s, ir(i)->subimages() - 1),
896-
std::min(m, ir(i)->miplevels(s))));
897-
902+
for (int i = 0; i < nimages(); ++i) {
903+
// If the subimage doesn't exist, just use the last
904+
int ss = std::min(s, ir(i)->subimages() - 1);
905+
m_img[i] = &(
906+
(*ir(i))(ss, std::min(m, ir(i)->miplevels(ss))));
907+
}
898908
if (subimage_is_active(s)) {
899909
// Call the impl kernel for this subimage
900910
bool ok = impl(m_img);
@@ -1010,7 +1020,35 @@ class OiiotoolOp {
10101020
}
10111021
}
10121022
all_subimages |= m_options.get_int("allsubimages", ot.allsubimages);
1013-
return all_subimages ? (nimages() > 1 ? ir(1)->subimages() : 1) : 1;
1023+
1024+
// How many subimages are we going to operate on? There are a few
1025+
// strategies for handling the decision when the input images differ
1026+
// in the number of subimages. The default is to operate on subimage
1027+
// list from the first image input.
1028+
int n = 1;
1029+
if (all_subimages) {
1030+
OIIO_DASSERT(nimages() >= 1);
1031+
std::string subimage_policy = m_control_options["subimage_policy"];
1032+
if (subimage_policy == "max") {
1033+
// operate on the maximum number of subimages of the inputs
1034+
for (int i = 1; i < nimages(); ++i)
1035+
n = std::max(n, ir(i)->subimages());
1036+
} else if (subimage_policy == "min") {
1037+
// operate on the minimum number of subimages of the inputs
1038+
if (nimages() > 1)
1039+
n = ir(1)->subimages();
1040+
for (int i = 2; i < nimages(); ++i)
1041+
n = std::min(n, ir(i)->subimages());
1042+
} else if (subimage_policy == "last") {
1043+
// operate on the subimages of the last input
1044+
n = ir(nimages() - 1)->subimages();
1045+
} else /* if (subimage_policy == "first")*/ { // default
1046+
// default: operate on the subimages of the first input
1047+
if (nimages() > 1)
1048+
n = ir(1)->subimages();
1049+
}
1050+
}
1051+
return n;
10141052
}
10151053

10161054
// Is the given subimage in the active set to be operated on by this op?
@@ -1112,6 +1150,7 @@ class OiiotoolOp {
11121150
new_output_imagerec_func_t m_new_output_imagerec_func;
11131151
int m_current_subimage; // for impl(), which subimage are we on?
11141152
int m_current_miplevel; // for impl(), which miplevel are we on?
1153+
ParamValueList m_control_options;
11151154
};
11161155

11171156

File renamed without changes.

testsuite/oiiotool-copy/ref/out.txt

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ green.exr : 64 x 64, 3 channel, half openexr
8383
oiio:ColorSpace: "lin_rec709"
8484
oiio:subimages: 1
8585
openexr:lineOrder: "increasingY"
86-
Reading greenmeta.exr
87-
greenmeta.exr : 64 x 64, 3 channel, half openexr
86+
Reading greenmeta-replace.exr
87+
greenmeta-replace.exr : 64 x 64, 3 channel, half openexr
8888
SHA-1: 8B61993247469F3C208CA894D71856727B11606A
8989
channel list: R, G, B
9090
compression: "zip"
@@ -145,6 +145,63 @@ greenmeta-merge-camera.exr : 64 x 64, 3 channel, half openexr
145145
oiio:ColorSpace: "lin_rec709"
146146
oiio:subimages: 1
147147
openexr:lineOrder: "increasingY"
148+
Reading greenmeta-merge-2.exr
149+
greenmeta-merge-2.exr : 64 x 64, 3 channel, half openexr
150+
3 subimages: 64x64 [h,h,h], 64x64 [h,h,h], 64x64 [h,h,h]
151+
subimage 0: 64 x 64, 3 channel, half openexr
152+
SHA-1: 8B61993247469F3C208CA894D71856727B11606A
153+
channel list: R, G, B
154+
compression: "zip"
155+
eyes: 2
156+
hair: "black"
157+
name: "subimage00"
158+
PixelAspectRatio: 1
159+
screenWindowCenter: 0, 0
160+
screenWindowWidth: 1
161+
weight: 20.5
162+
camera:lens: "AX30"
163+
camera:shutter: 0.0125
164+
oiio:ColorSpace: "lin_rec709"
165+
oiio:subimagename: "subimage00"
166+
oiio:subimages: 3
167+
openexr:chunkCount: 4
168+
openexr:lineOrder: "increasingY"
169+
subimage 1: 64 x 64, 3 channel, half openexr
170+
SHA-1: 8B61993247469F3C208CA894D71856727B11606A
171+
channel list: R, G, B
172+
compression: "zip"
173+
eyes: 2
174+
hair: "black"
175+
name: "subimage01"
176+
PixelAspectRatio: 1
177+
screenWindowCenter: 0, 0
178+
screenWindowWidth: 1
179+
weight: 20.5
180+
camera:lens: "AX30"
181+
camera:shutter: 0.0125
182+
oiio:ColorSpace: "lin_rec709"
183+
oiio:subimagename: "subimage01"
184+
oiio:subimages: 3
185+
openexr:chunkCount: 4
186+
openexr:lineOrder: "increasingY"
187+
subimage 2: 64 x 64, 3 channel, half openexr
188+
SHA-1: 8B61993247469F3C208CA894D71856727B11606A
189+
channel list: R, G, B
190+
compression: "zip"
191+
eyes: 2
192+
hair: "black"
193+
name: "subimage02"
194+
PixelAspectRatio: 1
195+
screenWindowCenter: 0, 0
196+
screenWindowWidth: 1
197+
weight: 20.5
198+
camera:lens: "AX30"
199+
camera:shutter: 0.0125
200+
oiio:ColorSpace: "lin_rec709"
201+
oiio:subimagename: "subimage02"
202+
oiio:subimages: 3
203+
openexr:chunkCount: 4
204+
openexr:lineOrder: "increasingY"
148205
Reading nometamerge.exr
149206
nometamerge.exr : 64 x 64, 6 channel, float openexr
150207
SHA-1: 9F13A523321C66208E90D45F87FA0CD9B370E111
@@ -202,7 +259,7 @@ Comparing "mosaic.tif" and "ref/mosaic.tif"
202259
PASS
203260
Comparing "mosaicfit.tif" and "ref/mosaicfit.tif"
204261
PASS
205-
Comparing "greenmeta.exr" and "ref/greenmeta.exr"
262+
Comparing "greenmeta-replace.exr" and "ref/greenmeta-replace.exr"
206263
PASS
207264
Comparing "chappend-3images.exr" and "ref/chappend-3images.exr"
208265
PASS

testsuite/oiiotool-copy/run.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,22 @@
9595
# test --pastemeta
9696
command += oiiotool ("--pattern:type=half constant:color=0,1,0 64x64 3 -attrib hair black -o green.exr")
9797
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")
10298
command += info_command ("green.exr", safematch=True)
103-
command += info_command ("greenmeta.exr", safematch=True)
99+
# straight pastemeta -- full replacement
100+
command += oiiotool ("meta.exr green.exr --pastemeta -o greenmeta-replace.exr")
101+
command += info_command ("greenmeta-replace.exr", safematch=True)
102+
# paste with nondestructive merge
103+
command += oiiotool ("meta.exr green.exr --pastemeta:merge=1 -o greenmeta-merge.exr")
104104
command += info_command ("greenmeta-merge.exr", safematch=True)
105+
# pastemeta with destructive merge
106+
command += oiiotool ("meta.exr green.exr --pastemeta:merge=2 -o greenmeta-merge-override.exr")
105107
command += info_command ("greenmeta-merge-override.exr", safematch=True)
108+
# pastemeta with pattern-selected subset
109+
command += oiiotool ("meta.exr green.exr --pastemeta:merge=1:pattern=\"^camera:\" -o greenmeta-merge-camera.exr")
106110
command += info_command ("greenmeta-merge-camera.exr", safematch=True)
111+
# pastemeta odd case with more pixel subimages than meta subimages
112+
command += oiiotool ("-a meta.exr green.exr --dup --dup --siappend --siappend --pastemeta:merge=1 -o greenmeta-merge-2.exr")
113+
command += info_command ("greenmeta-merge-2.exr", safematch=True)
107114

108115
# test mosaic
109116
# Purposely test with fewer images than the mosaic array size
@@ -146,7 +153,7 @@
146153
"chappend-rgbaz.exr", "chname.exr",
147154
"crop.tif", "cut.tif", "pasted.tif",
148155
"mosaic.tif", "mosaicfit.tif",
149-
"greenmeta.exr",
156+
"greenmeta-replace.exr",
150157
"chappend-3images.exr",
151158
"out.txt"
152159
]

0 commit comments

Comments
 (0)