-
Notifications
You must be signed in to change notification settings - Fork 3
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-24325: Include CCDData cutouts in alert packets #89
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,10 +21,15 @@ | |
|
||
__all__ = ("PackageAlertsConfig", "PackageAlertsTask") | ||
|
||
import io | ||
import os | ||
|
||
import lsst.afw.fits as afwFits | ||
from astropy import wcs | ||
import astropy.units as u | ||
from astropy.nddata import CCDData, VarianceUncertainty | ||
|
||
import lsst.alert.packet as alertPack | ||
import lsst.afw.geom as afwGeom | ||
import lsst.geom as geom | ||
import lsst.pex.config as pexConfig | ||
import lsst.pipe.base as pipeBase | ||
|
@@ -66,6 +71,8 @@ class PackageAlertsTask(pipeBase.Task): | |
ConfigClass = PackageAlertsConfig | ||
_DefaultName = "packageAlerts" | ||
|
||
_scale = (1.0 * geom.arcseconds).asDegrees() | ||
|
||
def __init__(self, **kwargs): | ||
super().__init__(**kwargs) | ||
self.alertSchema = alertPack.Schema.from_file(self.config.schemaFile) | ||
|
@@ -106,6 +113,7 @@ def run(self, | |
self._patchDiaSources(diaSourceCat) | ||
self._patchDiaSources(diaSrcHistory) | ||
ccdVisitId = diffIm.getInfo().getVisitInfo().getExposureId() | ||
diffImPhotoCalib = diffIm.getPhotoCalib() | ||
for srcIndex, diaSource in diaSourceCat.iterrows(): | ||
# Get all diaSources for the associated diaObject. | ||
diaObject = diaObjectCat.loc[srcIndex[0]] | ||
|
@@ -117,7 +125,11 @@ def run(self, | |
diaSource["decl"], | ||
geom.degrees) | ||
cutoutBBox = self.createDiaSourceBBox(diaSource["bboxSize"]) | ||
diffImCutout = diffIm.getCutout(sphPoint, cutoutBBox) | ||
diffImCutout = self.createCcdDataCutout( | ||
diffIm.getCutout(sphPoint, cutoutBBox), | ||
sphPoint, | ||
diffImPhotoCalib) | ||
|
||
templateCutout = None | ||
# TODO: Create alertIds DM-24858 | ||
alertId = diaSource["diaSourceId"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also not part of your changes, but I don't see how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed. Should have been removed in a previous ticket but was not. |
||
|
@@ -134,8 +146,7 @@ def run(self, | |
self.alertSchema.store_alerts(f, alerts) | ||
|
||
def _patchDiaSources(self, diaSources): | ||
"""Add the ``programId`` column to the data and change currently | ||
grouped alert data to ``None``. | ||
"""Add the ``programId`` column to the data. | ||
|
||
Parameters | ||
---------- | ||
|
@@ -165,6 +176,84 @@ def createDiaSourceBBox(self, bboxSize): | |
bbox = geom.Extent2I(bboxSize, bboxSize) | ||
return bbox | ||
|
||
def createCcdDataCutout(self, cutout, skyCenter, photoCalib): | ||
"""Convert a cutout into a calibrate CCDData image. | ||
|
||
Parameters | ||
---------- | ||
cutout : `lsst.afw.image.ExposureF` | ||
Cutout to convert. | ||
skyCenter : `lsst.geom.SpherePoint` | ||
Center point of DiaSource on the sky. | ||
photoCalib : `lsst.afw.image.PhotoCalib` | ||
Calibrate object of the image the cutout is cut from. | ||
|
||
Returns | ||
------- | ||
ccdData : `astropy.nddata.CCDData` | ||
CCDData object storing the calibrate information from the input | ||
difference or template image. | ||
""" | ||
# Find the value of the bottom corner of our cutout's BBox and | ||
# subtract 1 so that the CCDData cutout position value will be | ||
# [1, 1]. | ||
cutOutMinX = cutout.getBBox().minX - 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comment explaining the additional |
||
cutOutMinY = cutout.getBBox().minY - 1 | ||
center = cutout.getWcs().skyToPixel(skyCenter) | ||
calibCutout = photoCalib.calibrateImage(cutout.getMaskedImage()) | ||
|
||
cutoutWcs = wcs.WCS(naxis=2) | ||
cutoutWcs.array_shape = (cutout.getBBox().getWidth(), | ||
cutout.getBBox().getWidth()) | ||
cutoutWcs.wcs.crpix = [center.x - cutOutMinX, center.y - cutOutMinY] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the crpix at the corner rather than the center of the cutout? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, CCDData doesn't store absolute pixel locations. So when you create a dataset with it the "bottom left" pixel is always [1, 1]. Thus we center the crpix such that it's correctly within this newly shifted cutout and at the location of the DiaSource in the cutout. There is a possible future feature in astropy to add an origin to the CCDData object but it is currently not a priority. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think "not a priority" is right for them but if we want to add that feature they seem like they would be more than happy to accept a patch. We have been in discussion for years to add pixel origin to NDData. |
||
cutoutWcs.wcs.crval = [skyCenter.getRa().asDegrees(), | ||
skyCenter.getDec().asDegrees()] | ||
cutoutWcs.wcs.cd = self.makeLocalTransformMatrix(cutout.getWcs(), | ||
center, | ||
skyCenter) | ||
|
||
return CCDData( | ||
data=calibCutout.getImage().array, | ||
uncertainty=VarianceUncertainty(calibCutout.getVariance().array), | ||
flags=calibCutout.getMask().array, | ||
wcs=cutoutWcs, | ||
meta={"cutMinX": cutOutMinX, | ||
"cutMinY": cutOutMinY}, | ||
unit=u.nJy) | ||
|
||
def makeLocalTransformMatrix(self, wcs, center, skyCenter): | ||
"""Create a local, linear approximation of the wcs transformation | ||
matrix. | ||
|
||
The approximation is created as if the center is at RA=0, DEC=0. All | ||
comparing x,y coordinate are relative to the position of center. Matrix | ||
is initially calculated with units arcseconds and then converted to | ||
degrees. This yields higher precision results due to quirks in AST. | ||
|
||
Parameters | ||
---------- | ||
wcs : `lsst.afw.geom.SkyWcs` | ||
Wcs to approximate | ||
center : `lsst.geom.Point2D` | ||
Point at which to evaluate the LocalWcs. | ||
skyCenter : `lsst.geom.SpherePoint` | ||
Point on sky to approximate the Wcs. | ||
|
||
Returns | ||
------- | ||
localMatrix : `numpy.ndarray` | ||
Matrix representation the local wcs approximation with units | ||
degrees. | ||
""" | ||
blankCDMatrix = [[self._scale, 0], [0, self._scale]] | ||
localGnomonicWcs = afwGeom.makeSkyWcs( | ||
center, skyCenter, blankCDMatrix) | ||
measurementToLocalGnomonic = wcs.getTransform().then( | ||
localGnomonicWcs.getTransform().inverted() | ||
) | ||
localMatrix = measurementToLocalGnomonic.getJacobian(center) | ||
return localMatrix / 3600 | ||
|
||
def makeAlertDict(self, | ||
alertId, | ||
diaSource, | ||
|
@@ -182,12 +271,12 @@ def makeAlertDict(self, | |
DiaObject that ``diaSource`` is matched to. | ||
objDiaSrcHistory : `pandas.DataFrame` | ||
12 month history of ``diaObject`` excluding the latest DiaSource. | ||
diffImCutout : `lsst.afw.image.ExposureF` or `None` | ||
diffImCutout : `astropy.nddata.CCDData` or `None` | ||
Cutout of the difference image around the location of ``diaSource`` | ||
with a size set by the ``cutoutSize`` configurable. | ||
templateCutout : `lsst.afw.image.ExposureF` or `None` | ||
with a min size set by the ``cutoutSize`` configurable. | ||
templateCutout : `astropy.nddata.CCDData` or `None` | ||
Cutout of the template image around the location of ``diaSource`` | ||
with a size set by the ``cutoutSize`` configurable. | ||
with a min size set by the ``cutoutSize`` configurable. | ||
""" | ||
alert = dict() | ||
alert['alertId'] = alertId | ||
|
@@ -205,25 +294,26 @@ def makeAlertDict(self, | |
|
||
alert['ssObject'] = None | ||
|
||
alert['cutoutDifference'] = self.makeCutoutBytes(diffImCutout) | ||
alert['cutoutDifference'] = self.streamCcdDataToBytes(diffImCutout) | ||
# TODO: add template cutouts in DM-24327 | ||
alert["cutoutTemplate"] = None | ||
|
||
return alert | ||
|
||
def makeCutoutBytes(self, cutout): | ||
def streamCcdDataToBytes(self, cutout): | ||
"""Serialize a cutout into bytes. | ||
|
||
Parameters | ||
---------- | ||
cutout : `lsst.afw.image.ExposureF` | ||
cutout : `astropy.nddata.CCDData` | ||
Cutout to serialize. | ||
|
||
Returns | ||
------- | ||
coutputBytes : `bytes` | ||
Input cutout serialized into byte data. | ||
""" | ||
mgr = afwFits.MemFileManager() | ||
cutout.writeFits(mgr) | ||
return mgr.getData() | ||
with io.BytesIO() as streamer: | ||
cutout.write(streamer, format="fits") | ||
cutoutBytes = streamer.getvalue() | ||
return cutoutBytes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not part of your changes, but it doesn't look like
template
orccdExposureIdBits
are used anywhere.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually will so I'm going to leave it for now.