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

Add unit tests for ImageMapReduce task #45

Closed
wants to merge 4 commits into from

Conversation

djreiss
Copy link
Contributor

@djreiss djreiss commented Feb 23, 2017

No description provided.

@djreiss djreiss mentioned this pull request Feb 23, 2017
Copy link
Contributor

@parejkoj parejkoj left a comment

Choose a reason for hiding this comment

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

Several changes requested in comments. I haven't looked at the actual functional code that this is testing, but this test structure makes sense to me. I do feel like there should be at least one more test that does something a bit more complicated: for example, although you set the PSF of the exposure, it doesn't look like it actually impacts the results of any of the tests.

@@ -0,0 +1,243 @@
from __future__ import absolute_import, division, print_function
from builtins import range
from past.builtins import basestring
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need the basestring import here? I don't see it used.



class TestImageMapperSubtaskConfig(ImageMapperSubtaskConfig):
"""!
Copy link
Contributor

Choose a reason for hiding this comment

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

Please switch to pydoc style for all python docstrings here, and below: https://developer.lsst.io/docs/py_docs.html

_DefaultName = "ip_diffim_TestImageMapperSubtask"

def __init__(self, *args, **kwargs):
"""! Create the image gridding subTask
Copy link
Contributor

Choose a reason for hiding this comment

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

The docstring for __init__ goes in the class-level docstring.

