OpenImageIO
OpenImageIO copied to clipboard
[BUG] Unable to sample UDIMs from pre-populated in-memory ImageCache
Description
Note - this is the result of a discussion on the Academy Software Foundation slack.
I wanted to sample a number of in-memory buffers representing <UDIM> tiles using the texturesystem. I thought (based on advice) that I could populate an ImageCache with "fake" images named in the fashion of UDIM tiles such as "test.1001.null", "test.1002.null", and sample them using the name containing the UDIM pattern (such as test.<UDIM>.null). This does work for single images but not for UDIM tiles.
OpenImageIO version and dependencies
OpenImageIO 2.5.10.0
To Reproduce
Steps to reproduce the behavior:
- Use the test C++ code snippet below in a setup that has OIIO available (sorry I don't have exact steps here).
- Call
OIIOTest::testBufferUDIMSampling(); - The output will be:
Checking test.1001.null at st: {0.5,0.5}
No inventory test.1001.null u0 v0
Checking test.1002.null at st: {0.5,0.5}
No inventory test.1002.null u0 v0
Checking test.<UDIM>.null at st: {0.5,0.5}
No inventory test.<UDIM>.null u0 v0
test.<UDIM>.null did not sample correctly.
Incorrect result: {0,0,0,0}
- This indicates that after populating the cache we were able to sample the image
test.1001.nullandtest.1002.null, and get the expected result, but were unable to sampletest.<UDIM>.null. The inventory of UDIM tiles always gives 0 tiles, in all three cases, which may be a clue.
The code using the nullImageInputCreator follows the example found here: https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/64f829febd352686538beaba10e4ca716a9403a1/src/libOpenImageIO/imagecache_test.cpp#L161-L186
Test code snippet:
#include <OpenImageIO/imagecache.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/texture.h>
#include <iostream>
#include <vector>
namespace OIIOTest {
using namespace OIIO;
// Simple wrapper to return a raw "null" ImageInput*.
// Taken from OIIO tests
ImageInput* nullImageInputCreator() {
// Original comment:
// Note: we can't create it directly, but we can ask for a managed
// pointer and then release the raw pointer from it.
return ImageInput::create("0.null").release();
}
bool checkSample(ImageCache* imageCache, ustring filename, float s, float t, float* expected) {
std::cerr << "Checking " << filename << " at st: {" << s << "," << t << "}\n";
float r[4] = {-1.f, -1.f, -1.f, -1.f};
TextureOpt opt;
opt.interpmode = TextureOpt::InterpMode::InterpClosest;
bool ok;
{
auto textureSys = TextureSystem::create(false /*Not shared*/, imageCache);
{
std::vector<ustring> filenames;
int ut, vt;
textureSys->inventory_udim(filename, filenames, ut, vt);
if (filenames.empty())
std::cerr << " No inventory " << filename << " u" << ut << " v" << vt << "\n";
}
ok = textureSys->texture(filename, opt, //
s, t, // float s, t
0.f, 0.f, 0.f, 0.f, // float dsdx, dtdx, dsdy, dtdy,
4, // int nchannels,
r); // float *result
TextureSystem::destroy(textureSys);
}
if (!imageCache->geterror(false).empty() || !ok) {
std::cerr << " " << filename << " gave OIIO error. \n";
std::cerr << " " << imageCache->geterror(true);
return false;
}
if (r[0] != expected[0] || r[1] != expected[1] || r[2] != expected[2] || r[3] != expected[3]) {
std::cerr << " " << filename << " did not sample correctly. \n";
std::cerr << " Incorrect result: {" << r[0] << "," << r[1] << "," << r[2] << "," << r[3]
<< "}\n";
return false;
}
return true;
}
bool testBufferUDIMSampling() {
/*
We create two UDIM tiles, 1001 and 1002.
*/
std::vector<float> pixels1001 = {
1.f, 0.f, 0.f, 1.f, // red
1.f, 0.f, 0.f, 1.f, // red
1.f, 0.f, 0.f, 1.f, // red
1.f, 0.f, 0.f, 1.f, // red
};
std::vector<float> pixels1002 = {
0.f, 1.f, 0.f, 1.f, // green
0.f, 1.f, 0.f, 1.f, // green
0.f, 1.f, 0.f, 1.f, // green
0.f, 1.f, 0.f, 1.f, // green
};
const ustring nameUDIM("test.<UDIM>.null");
const ustring name1001("test.1001.null");
const ustring name1002("test.1002.null");
auto imageCache = ImageCache::create(false); // unshared
ImageSpec spec = ImageSpec(2, 2, 4, TypeDesc::FLOAT);
spec.attribute("null:force", 1); // necessary because no .null extension
imageCache->add_file(name1001, nullImageInputCreator, &spec);
imageCache->add_tile(name1001,
0, // subimage
0, // miplevel
0, 0, 0, // origin x, y, z
0, 4, // channel start, end
TypeDesc::FLOAT, // image type
pixels1001.data(), // the buffer
AutoStride, AutoStride, AutoStride, // strides
true // copy
);
imageCache->add_file(name1002, nullImageInputCreator, &spec);
imageCache->add_tile(name1002,
0, // subimage
0, // miplevel
0, 0, 0, // origin x, y, z
0, 4, // channel start, end
TypeDesc::FLOAT, // image type
pixels1002.data(), // the buffer
AutoStride, AutoStride, AutoStride, // strides
true // copy
);
if (!imageCache->geterror(false).empty()) {
std::cerr << imageCache->geterror(true);
return false;
}
float s = 0.5f, t = 0.5f;
float expected1001[4] = {1.f, 0.f, 0.f, 1.f};
float expected1002[4] = {0.f, 1.f, 0.f, 1.f};
// Sampling "test.1001.null" works
if (!checkSample(imageCache, name1001, s, t, expected1001)) return false;
// Sampling "test.1002.null" works as well (no udim tile behavior, just shows this loaded.)
if (!checkSample(imageCache, name1002, s, t, expected1002)) return false;
// Sampling it as "test.<UDIM>.null" fails
if (!checkSample(imageCache, nameUDIM, s, t, expected1001)) return false;
// (never reached)
if (!checkSample(imageCache, nameUDIM, s + 1.f, t, expected1002)) return false;
std::cerr << "Success \n";
return true;
}
} // namespace OIIOTest
Debugged through this today and, yeah, the current UDIM code here only traverses the file-system, not the items loaded into the ImageCache, when building its inventory. This lack of inventory for test.<UDIM>.null means sampling won't work as you expect it to.
Let me see if I can get others to chime in here about possible ways forward.