22 from __future__
import absolute_import, division, print_function
23 from builtins
import zip
25 import lsst.pex.config
as pexConfig
26 import lsst.pex.exceptions
as pexExceptions
27 import lsst.afw.geom
as afwGeom
28 import lsst.pipe.base
as pipeBase
30 __all__ = [
"BaseSelectImagesTask",
"BaseExposureInfo",
"WcsSelectImagesTask",
"PsfWcsSelectImagesTask",
31 "DatabaseSelectImagesConfig"]
35 """Base configuration for subclasses of BaseSelectImagesTask that use a database"""
36 host = pexConfig.Field(
37 doc=
"Database server host name",
40 port = pexConfig.Field(
41 doc=
"Database server port",
44 database = pexConfig.Field(
45 doc=
"Name of database",
48 maxExposures = pexConfig.Field(
49 doc=
"maximum exposures to select; intended for debugging; ignored if None",
56 """Data about a selected exposure
60 """Create exposure information that can be used to generate data references
62 The object has the following fields:
63 - dataId: data ID of exposure (a dict)
64 - coordList: a list of corner coordinates of the exposure (list of afwCoord.IcrsCoord)
65 plus any others items that are desired
67 super(BaseExposureInfo, self).
__init__(dataId=dataId, coordList=coordList)
71 """Base task for selecting images suitable for coaddition
73 ConfigClass = pexConfig.Config
74 _DefaultName =
"selectImages"
77 def run(self, coordList):
78 """Select images suitable for coaddition in a particular region
80 @param[in] coordList: list of coordinates defining region of interest; if None then select all images
81 subclasses may add additional keyword arguments, as required
83 @return a pipeBase Struct containing:
84 - exposureInfoList: a list of exposure information objects (subclasses of BaseExposureInfo),
85 which have at least the following fields:
86 - dataId: data ID dictionary
87 - coordList: coordinates of the corner of the exposure (list of afwCoord.IcrsCoord)
89 raise NotImplementedError()
91 def _runArgDictFromDataId(self, dataId):
92 """Extract keyword arguments for run (other than coordList) from a data ID
94 @return keyword arguments for run (other than coordList), as a dict
96 raise NotImplementedError()
98 def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]):
99 """Run based on a data reference
101 This delegates to run() and _runArgDictFromDataId() to do the actual
102 selection. In the event that the selectDataList is non-empty, this will
103 be used to further restrict the selection, providing the user with
104 additional control over the selection.
106 @param[in] dataRef: data reference; must contain any extra keys needed by the subclass
107 @param[in] coordList: list of coordinates defining region of interest; if None, search the whole sky
108 @param[in] makeDataRefList: if True, return dataRefList
109 @param[in] selectDataList: List of SelectStruct with dataRefs to consider for selection
110 @return a pipeBase Struct containing:
111 - exposureInfoList: a list of objects derived from ExposureInfo
112 - dataRefList: a list of data references (None if makeDataRefList False)
115 exposureInfoList = self.
run(coordList, **runArgDict).exposureInfoList
117 if len(selectDataList) > 0
and len(exposureInfoList) > 0:
119 ccdKeys, ccdValues = _extractKeyValue(exposureInfoList)
120 inKeys, inValues = _extractKeyValue([s.dataRef
for s
in selectDataList], keys=ccdKeys)
121 inValues = set(inValues)
122 newExposureInfoList = []
123 for info, ccdVal
in zip(exposureInfoList, ccdValues):
124 if ccdVal
in inValues:
125 newExposureInfoList.append(info)
127 self.log.info(
"De-selecting exposure %s: not in selectDataList" % info.dataId)
128 exposureInfoList = newExposureInfoList
131 butler = dataRef.butlerSubset.butler
132 dataRefList = [butler.dataRef(datasetType=
"calexp",
133 dataId=expInfo.dataId,
134 )
for expInfo
in exposureInfoList]
138 return pipeBase.Struct(
139 dataRefList=dataRefList,
140 exposureInfoList=exposureInfoList,
144 def _extractKeyValue(dataList, keys=None):
145 """Extract the keys and values from a list of dataIds
147 The input dataList is a list of objects that have 'dataId' members.
148 This allows it to be used for both a list of data references and a
151 assert len(dataList) > 0
153 keys = sorted(dataList[0].dataId.keys())
156 for data
in dataList:
157 thisKeys = set(data.dataId.keys())
158 if thisKeys != keySet:
159 raise RuntimeError(
"DataId keys inconsistent: %s vs %s" % (keySet, thisKeys))
160 values.append(tuple(data.dataId[k]
for k
in keys))
165 """A container for data to be passed to the WcsSelectImagesTask"""
168 super(SelectStruct, self).
__init__(dataRef=dataRef, wcs=wcs, dims=dims)
172 """Select images using their Wcs"""
174 def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]):
175 """Select images in the selectDataList that overlap the patch
177 We use the "convexHull" function in the geom package to define
178 polygons on the celestial sphere, and test the polygon of the
179 patch for overlap with the polygon of the image.
181 We use "convexHull" instead of generating a SphericalConvexPolygon
182 directly because the standard for the inputs to SphericalConvexPolygon
183 are pretty high and we don't want to be responsible for reaching them.
184 If "convexHull" is found to be too slow, we can revise this.
186 @param dataRef: Data reference for coadd/tempExp (with tract, patch)
187 @param coordList: List of Coord specifying boundary of patch
188 @param makeDataRefList: Construct a list of data references?
189 @param selectDataList: List of SelectStruct, to consider for selection
191 from lsst.geom
import convexHull
194 exposureInfoList = []
196 patchVertices = [coord.getVector()
for coord
in coordList]
197 patchPoly = convexHull(patchVertices)
199 for data
in selectDataList:
200 dataRef = data.dataRef
204 imageBox = afwGeom.Box2D(afwGeom.Point2D(0, 0), afwGeom.Extent2D(nx, ny))
206 imageCorners = [imageWcs.pixelToSky(pix)
for pix
in imageBox.getCorners()]
207 except (pexExceptions.DomainError, pexExceptions.RuntimeError)
as e:
209 self.log.debug(
"WCS error in testing calexp %s (%s): deselecting", dataRef.dataId, e)
212 imagePoly = convexHull([coord.getVector()
for coord
in imageCorners])
213 if imagePoly
is None:
214 self.log.debug(
"Unable to create polygon from image %s: deselecting", dataRef.dataId)
216 if patchPoly.intersects(imagePoly):
217 self.log.info(
"Selecting calexp %s" % dataRef.dataId)
218 dataRefList.append(dataRef)
221 return pipeBase.Struct(
222 dataRefList=dataRefList
if makeDataRefList
else None,
223 exposureInfoList=exposureInfoList,
228 maxEllipResidual = pexConfig.Field(
229 doc=
"Maximum median ellipticity residual",
234 maxSizeScatter = pexConfig.Field(
235 doc=
"Maximum scatter in the size residuals",
239 maxScaledSizeScatter = pexConfig.Field(
240 doc=
"Maximum scatter in the size residuals, scaled by the median size",
245 starSelection = pexConfig.Field(
246 doc=
"select star with this field",
248 default=
'calib_psfUsed'
250 starShape = pexConfig.Field(
251 doc=
"name of star shape",
253 default=
'base_SdssShape'
255 psfShape = pexConfig.Field(
256 doc=
"name of psf shape",
258 default=
'base_SdssShape_psf'
263 "Return median absolute deviation scaled to normally distributed data"
264 return 1.4826*np.median(np.abs(array - np.median(array)))
267 """Select images using their Wcs and cuts on the PSF properties"""
269 ConfigClass = PsfWcsSelectImagesConfig
270 _DefaultName =
"PsfWcsSelectImages"
272 def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]):
273 """Select images in the selectDataList that overlap the patch and satisfy PSF quality critera.
275 The PSF quality criteria are based on the size and ellipticity residuals from the
276 adaptive second moments of the star and the PSF.
279 - the median of the ellipticty residuals
280 - the robust scatter of the size residuals (using the median absolute deviation)
281 - the robust scatter of the size residuals scaled by the square of
284 @param dataRef: Data reference for coadd/tempExp (with tract, patch)
285 @param coordList: List of Coord specifying boundary of patch
286 @param makeDataRefList: Construct a list of data references?
287 @param selectDataList: List of SelectStruct, to consider for selection
289 result = super(PsfWcsSelectImagesTask, self).
runDataRef(dataRef, coordList, makeDataRefList,
293 exposureInfoList = []
294 for dataRef, exposureInfo
in zip(result.dataRefList, result.exposureInfoList):
295 butler = dataRef.butlerSubset.butler
296 srcCatalog = butler.get(
'src',dataRef.dataId)
297 mask = srcCatalog[self.config.starSelection]
299 starXX = srcCatalog[self.config.starShape+
'_xx'][mask]
300 starYY = srcCatalog[self.config.starShape+
'_yy'][mask]
301 starXY = srcCatalog[self.config.starShape+
'_xy'][mask]
302 psfXX = srcCatalog[self.config.psfShape+
'_xx'][mask]
303 psfYY = srcCatalog[self.config.psfShape+
'_yy'][mask]
304 psfXY = srcCatalog[self.config.psfShape+
'_xy'][mask]
306 starSize = np.power(starXX*starYY - starXY**2, 0.25)
307 starE1 = (starXX - starYY)/(starXX + starYY)
308 starE2 = 2*starXY/(starXX + starYY)
309 medianSize = np.median(starSize)
311 psfSize = np.power(psfXX*psfYY - psfXY**2, 0.25)
312 psfE1 = (psfXX - psfYY)/(psfXX + psfYY)
313 psfE2 = 2*psfXY/(psfXX + psfYY)
315 medianE1 = np.abs(np.median(starE1 - psfE1))
316 medianE2 = np.abs(np.median(starE2 - psfE2))
317 medianE = np.sqrt(medianE1**2 + medianE2**2)
319 scatterSize =
sigmaMad(starSize - psfSize)
320 scaledScatterSize = scatterSize/medianSize**2
323 if self.config.maxEllipResidual
and medianE > self.config.maxEllipResidual:
324 self.log.info(
"Removing visit %s because median e residual too large: %f vs %f" %
325 (dataRef.dataId, medianE, self.config.maxEllipResidual))
327 elif self.config.maxSizeScatter
and scatterSize > self.config.maxSizeScatter:
328 self.log.info(
"Removing visit %s because size scatter is too large: %f vs %f" %
329 (dataRef.dataId, scatterSize, self.config.maxSizeScatter))
331 elif self.config.maxScaledSizeScatter
and scaledScatterSize > self.config.maxScaledSizeScatter:
332 self.log.info(
"Removing visit %s because scaled size scatter is too large: %f vs %f" %
333 (dataRef.dataId, scaledScatterSize, self.config.maxScaledSizeScatter))
339 dataRefList.append(dataRef)
340 exposureInfoList.append(exposureInfo)
342 return pipeBase.Struct(
343 dataRefList=dataRefList,
344 exposureInfoList=exposureInfoList,
def _runArgDictFromDataId