@param *args arguments to be passed to lsst.pipe.base.task.Task.__init__
@param **kwargs keyword arguments to be passed to lsst.pipe.base.task.Task.__init__
"""
ImageMapperSubtask.__init__(self, *args, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you even need this __init__, if you're not doing anything besides calling super.

self.exposure = afwImage.ExposureF(128, 128)
self.exposure.setPsf(measAlg.DoubleGaussianPsf(11, 11, 2.0, 3.7))
mi = self.exposure.getMaskedImage()
mi[:, :] = 0.
Copy link
Contributor

Choose a reason for hiding this comment

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

Huh. I just looked, and didn't see an Exposure constructor that would assign a value to the entire maskedImage. Could you ask slack#dm if such a constructor exists, and if not file a ticket against afw?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For exposures, that would be tricky (image, variance, masks, psfs, etc.) To me, that would only make sense if it explicitly sets everything to zero. I am not sure if the default empty constructor guarantees that (probably not, for speed), so I just do it explicitly here.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's good we brought this up: https://jira.lsstcorp.org/browse/DM-9593


mn = newArr.mean()
self.assertClose(mi.getImage().getArray().mean(), mn - expectedVal)
self.assertClose(mn, expectedVal)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comments as above about testing the full array.


task = ImageMapReduceTask(config)
boxes = task._generateGrid(self.exposure)
if len(boxes[0]) > 1000: # bypass to prevent slow testing
Copy link
Contributor

Choose a reason for hiding this comment

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

How much slower is it? What bit of code makes it slow? Seems like a potentially interesting possible failure mode (having many boxes).

Copy link
Contributor Author

@djreiss djreiss Feb 24, 2017

Choose a reason for hiding this comment

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

It is not a failure; it would just lead to a very long unit test run which I figure we want to avoid for regular CI. As it turns out I adjusted the parameters to be more realistic and now this case never happens. But I will leave it in there just in case.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm uncomfortable with a "just in case" thing that disables tests. If you've tweaked the parameters so that it doesn't happen, just get rid of it. If a future person re-changes the parameters such that the test takes a long time, they can figure out what to do then.

for scaleByFwhm in (False, True):
config.scaleByFwhm = scaleByFwhm
for gsx in range(51, 1, -10):
config.gridStepX = config.gridSizeX = gsx
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the gridSize and gridStep always be equal? If not, you should have at least one test where they're different.

expectedVal = 1.
config.mapperSubtask.addAmount = expectedVal

for scaleByFwhm in (False, True):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd put the contents of one of these loops (maybe the inner-most one?) in a separate method, say _testGridValidity(self, scaleByFwhm, gridX, gridY), and then call that. The nested loops are a bit unwieldy.

Copy link
Contributor

Choose a reason for hiding this comment

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

It also concerns me that scaleByFwhm doesn't change any of the results. You should probably have a test where that value matters, otherwise it might be doing nothing and you'd never know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

scaleByFwhm changes the grid parameters, but here we simply want to ensure that given a set of input grid parameters, we don't fail and the final output image is not wrong. Thus the fact that they are the same is a good thing. That said, you're right that we should check the grid itself. I'll add a test for that.

"""
addAmount = pexConfig.Field(
dtype=float,
doc="""Amount to add to image""",
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't need triple quotes here.

Copy link
Contributor

@parejkoj parejkoj left a comment

Choose a reason for hiding this comment

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

More comments on the latest commit.

Bounding box of original exposure (not used here)
kwargs
Arbitrary keyword arguments (ignored)
-------
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't need line here, just under Returns.

Bounding box of original exposure (not used here)
kwargs
Arbitrary keyword arguments (ignored)
-------
Copy link
Contributor

Choose a reason for hiding this comment

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

Another unnecessary line.

boxes = task._generateGrid(self.exposure)
ind = 0 if scaleByFwhm else 1
lenBoxes[ind] = len(boxes[0])
if len(boxes[0]) > 1000: # bypass to prevent slow testing
Copy link
Contributor

Choose a reason for hiding this comment

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

I still don't like needing this if statement, particularly if you've reworked it to not result in such a large number of boxes.

mn = newArr.mean()
self.assertClose(mi.getImage().getArray().mean(), mn - expectedVal)
self.assertClose(mn, expectedVal)
if scaleByFwhm:
Copy link
Contributor

Choose a reason for hiding this comment

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

This suggests to me that you should have two separate test methods, one for scaleByFwhm=True and the other for False.

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 will lead to a lot of code duplication. Plus I want to compare the results of scaleByFwhm=True vs. False.


def testGridValidity(self):
"""!Test sample grids with various spacings and sizes and other options.
"""Test sample grids with various spacings and sizes and other options.

Try to see if we can break it.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure this line of the docstring is actually helpful? Maybe if you defined what this could break, and how?

Copy link
Contributor

@parejkoj parejkoj left a comment

Choose a reason for hiding this comment

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

Thanks for the added tests. I'll bet the coverage is a fair bit better now.

A few comments about preferred assert choices.

mi = self.exposure.getMaskedImage()
isnan = np.isnan(newArr)
if not withNaNs:
self.assertTrue(np.sum(isnan) == 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't you want self.assertEqual(np.sum(isnan), 0)?

@@ -130,58 +142,122 @@ class ImageMapReduceTest(lsst.utils.tests.TestCase):
"""A test case for the image gridded processing task
"""
def setUp(self):
self._makeImage()
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you're using msg= now, probably set self.longMessage=True, to get both your custom messages, and the default unittest ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently True is the default, but I'll set it explicitly anyway.

self.testAverageVersusCopy(withNaNs=False)
self.testAverageVersusCopy(withNaNs=True)

def testAverageVersusCopy(self, withNaNs=False):
Copy link
Contributor

Choose a reason for hiding this comment

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

This should have a preceeding _, otherwise it'll get run by the test suite on its own.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, missed that.

newMI = newExp.getMaskedImage()
newArr = newMI.getImage().getArray()
mi = self.exposure.getMaskedImage()
isnan = np.isnan(newArr)
if not withNaNs:
self.assertTrue(np.sum(isnan) == 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

Another assertEqual here.

newMA1 = newMI1.getImage().getArray()
isnan = np.isnan(newMA1)
if not withNaNs:
self.assertTrue(np.sum(isnan) == 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

assertEqual

self.assertTrue(np.sum(isnan) == 0)
newMA2 = newMI2.getImage().getArray()

self.assertFloatsAlmostEqual(newMA1[~isnan], newMA2[~isnan])
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

newMI = newExp.getMaskedImage()
newArr = newMI.getImage().getArray()
mi = self.exposure.getMaskedImage()
isnan = np.isnan(newArr)
self.assertTrue(np.sum(isnan) == 0, msg='Failed NaN (%d), on config: %s' %
Copy link
Contributor

Choose a reason for hiding this comment

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

assertEqual again

msg='Failed on config: %s' % str(config))

self.assertTrue(lenBoxes[0] < lenBoxes[1], msg='Failed on config: %s' % str(config))
self.assertTrue(lenBoxes[0] < lenBoxes[1], msg='Failed lengths on config: %s' %
Copy link
Contributor

Choose a reason for hiding this comment

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

self.assertLess(lenBoxes[0], lenBoxes[1], ...)

@djreiss
Copy link
Contributor Author

djreiss commented Mar 4, 2017

Final commit of the unit test in this PR was instead moved to PR #46, to be merged alongside ImageMapReduceTask.

@djreiss djreiss closed this Mar 4, 2017
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

3 participants