skip to Main Content

Currently, I try to get Open Image Denoise to work with Bazel.
Therefore, I implemented rules_oidn.

To try it out, you can do a

git clone https://github.com/Vertexwahn/rules_oidn.git
cd rules_oidn
cd tests

Run example with Ubuntu 22.04:

bazel run --config=gcc11 //:example

Run example with Visual Studio 2022:

bazel run --config=vs2022 //:example

The example takes a noisy image and denoises it.

...
int main() {
    cout << "Simple denoising example" << endl;

    Image3f color = load_image_openexr("data/cornel_box.naive_diffuse.box_filter.spp128.embree.exr");
    ...
    Image3f out{color.width(), color.height()};

    ...

    float* colorPtr = color.data();
    ...
    float* outputPtr = out.data();
    int width = out.width();
    int height = out.height();

    oidn::DeviceRef device = oidn::newDevice();
    device.set("verbose", 1);
    device.commit();

    // Create a filter for denoising a beauty (color) image using optional auxiliary images too
    oidn::FilterRef filter = device.newFilter("RT"); // generic ray tracing filter
    filter.setImage("color",  colorPtr,  oidn::Format::Float3, width, height); // beauty
    ...
    filter.setImage("output", outputPtr, oidn::Format::Float3, width, height); // denoised beauty
    filter.set("hdr", true); // beauty image is HDR
    filter.commit();

    // Filter the image
    filter.execute();

    // Check for errors
    const char* errorMessage;
    if (device.getError(errorMessage) != oidn::Error::None) {
        std::cout << "Error: " << errorMessage << std::endl;
    }

    store_open_exr("denoised.exr", out);

    return 0;
}

Unfortunately, the denoised image contains black stripes:

enter image description here

I tested the same input with https://github.com/DeclanRussell/IntelOIDenoiser and got the expected result (without black stripes). Therefore, the problem must be within my bazalization of OIDN or my supporting around it.

If I choose a constant color image, e.g.

// for debug reasons the color image can be initialized with a const color
if(true) { 
    for (int x = 0; x < color.width(); ++x) {
        for (int y = 0; y < color.height(); ++y) {
            color.set_pixel(x,y,.5f, .5f, .5f);
        }
    }
}

I also get black stripes.

Currently, I am missing a good strategy to find the issue.
Any hints or solutions to fix the issue are welcome.

I also created a branch of oidn that contains a "direct"
bazelization of oidn which is almost similar to rules_oidn: https://github.com/Vertexwahn/oidn/tree/add-bazel-support. In this branch I bazelized oidnTest which contains a few test cases which all pass with success. I bazelized also oidnDenoiser. You can run it via:

# the_cornell_box.pfm is in data folder in the oidn repo
bazel run --config=gcc11 //:oidnDenoise -- --hdr /home/vertexwahn/Desktop/the_cornell_box.pfm -o /home/vertexwahn/Desktop/denoised.pfm

The generated file denoised.pfm shows the same black stripes. It seems that the black stripes are always 5 pixel wide followed by 3 correct looking color stripes.

Not sure if there are any problems with handing over memory to ISPC, or doing the Filter Operation, etc. Since I tested different image formats (OpenEXR, PFM), I assume that the error is not within my way of storing images.

I started to debug and compare the working CMake/Windows build with the non-working Bazel/Ubuntu build – but until now I did not find obvious differences:

Compare Bazel build with CMake build

The Image3f class looks like this:

class Image3f {
public:
    Image3f(const int width, const int height) : width_(width), height_(height) {
        data_ = new float[width_ * height_ * 3];

        for (int x = 0; x < width_; ++x) {
            for (int y = 0; y < height_; ++y) {
                auto r = 0.f;
                auto g = 0.f;
                auto b = 0.f;

                data_[(width_ * y + x) * 3] = r;
                data_[(width_ * y + x) * 3 + 1] = g;
                data_[(width_ * y + x) * 3 + 2] = b;
            }
        }
    }

    void set_pixel(int x, int y, float red, float green, float blue) {
        assert(x >= 0);
        assert(y >= 0);
        assert(x < width_);
        assert(y < height_);

        data_[(width_ * y + x) * 3] = red;
        data_[(width_ * y + x) * 3 + 1] = green;
        data_[(width_ * y + x) * 3 + 2] = blue;
    }

    float *data() const {
        return data_;
    }

