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-34909: Add preconvolution option for image differencing #238

Merged
merged 4 commits into from Feb 7, 2023

Conversation

isullivan
Copy link
Contributor

No description provided.

@isullivan isullivan force-pushed the tickets/DM-34909 branch 2 times, most recently from ef77dbb to ecc3c2c Compare November 24, 2022 00:15
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.

A lot of docstring-related comments, and several places where I think you can consolidate to reduce code duplication.

I am concerned about the clip-to-bbox change that is part of this: are we sure that that won't break anything downstream that is expecting the calexp and template/difference to all have the same size? Or am I not understanding the nature of the change.

python/lsst/ip/diffim/detectAndMeasure.py Outdated Show resolved Hide resolved
python/lsst/ip/diffim/detectAndMeasure.py Outdated Show resolved Hide resolved

class DetectAndMeasureScoreConnections(DetectAndMeasureConnections):
scoreExposure = pipeBase.connectionTypes.Input(
doc="Output likelihood score image",
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like this docstring needs some more information: does it have a mask/variance? units? What does likelihood score mean exactly? I don't know how much of that should go into this docstring, but something more informative, please.

@timeMethod
def run(self, science, matchedTemplate, difference, scoreExposure,
idFactory=None):
"""Detect and measure sources on a difference image.
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 torn on whether we should have a docstring for this run method at all. The only API change is adding scoreExposure, but also what run does here is different because of that score image. If you do leave this docstring, please expand the opening sentence/paragraph a bit to describe how it's different, otherwise expand the class docstring a bunch.

python/lsst/ip/diffim/detectAndMeasure.py Outdated Show resolved Hide resolved
# This will fail if we have mis-subtracted the background.
stdVal = _computeRobustStatistics(output.scoreExposure.image, output.scoreExposure.mask,
statsCtrl, statistic=afwMath.STDEV)
# get the img psf Noise Equivalent Area value
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment doesn't add anything; the function name is descriptive enough.

deltaStd = _computeRobustStatistics(delta.image, delta.mask, statsCtrl,
statistic=afwMath.STDEV)
self.assertFloatsAlmostEqual(deltaMean, 0, atol=5*meanError)
# get the img psf Noise Equivalent Area value
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment doesn't add anything; the function name is descriptive enough.


Returns
-------
nea : `float`
Copy link
Contributor

Choose a reason for hiding this comment

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

Docstrings for Parameters and Returns.



def _computePSFNoiseEquivalentArea(psf):
"""Compute the noise equivalent area for an image psf
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 familiar with "noise equivalent area" - should this be something available for PSFs in general, or is it really only a thing for these tests? Is it worth a sentence describing what it means here?



class DetectAndMeasureScoreConfig(DetectAndMeasureConfig,
pipelineConnections=DetectAndMeasureScoreConnections):
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 even need this class, if it's just DetectAndMeasureConfig? Until you add/override things in it, might as well just use DetectAndMeasureConfig as the ConfigClass for the new Task.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's needed to set the connections.

@isullivan isullivan force-pushed the tickets/DM-34909 branch 2 times, most recently from 1aa4258 to 1a9a284 Compare December 8, 2022 20:51
@isullivan isullivan force-pushed the tickets/DM-34909 branch 2 times, most recently from 9abc085 to 60ce4e1 Compare January 10, 2023 02:55
@isullivan isullivan force-pushed the tickets/DM-34909 branch 2 times, most recently from 53d5247 to cc46b73 Compare January 25, 2023 02:38
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.

I see several previous comments that were maybe missed? I tagged the ones I saw with eyes.

I think there's a lot of consolidation that can happen in these tests: as it stands, it's very unclear what's different between the different test methods.

I don't think you want to use the mask image in selectSources at all: the input catalog should already have all relevant flags set.


class DetectAndMeasureScoreTask(DetectAndMeasureTask):
"""Detect DIA sources using a Score image,
and measure the detections on the difference image.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please expand the class docstring to explain why this is different from regular DetectandMeasure.

Also, what's up with the formatting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's wrong with the formatting? It's too long for a single line, and I prefer to break lines at a comma if there is one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fine to leave it, but we normally just wrap at the 79char limit.

defaultTemplates=_defaultTemplates):
scoreExposure = connectionTypes.Output(
doc="The Score, or maximum likelihood, exposure,"
" used for the detection of diaSources.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is Score capitalized?

Why is this two lines?

@@ -557,18 +559,28 @@ def _convolveExposure(exposure, kernel, convolutionControl,
else:
return convolvedExposure[bbox]

def _sourceSelector(self, sources):
def sourceSelector(self, mask, sources):
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are you making this non-private?

Also, generally when changing the args, I'd recommend adding the new arg after the old one (so sources, mask).

------
RuntimeError
If there are too few sources to compute the PSF matching kernel
remaining after source selection.
"""
flags = [True, ]*len(sources)
Copy link
Contributor

Choose a reason for hiding this comment

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

I know it's not your commit, but I'm a bit bothered by this being a list and not np.ones(len(sources), dtype=bool).

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'm changing it, but I think the original was more clear.

selectSources = sources[flags]
self.log.info("%i sources selected for PSF matching out of %i from the input catalog",
len(selectSources), len(sources))
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this would be easier to parse in the logs if it were (putting the numbers for comparison together):

nSelected = sum(flags)
"%i/%i=%.3f sources selected for PSF matching from the input catalog", len(selectSources), len(sources), nSelected, len(sources)/nSelected

Not sure whether it's worth including the fraction at the end, but doing the math for people can be handy.

for diaSource in output.diaSources:
self._check_diaSource(transientSources, diaSource, refIds=refIds, scale=polarity)
_detection_wrapper(polarity=1)
_detection_wrapper(polarity=-1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Polarity is really a bool, no? False=>negative, True->positive? Or just make it positive=True/False instead?

Comment on lines +417 to +475
matchedTemplate.image.array[...] = np.roll(matchedTemplate.image.array[...], offset, axis=0)
matchedTemplate.variance.array[...] = np.roll(matchedTemplate.variance.array[...], offset, axis=0)
matchedTemplate.mask.array[...] = np.roll(matchedTemplate.mask.array[...], offset, axis=0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably worth a comment to say that this shifts the maskedImage by one pixel, to cause dipoles in the subtraction; it wasn't obvious on first view.

config.doForcedMeasurement = False
detectionTask = detectAndMeasure.DetectAndMeasureScoreTask(config=config)
output = detectionTask.run(science, matchedTemplate, difference, score)
self.assertIn(dipoleFlag, output.diaSources.schema.getNames())
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't that flag always be in the output schema, whether there are detected dipoles in the image or not?

output = detectionTask.run(science, matchedTemplate, difference, score)
self.assertIn(dipoleFlag, output.diaSources.schema.getNames())
nSourcesDet = len(sources)
self.assertEqual(len(output.diaSources), 2*nSourcesDet)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why should diaSources be twice the length of the input source list?

self._check_diaSource(transientSources, skySource, matchDistance=1000, scale=0.,
atol=np.sqrt(transientFluxRange*transientFluxLevel))

def _check_diaSource(self, refSources, diaSource, refIds=None,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be in a parent class or a free function? This version looks identical to the one in DetectAndMeasureTest.

@isullivan isullivan force-pushed the tickets/DM-34909 branch 3 times, most recently from eaa9f7c to f742b0f Compare February 4, 2023 01:14
rtol=rtol, atol=atol)

def _check_values(self, values, minValue=None, maxValue=None):
"""Summary
Copy link
Contributor

Choose a reason for hiding this comment

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

"summary?"

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 class docs: that's helpful!

I want to chat about the mask plane check in selectSources: I think I'm still not fully understanding that.

# Run detection and check the results
def _detection_wrapper(positive=True):
"""
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

?

@parejkoj
Copy link
Contributor

parejkoj commented Feb 7, 2023

Lets chat about it today, but I think what you want to replace the mask flags check with is something like getSelectSources from imagePsfMatch: that runs a handful of measurement plugins on the image, including the PixelFlags plugin, which will fill in all the pixel flags.

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.

After chatting, please file a ticket to look into using the base_PixelFlags measurement plugin to deal with the mask bits.

@isullivan isullivan merged commit 5801d47 into main Feb 7, 2023
@isullivan isullivan deleted the tickets/DM-34909 branch February 7, 2023 21:39
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