OpenImageIO icon indicating copy to clipboard operation
OpenImageIO copied to clipboard

[BUG] Unable to sample UDIMs from pre-populated in-memory ImageCache

Open jonahfriedman opened this issue 1 year ago • 1 comments
trafficstars

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:

  1. Use the test C++ code snippet below in a setup that has OIIO available (sorry I don't have exact steps here).
  2. Call OIIOTest::testBufferUDIMSampling();
  3. 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}
  1. This indicates that after populating the cache we were able to sample the image test.1001.null and test.1002.null, and get the expected result, but were unable to sample test.<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

jonahfriedman avatar Aug 14 '24 16:08 jonahfriedman

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.

jessey-git avatar Sep 27 '24 22:09 jessey-git