PSF image representation in NDData/CCDData#13743
Conversation
| meta = MetaData(doc=_meta_doc, copy=False) | ||
|
|
||
| def __init__(self, data, uncertainty=None, mask=None, wcs=None, | ||
| def __init__(self, data, uncertainty=None, mask=None, wcs=None, psf=None, |
There was a problem hiding this comment.
Is it safer to put new keyword in the end in case people been passing in keywords as pos args?
There was a problem hiding this comment.
While adding this, I wondered if we shouldn't modify this API to make everything after data be keyword-only, since there's a lot of them? So it would look like: __init__(self, data, *, uncertainty=None, ...).
There was a problem hiding this comment.
Depends on who you ask. It would definitely break someone's code but if you ask @Cadair , he would be like 🔥 🔥 🔥
There was a problem hiding this comment.
While adding this, I wondered if we shouldn't modify this API to make everything after data be keyword-only, since there's a lot of them?
I don't think we want to do this in minor version bump; there may be issues also with the @use_nddata decorator but I don't recall the details of that.
|
Thanks! Definitely need a change log. Maybe a What's New too but I'll let others decide on that one. |
That is one of the things I was wondering about: I think it hinges on the question of how much structure you want to have in the CCDData object. Having a dedicated name for the PSF makes it readily discoverable, and we can enforce the type of the object. LSST's equivalent to CCDData ( I can think of a handful of other things one might add as named objects on CCDData (e.g. an LSF for spectra), but none of them are as broadly useful as the PSF, to my mind. Eventually extending this PSF to include model representations seems like a future next step, but we don't need it for our purposes, and there's not really any way to generically represent a model PSF. I think the question is "what, besides WCS, should be a first-class named parameter?" I think the PSF is the next thing. An object representing the photometric calibration isn't as broadly applicable as the WCS and PSF are, otherwise it would be my next suggestion. |
|
@parejkoj -- it is best to mention me in replies -- my github notifications are a train wreck because of conda-forge (I know I could fix that, but) (and still my bad for not checking back sooner).
That makes sense -- I'm fine with adding it at a fairly high level. Do you have time to do the changelog and some basics documentation this week? |
|
@mwcraig :
I'm trying to finish it up now. One of the difficulties I've had is figuring out exactly where I should put all the necessary functionality and tests. I'm fixing the one line I missed that the coverage test pointed out, but I don't really know where else I need to add code. Suggestions very welcome there. |
1b8062f to
76b639f
Compare
|
@mwcraig : Ok, I believe I've added working FITS write/read support and a test that demonstrates it, there's a note in the changelog and what's new, and I've added some notes to the narrative docs (though I'm not sure what else should go there). Further comments very welcome. |
dhomeier
left a comment
There was a problem hiding this comment.
Some comments from a relatively outsider point of view.
To elaborate a bit more on the requirements a PSF image has to fulfil, all the docs mention it should be 'normalized', but that does not seem to be checked on input anywhere – at least the somewhat pathological np.random.normal(size=[20, 20]) used in the tests generally is not (where by 'normalized' I'd understand that convolution with such a PSF will be automatically flux-preserving; if something else is meant, that should probably be pointed out in the docs as well).
mwcraig
left a comment
There was a problem hiding this comment.
This is close to ready to go. One last big question: how should PSF's be handled in arithmetic operations? My inclination is to treat it like metadata, where I think we punt to the user to decide what the resulting metadata should be. The logic for several of the other properties is here: https://github.com/astropy/astropy/blob/main/astropy/nddata/mixins/ndarithmetic.py#L231
|
@mwcraig : The PSF should definitely not be modified during arithmetic operations. This sort of question is one of the reasons the LSST CCDData-like object ( |
Co-authored-by: Derek Homeier <dhomeie@gwdg.de>
|
pre-commit.ci autofix |
for more information, see https://pre-commit.ci
|
@dhomeier requested changes -- does he want to re-review? |
|
Do the commits need squashing? 😸 |
|
Thanks for opening the issue on documentation! |
@pllim -- I can do the squash if you want (I don't have strong feelings either way) |
| Parameters | ||
| ---------- | ||
| hdu_mask, hdu_uncertainty, hdu_flags : str or None, optional | ||
| hdu_mask, hdu_uncertainty, hdu_flags, hdu_psf: str or None, optional |
There was a problem hiding this comment.
| hdu_mask, hdu_uncertainty, hdu_flags, hdu_psf: str or None, optional | |
| hdu_mask, hdu_uncertainty, hdu_flags, hdu_psf : str or None, optional |
| If the unit is ``None`` or not otherwise specified it will raise a | ||
| ``ValueError`` | ||
|
|
||
| psf : `numpy.ndarray` or None, optional |
There was a problem hiding this comment.
tl;dr -- Is Quantity support planned for the future?
There was a problem hiding this comment.
I believe a psf image should always be unitless, but I'm not positive of that.
There was a problem hiding this comment.
I am just thinking what if someone stored it as data with pixel unit attached to it.
There was a problem hiding this comment.
hmmm... I'm not sure what to do on that point: I think the psf image as being purely an array (because the thing you do with it is (de)convolve it with the image), and I can't think of a reason someone would want it to have pixel units or what would be gained by that. Might be worth waiting to see how use-cases shake out and if anyone requests it?
There was a problem hiding this comment.
That is fine. I was just curious if this was discussed.
|
|
||
| # If both are None, there is nothing to do. | ||
| if self.psf is not None or operand.psf is not None: | ||
| warnings.warn(f"Not setting psf attribute during {operation.__name__}.", RuntimeWarning) |
There was a problem hiding this comment.
I didn't even know that was a thing. I don't have a strong opinion either way; your suggestion might make the warning more obvious to astropy users?
There was a problem hiding this comment.
Yes, I do think AstropyUserWarning is more explicit (short of creating your own warning class).
| def test_ccddata_with_psf(): | ||
| psf = _random_psf.copy() | ||
| ccd = CCDData(_random_array.copy(), unit=u.adu, psf=psf) | ||
| assert (ccd.psf == psf).all() |
There was a problem hiding this comment.
Why not use np.testing.assert_array_equal? Same question for all similar occurrences.
There was a problem hiding this comment.
I was just following the other tests above.
There was a problem hiding this comment.
I think the reasoning was that the created ccd should have an exact copy of the input psf in this case (and the other similar cases) so we really do want to check for exact equality.
There was a problem hiding this comment.
Not that it really matters, but np.testing.assert_array_equal is exact equality. np.testing.assert_array_almost_equal is probably the one you're thinking of.
There was a problem hiding this comment.
Maybe changing assert (a == b).all() to np.testing.assert_array_equal(a, b) can be a follow-up PR if you are interested.
| ccd.psf = "something" | ||
|
|
||
|
|
||
| def test_write_read_psf(tmpdir): |
There was a problem hiding this comment.
Please use tmp_path. We just went through a mini-campaign to get rid of tmpdir.
|
Thanks for the review @pllim -- I'll update around noon locally |
Co-authored-by: P. L. Lim <2090236+pllim@users.noreply.github.com>
|
|
||
| # If both are None, there is nothing to do. | ||
| if self.psf is not None or operand.psf is not None: | ||
| warnings.warn(f"Not setting psf attribute during {operation.__name__}.", RuntimeWarning) |
There was a problem hiding this comment.
You'll have to add the import yourself. I cannot suggest it at the line where it is supposed to go. Now precommit complains if it is not at the right place.
| warnings.warn(f"Not setting psf attribute during {operation.__name__}.", RuntimeWarning) | |
| warnings.warn(f"Not setting psf attribute during {operation.__name__}.", AstropyUserWarning) |
| # no warning if both are None | ||
| with warnings.catch_warnings(): | ||
| warnings.simplefilter("error") | ||
| ndd2.add(ndd2) |
| warnings.simplefilter("error") | ||
| ndd2.add(ndd2) | ||
|
|
||
| with pytest.warns(RuntimeWarning, match="Not setting psf attribute during add"): |
There was a problem hiding this comment.
| with pytest.warns(RuntimeWarning, match="Not setting psf attribute during add"): | |
| with pytest.warns(AstropyUserWarning, match="Not setting psf attribute during add"): |
|
|
||
| with pytest.warns(RuntimeWarning, match="Not setting psf attribute during add"): | ||
| ndd1.add(ndd2) | ||
| with pytest.warns(RuntimeWarning, match="Not setting psf attribute during add"): |
There was a problem hiding this comment.
| with pytest.warns(RuntimeWarning, match="Not setting psf attribute during add"): | |
| with pytest.warns(AstropyUserWarning, match="Not setting psf attribute during add"): |
| ndd1.add(ndd2) | ||
| with pytest.warns(RuntimeWarning, match="Not setting psf attribute during add"): | ||
| ndd2.add(ndd1) | ||
| with pytest.warns(RuntimeWarning, match="Not setting psf attribute during add"): |
There was a problem hiding this comment.
| with pytest.warns(RuntimeWarning, match="Not setting psf attribute during add"): | |
| with pytest.warns(AstropyUserWarning, match="Not setting psf attribute during add"): |
|
@pllim -- thanks for the review. I think all of your comments have been addressed. |
Co-authored-by: P. L. Lim <2090236+pllim@users.noreply.github.com>
|
Hopefully this time I really have them all! |
|
Interesting... Could it be that you actually support Quantity by accident? |
|
|
Turns out quantity is supported (though we don't get credit for that): In [1]: from astropy import units as u
In [3]: import numpy as np
In [4]: foo = 5 * u.pixel
In [5]: foo
Out[5]: <Quantity 5. pix>
In [6]: isinstance(foo, np.ndarray)
Out[6]: True |
|
Thanks, all! |
|
Thank you for all the help in getting this merged! |
Description
This pull request adds a simple PSF image to NDData/CCDData, to support the Rubin Observatory LSST Prompt Processing alert distribution system. The LSST alert packet image cutouts will be sent as astropy CCDData objects, and the only thing we see as missing from that format is the PSF. For our purposes for these image cutouts, all we need is a realization of the PSF image at the image center, not a generic parameterized or model PSF.
TODO:
Checklist for package maintainer(s)
This checklist is meant to remind the package maintainer(s) who will review this pull request of some common things to look for. This list is not exhaustive.
Extra CIlabel. Codestyle issues can be fixed by the bot.no-changelog-entry-neededlabel. If this is a manual backport, use theskip-changelog-checkslabel unless special changelog handling is necessary.astropy-botcheck might be missing; do not let the green checkmark fool you.backport-X.Y.xlabel(s) before merge.