    float *data() {
        return data_;
    }

    int width() const {
        return width_;
    }

    int height() const {
        return height_;
    }

private:
    int width_ = 0;
    int height_ = 0;
    float *data_;
};

More details on the Bazelization

Open Image Denoise v1.4.3 has some dependencies. It depends on oneTBB, oneDNN, makes use of ISPC and the Intel Implicit SPMD Program Compiler, and uses Python to generate C++ Code from trained weights. All of this needs to be supported via Bazel to get a Open Image Denoise Bazel build working.

oneTBB was already bazelized by me in Mid of the year 2021. I have a CI build job that pulls the latest oneTBB master and does a Bazel test build with it. At the time of this writing, this still works. The test build uses oneTBB in combination Embree. I have also a standalone demo that shows how to use oneTBB with Bazel alone.

To support ISPC with Bazel I created rules_ispc. To use ISPC with C++ you need to write ISPC programs that are then translated by the ISPC compiler. The ISPC-compiled program can be invoked via C++. All the scaffolding to download the correct ISPC compiler version, translation of ISPC programs, and linking them to C++ executables is part of rules_ispc.

An overview of the whole dependencies can be seen here:

enter image description here

Generated via:

bazel query --noimplicit_deps 'deps(//:example) - @com_openexr//...:* - @Imath//...:* - @net_zlib_zlib//...:*' --output graph > simplified_graph.in
dot -Tpng < simplified_graph.in > simple_graph.png                                                                                                 

A failing test case for the Bazel Build (but working on CMake side)

If I add

// -----------------------------------------------------------------------------

TEST_CASE("denoise constant image", "[sanitization]")
{
    DeviceRef device = newDevice();
    REQUIRE(bool(device));
    device.commit();
    REQUIRE(device.getError() == Error::None);

    const int W = 1024;
    const int H = 1024;

    FilterRef filter = device.newFilter("RT");
    REQUIRE(bool(filter));

    std::shared_ptr<ImageBuffer> input = makeConstImage(device, W, H, 3, 0.5f);
    ImageBuffer output(device, W, H, 3);
    setFilterImage(filter, "color", *input);
    setFilterImage(filter, "output", output);
    filter.set("hdr", true);

    filter.commit();
    REQUIRE(device.getError() == Error::None);

    filter.execute();
    REQUIRE(device.getError() == Error::None);

    REQUIRE_THAT(((float*)output.bufferPtr)[0], Catch::Matchers::WithinAbs(0.4787f, 1e-4));

    REQUIRE(((float*)output.bufferPtr)[9] != 0.f); // This is violated on Bazel build but not CMake build

    for (int i = 0; i < output.size(); ++i) {
        REQUIRE(((float*)output.bufferPtr)[i] != 0.f); // This is violated on Bazel build but not CMake build
    }
}

to oidnTest all tests succeed in a CMake-based build environment. At the same time, the same code fails with Bazel.

Note: I am also willing to accept answers that narrow the problem to a smaller code "footprint" as in the shown unit test (which is more like high-level acceptance test)

2

Answers


  1. Chosen as BEST ANSWER

    The issue was that the ISCP compiler also need the define OIDN_DNNL. When looking into the tensor.isph file one can see this:

    inline size_t getIndex(uniform TensorAccessor& tz, uniform int h, int w, uniform int c)
    {
    #if defined(OIDN_DNNL)
      // ChwKc layout (blocked)
      return ((size_t)tz.H * (c/K) + h) * ((size_t)tz.W*K) + (size_t)w*K + (c%K);
    #else
      // chw layout
      return ((size_t)tz.H * c + h) * (size_t)tz.W + w;
    #endif
    }
    

    Since I am using oneDNN and but did not define for the ISCP files the defines I got black strips. This issue is fixed now and everything works as expected.


  2. A possible reason is that you are not setting the stride parameters correctly when calling filter.setImage(). You need to specify the byte size of each row of pixels for each image buffer.

    For example, if your image has 3 channels and 4 bytes per channel, then your stride should be width * 3 * 4.

    You can try adding these lines before filter.commit():

    size_t colorStride = width * 3 * sizeof(float);
    size_t outputStride = width * 3 * sizeof(float);
    filter.setImage("color", colorPtr, oidn::Format::Float3, width, height, colorStride);
    filter.setImage("output", outputPtr, oidn::Format::Float3, width, height, outputStride);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search