DIPlib / diplib

Quantitative Image Analysis in C++, MATLAB and Python
https://diplib.org
Apache License 2.0
228 stars 50 forks source link

BinaryPropagation_Fast produces different results to BinaryPropagation_Iterative #135

Closed milianw closed 1 year ago

milianw commented 1 year ago

Component DIPlib d46615a00c0ab08d0abfa2083f4469070990b437

Describe the bug In 348ffa66ebd31cca31ede14eb3dc8909b91de768 a new BinaryPropagation_Fast was introduced which behaves differently from the old BinaryPropagation_Iterative code when a non-empty c_inSeed is given.

In the old code, out won't include c_inSeed. The new code does seem to include it. This behavior change breaks a lot of our assumptions and I doubt this is a desired/intended behavior change?

To Reproduce

I'm attaching a couple of tiff files, with which you can easily reproduce it with a function such as:

// see dip::EdgeObjectsRemove, this just uses a non-empty seed mask to allow removal of NROI edges too
// furthermore, this also handles the 1px border we get from using the fast watershed algorithm
void edgeObjectsRemove(const dip::Image &c_in, const dip::Image &mask, dip::Image &out, dip::uint connectivity = 1)
{
    DIP_START_STACK_TRACE
    dip::Image in = c_in; // NOLINT
    if (out.Aliases(in))
        out.Strip(); // prevent `in` data being overwritten if `out` points to the same data.

    // prepare the seed with the inverted mask to detect NROI edges
    dip::Image seed;
    if (!mask.IsForged())
    {
        seed = c_in.Similar();
        seed.Fill(0);
    }
    else
    {
        seed = dip::Invert(mask);
    }

    // because we use the fast watershed algorithm, there is a 1px border around the image
    // this should be considered as part of the seed too
    dip::SetBorder(seed, {1});

    // now actually find and remove objects that touch the edges
    dip::BinaryPropagation(seed, in, out, static_cast<dip::sint>(connectivity), 0, dip::S::OBJECT);

/// BEHAVIOR CHANGE START
    // in newer diplib we apparently also need to remove the seed as it gets merged int `in`
    // in ^= seed;
/// BEHAVIOR CHANGE END

    // The out-image now contains the edge objects
    // Remove them by toggling these bits in the in-image and writing the result in out
    out ^= in;

}

The three attached files show the behavior difference - if you deactivate the new fast code in diplib, or if you comment out the in ^= seed line in the function above, the old behavior is re-instantiated. in that case, the pre/post boundary images will be the same (since there's nothing touching the edge which should get removed in this case).

test.zip

System information:

crisluengo commented 1 year ago

Thank you Milian for the very detailed report. You made it really easy to fix this issue!

milianw commented 1 year ago

And thanks to you for the quick fix :)