Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-10486: warpExposure and warpImage do not test correctly for dest = src #352

Merged
merged 5 commits into from May 14, 2018

Conversation

r-owen
Copy link
Contributor

@r-owen r-owen commented May 10, 2018

No description provided.

Copy link
Member

@kfindeisen kfindeisen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imagesOverlap's behavior is compiler-dependent, and we can't fix that entirely, but mitigating it is pretty easy. Otherwise looks good.

/**
* Return true if the pixels for two masked images (image, variance or mask plane) overlap in memory
* (e.g. one is identical to or a subimage of the other).
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't the planes of a MaskedImage need to correspond to each other? Shouldn't either all overlap, or none of them overlap?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out to be trivial to make a MaskedImage from any Image, Mask and Variance, so long as they have the same dimensions (and perhaps the same XY0). Just call lsst.afw.image.makeMaskedImage. I added a unit test that does exactly that.

It is not very likely that real pipeline code would produce such masked images, but we cannot rule it out and it's quick and easy to test for this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I agree the code is fine. I was only asking about the wording of the documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to suggest other wording.

Note that it is, in theory, possible for planes (image, variance, mask) to actually overlap in the sense that the union is a subset of both (e.g. the way [0, 5] and [2, 7] overlap). Constructing such images takes some work (probably in ndarray or numpy) and I don't expect anyone will do it. But if they do, this test will, in theory, catch it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove the parenthetical comment

@@ -341,6 +341,9 @@ PYBIND11_PLUGIN(image) {
declareCastConstructor<std::uint64_t, float>(clsImageF);
declareCastConstructor<std::uint64_t, double>(clsImageD);

// Note: wrap both the Image and MaskedImage versions of imagesOverlap in the MaskedImage wrapper,
// as wrapping the Image version here results in it being invisible
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is because of a name collision in the afw.image package, and that they're still visible in afw.image.image and afw.image.maskedImage? If so, it might be good to say that the key is to keep all overloads in the same Python module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds plausible but I did not test it. I will add "invisible in lsst.afw.image"

declareImagesOverlap<std::uint64_t, float>(mod);
declareImagesOverlap<std::uint64_t, double>(mod);
declareImagesOverlap<std::uint64_t, std::uint16_t>(mod);
declareImagesOverlap<std::uint64_t, std::uint64_t>(mod);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's possible to write something that uses TMP to deal with these "outer product" style wrappers. I think we're using them in quite a few places at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that would be very nice.


auto arr1 = image1.getArray();
auto beg1Addr = reinterpret_cast<void const *>(arr1.front().begin());
auto end1Addr = reinterpret_cast<void const *>(arr1.back().end());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, I had to dig deep into both ndarray and the C++ language spec to figure out what this code does. I'll concede that you do need to compare void* rather than T1* and T2*, but I suggest getting rid of the reinterpret_cast. Any pointer can be implicitly converted to void*, so the use of a frequently unsafe cast just makes things confusing (especially since it's not at all obvious to the reader that arr1.front().begin() is a T1*).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I needed reinterpret_cast but you are right that I can do without it (the compiler was complaining about something else).

I agree that arr1.front().begin() is less than obvious, but it's what @TallJimbo suggested and I think it's safer than arr[height][width] because one can easily get the indices in the wrong order. I will document what it does, but I can't think of anything else to do about it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you use an implicit conversion to void* than there's no need to document that construct -- pointer to element is the only thing that both compiles and makes sense as an iterator type.

auto beg2Addr = reinterpret_cast<void const *>(arr2.front().begin());
auto end2Addr = reinterpret_cast<void const *>(arr2.back().end());

return beg1Addr < end2Addr && beg2Addr < end1Addr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to cppreference, the < operator has undefined behavior for two pointers to unrelated variables; it's not even required to be self-consistent(!). However, std::less is safe.

Let's see... beg1Addr < end1Addr and beg2Addr < end2Addr are guaranteed because they're from the same array. The order also works the way you'd intuitively expect if arr1 and arr2 are backed by the same C array, so there will be no false negatives. However, while std::less<void*> guarantees a strict total order, it does not guarantee that order is globally consistent with memory address numbers. I think that means that, strictly speaking, any overlap test based on pointer comparisons may have false positives. 😞

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a fatal flaw in any such test. Is it even safe to compare pointer identity? If so, I could at least check that one is not warping an exposure into self. If not, it may be time to give up on the test and just say "don't do that".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if @TallJimbo will have an opinion on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the documentation applies to == even though that's a common idiom. Let's hope I've misunderstood something. 😨

afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(6, 5)),
afwGeom.Box2I(afwGeom.Point2I(2, 1), afwGeom.Extent2I(6, 5)),
afwGeom.Box2I(afwGeom.Point2I(4, 3), afwGeom.Extent2I(6, 5)),
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, consider including non-overlapping boxes.

mask=afwImage.Mask(mask2, bbox2),
variance=afwImage.ImageF(variance2, bbox2))
self.assertEqual(afwImage.imagesOverlap(subMi1, subMi2), predOverlap)
self.assertEqual(afwImage.imagesOverlap(subMi2, subMi1), predOverlap)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these two assertions be inside the loop?

self.fail(
"warping into a destination Exception with no Wcs should fail")
except Exception:
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😱

@@ -149,6 +150,55 @@ def testSetGetValues(self):
self.assertEqual(self.mimage.getMask().get(1, 1), self.EDGE)
self.assertEqual(self.mimage.getMask().get(2, 2), 0x0)

def testImagesOverlap(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of code duplication with test_images.py. Can't some of it (e.g., the bounding box set and the associated subtest) be factored out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, I'm just not sure where I'd put it. I'm inclined to leave it as "good enough".

* Return true if the pixels for two images or masks overlap in memory
*
* Images are considered to overlap if they are identical or one is a
* subimage of the other.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the desired behavior if two images share pixels, but both have unshared pixels as well? I think this can happen if you have two subimages of the same image.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return true. I'll update the documentation to try to clarify this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think the simplest thing to do is get rid of the longer explanation and just leave the summary.

template <typename A, typename B, typename PtrLess = std::less<void const*>>
bool ptrsOverlap(A beg1Addr, A end1Addr, B beg2Addr, B end2Addr, PtrLess ptrLess = PtrLess()) {
return ptrLess(beg1Addr, end2Addr) && ptrLess(beg2Addr, end1Addr);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be simplified a bit:

bool ptrsOverlap(void const* const beg1Addr, void const* const end1Addr, void const* const beg2Addr, void const* const end2Addr) {
    static std::less<void const*> const ptrLess;    // std::less is safer than < for generic pointers
    return ptrLess(beg1Addr, end2Addr) && ptrLess(beg2Addr, end1Addr);
}

(depending on how rigorous you want to get with const, of course).

with overloads for images and masks (both based on ImageBase)
and masked images.
Make warpImage call imagesOverlap instead of isSameObject
(which didn't compare the right thing and didn't even try
to detect overlap).

Document that the source and destination must not overlap
(formerly we only attempted to make sure they were not the same image).

Fix the unit test that checked that overlapping images could not
be warped onto each other. It was completely broken.
from afw::math::details
Catch lsst.pex.exceptions.InvalidParameterError instead of
Exception (in some cases) or lsst.pex.exceptions.Exception
(in others)
@r-owen r-owen merged commit 473a8d6 into master May 14, 2018
@ktlim ktlim deleted the tickets/DM-10486 branch August 25, 2018 06:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants