LSST Applications 26.0.0,g0265f82a02+6660c170cc,g07994bdeae+30b05a742e,g0a0026dc87+17526d298f,g0a60f58ba1+17526d298f,g0e4bf8285c+96dd2c2ea9,g0ecae5effc+c266a536c8,g1e7d6db67d+6f7cb1f4bb,g26482f50c6+6346c0633c,g2bbee38e9b+6660c170cc,g2cc88a2952+0a4e78cd49,g3273194fdb+f6908454ef,g337abbeb29+6660c170cc,g337c41fc51+9a8f8f0815,g37c6e7c3d5+7bbafe9d37,g44018dc512+6660c170cc,g4a941329ef+4f7594a38e,g4c90b7bd52+5145c320d2,g58be5f913a+bea990ba40,g635b316a6c+8d6b3a3e56,g67924a670a+bfead8c487,g6ae5381d9b+81bc2a20b4,g93c4d6e787+26b17396bd,g98cecbdb62+ed2cb6d659,g98ffbb4407+81bc2a20b4,g9ddcbc5298+7f7571301f,ga1e77700b3+99e9273977,gae46bcf261+6660c170cc,gb2715bf1a1+17526d298f,gc86a011abf+17526d298f,gcf0d15dbbd+96dd2c2ea9,gdaeeff99f8+0d8dbea60f,gdb4ec4c597+6660c170cc,ge23793e450+96dd2c2ea9,gf041782ebf+171108ac67
LSST Data Management Base Package
Loading...
Searching...
No Matches
selectImages.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = ["BaseSelectImagesTask", "BaseExposureInfo", "WcsSelectImagesTask", "PsfWcsSelectImagesTask",
23 "DatabaseSelectImagesConfig", "BestSeeingSelectVisitsTask",
24 "BestSeeingQuantileSelectVisitsTask"]
25
26import numpy as np
27import lsst.sphgeom
28import lsst.utils as utils
29import lsst.pex.config as pexConfig
30import lsst.pex.exceptions as pexExceptions
31import lsst.geom as geom
32import lsst.pipe.base as pipeBase
33from lsst.skymap import BaseSkyMap
34from lsst.daf.base import DateTime
35from lsst.utils.timer import timeMethod
36
37
38class DatabaseSelectImagesConfig(pexConfig.Config):
39 """Base configuration for subclasses of BaseSelectImagesTask that use a
40 database.
41 """
42
43 host = pexConfig.Field(
44 doc="Database server host name",
45 dtype=str,
46 )
47 port = pexConfig.Field(
48 doc="Database server port",
49 dtype=int,
50 )
51 database = pexConfig.Field(
52 doc="Name of database",
53 dtype=str,
54 )
55 maxExposures = pexConfig.Field(
56 doc="maximum exposures to select; intended for debugging; ignored if None",
57 dtype=int,
58 optional=True,
59 )
60
61
62class BaseExposureInfo(pipeBase.Struct):
63 """Data about a selected exposure.
64
65 Parameters
66 ----------
67 dataId : `dict`
68 Data ID keys of exposure.
69 coordList : `list` [`lsst.afw.geom.SpherePoint`]
70 ICRS coordinates of the corners of the exposure
71 plus any others items that are desired.
72 """
73
74 def __init__(self, dataId, coordList):
75 super(BaseExposureInfo, self).__init__(dataId=dataId, coordList=coordList)
76
77
78class BaseSelectImagesTask(pipeBase.Task):
79 """Base task for selecting images suitable for coaddition.
80 """
81
82 ConfigClass = pexConfig.Config
83 _DefaultName = "selectImages"
84
85 @timeMethod
86 def run(self, coordList):
87 """Select images suitable for coaddition in a particular region.
88
89 Parameters
90 ----------
91 coordList : `list` [`lsst.geom.SpherePoint`] or `None`
92 List of coordinates defining region of interest; if `None`, then
93 select all images subclasses may add additional keyword arguments,
94 as required.
95
96 Returns
97 -------
98 result : `pipeBase.Struct`
99 Results as a struct with attributes:
100
101 ``exposureInfoList``
102 A list of exposure information objects (subclasses of
103 BaseExposureInfo), which have at least the following fields:
104 - dataId: Data ID dictionary (`dict`).
105 - coordList: ICRS coordinates of the corners of the exposure.
106 (`list` [`lsst.geom.SpherePoint`])
107 """
108 raise NotImplementedError()
109
110
111def _extractKeyValue(dataList, keys=None):
112 """Extract the keys and values from a list of dataIds.
113
114 The input dataList is a list of objects that have 'dataId' members.
115 This allows it to be used for both a list of data references and a
116 list of ExposureInfo.
117
118 Parameters
119 ----------
120 dataList : `Unknown`
121 keys : `Unknown`
122
123 Returns
124 -------
125 keys : `Unknown`
126 values : `Unknown`
127
128 Raises
129 ------
130 RuntimeError
131 Raised if DataId keys are inconsistent.
132 """
133 assert len(dataList) > 0
134 if keys is None:
135 keys = sorted(dataList[0].dataId.keys())
136 keySet = set(keys)
137 values = list()
138 for data in dataList:
139 thisKeys = set(data.dataId.keys())
140 if thisKeys != keySet:
141 raise RuntimeError("DataId keys inconsistent: %s vs %s" % (keySet, thisKeys))
142 values.append(tuple(data.dataId[k] for k in keys))
143 return keys, values
144
145
146class SelectStruct(pipeBase.Struct):
147 """A container for data to be passed to the WcsSelectImagesTask.
148
149 Parameters
150 ----------
151 dataRef : `Unknown`
152 Data reference.
154 Coordinate system definition (wcs).
155 bbox : `lsst.geom.box.Box2I`
156 Integer bounding box for image.
157 """
158
159 def __init__(self, dataRef, wcs, bbox):
160 super(SelectStruct, self).__init__(dataRef=dataRef, wcs=wcs, bbox=bbox)
161
162
164 """Select images using their Wcs.
165
166 We use the "convexHull" method of lsst.sphgeom.ConvexPolygon to define
167 polygons on the celestial sphere, and test the polygon of the
168 patch for overlap with the polygon of the image.
169
170 We use "convexHull" instead of generating a ConvexPolygon
171 directly because the standard for the inputs to ConvexPolygon
172 are pretty high and we don't want to be responsible for reaching them.
173 """
174
175 def run(self, wcsList, bboxList, coordList, dataIds=None, **kwargs):
176 """Return indices of provided lists that meet the selection criteria.
177
178 Parameters
179 ----------
180 wcsList : `list` [`lsst.afw.geom.SkyWcs`]
181 Specifying the WCS's of the input ccds to be selected.
182 bboxList : `list` [`lsst.geom.Box2I`]
183 Specifying the bounding boxes of the input ccds to be selected.
184 coordList : `list` [`lsst.geom.SpherePoint`]
185 ICRS coordinates specifying boundary of the patch.
186 dataIds : iterable [`lsst.daf.butler.dataId`] or `None`, optional
187 An iterable object of dataIds which point to reference catalogs.
188 **kwargs
189 Additional keyword arguments.
190
191 Returns
192 -------
193 result : `list` [`int`]
194 The indices of selected ccds.
195 """
196 if dataIds is None:
197 dataIds = [None] * len(wcsList)
198 patchVertices = [coord.getVector() for coord in coordList]
199 patchPoly = lsst.sphgeom.ConvexPolygon.convexHull(patchVertices)
200 result = []
201 for i, (imageWcs, imageBox, dataId) in enumerate(zip(wcsList, bboxList, dataIds)):
202 if imageWcs is None:
203 self.log.info("De-selecting exposure %s: Exposure has no WCS.", dataId)
204 else:
205 imageCorners = self.getValidImageCorners(imageWcs, imageBox, patchPoly, dataId)
206 if imageCorners:
207 result.append(i)
208 return result
209
210 def getValidImageCorners(self, imageWcs, imageBox, patchPoly, dataId=None):
211 """Return corners or `None` if bad.
212
213 Parameters
214 ----------
215 imageWcs : `Unknown`
216 imageBox : `Unknown`
217 patchPoly : `Unknown`
218 dataId : `Unknown`
219 """
220 try:
221 imageCorners = [imageWcs.pixelToSky(pix) for pix in geom.Box2D(imageBox).getCorners()]
223 # Protecting ourselves from awful Wcs solutions in input images
224 self.log.debug("WCS error in testing calexp %s (%s): deselecting", dataId, e)
225 return None
226
227 imagePoly = lsst.sphgeom.ConvexPolygon.convexHull([coord.getVector() for coord in imageCorners])
228 if imagePoly is None:
229 self.log.debug("Unable to create polygon from image %s: deselecting", dataId)
230 return None
231
232 if patchPoly.intersects(imagePoly):
233 # "intersects" also covers "contains" or "is contained by"
234 self.log.info("Selecting calexp %s", dataId)
235 return imageCorners
236
237 return None
238
239
240class PsfWcsSelectImagesConnections(pipeBase.PipelineTaskConnections,
241 dimensions=("tract", "patch", "skymap", "instrument", "visit"),
242 defaultTemplates={"coaddName": "deep"}):
243 pass
244
245
246class PsfWcsSelectImagesConfig(pipeBase.PipelineTaskConfig,
247 pipelineConnections=PsfWcsSelectImagesConnections):
248 maxEllipResidual = pexConfig.Field(
249 doc="Maximum median ellipticity residual",
250 dtype=float,
251 default=0.007,
252 optional=True,
253 )
254 maxSizeScatter = pexConfig.Field(
255 doc="Maximum scatter in the size residuals",
256 dtype=float,
257 optional=True,
258 )
259 maxScaledSizeScatter = pexConfig.Field(
260 doc="Maximum scatter in the size residuals, scaled by the median size",
261 dtype=float,
262 default=0.009,
263 optional=True,
264 )
265 maxPsfTraceRadiusDelta = pexConfig.Field(
266 doc="Maximum delta (max - min) of model PSF trace radius values evaluated on a grid on "
267 "the unmasked detector pixels (pixel).",
268 dtype=float,
269 default=0.7,
270 optional=True,
271 )
272
273
274class PsfWcsSelectImagesTask(WcsSelectImagesTask):
275 """Select images using their Wcs and cuts on the PSF properties.
276
277 The PSF quality criteria are based on the size and ellipticity
278 residuals from the adaptive second moments of the star and the PSF.
279
280 The criteria are:
281 - the median of the ellipticty residuals.
282 - the robust scatter of the size residuals (using the median absolute
283 deviation).
284 - the robust scatter of the size residuals scaled by the square of
285 the median size.
286 """
287
288 ConfigClass = PsfWcsSelectImagesConfig
289 _DefaultName = "PsfWcsSelectImages"
290
291 def run(self, wcsList, bboxList, coordList, visitSummary, dataIds=None, **kwargs):
292 """Return indices of provided lists that meet the selection criteria.
293
294 Parameters
295 ----------
296 wcsList : `list` [`lsst.afw.geom.SkyWcs`]
297 Specifying the WCS's of the input ccds to be selected.
298 bboxList : `list` [`lsst.geom.Box2I`]
299 Specifying the bounding boxes of the input ccds to be selected.
300 coordList : `list` [`lsst.geom.SpherePoint`]
301 ICRS coordinates specifying boundary of the patch.
302 visitSummary : `list` [`lsst.afw.table.ExposureCatalog`]
303 containing the PSF shape information for the input ccds to be
304 selected.
305 dataIds : iterable [`lsst.daf.butler.dataId`] or `None`, optional
306 An iterable object of dataIds which point to reference catalogs.
307 **kwargs
308 Additional keyword arguments.
309
310 Returns
311 -------
312 goodPsf : `list` [`int`]
313 The indices of selected ccds.
314 """
315 goodWcs = super(PsfWcsSelectImagesTask, self).run(wcsList=wcsList, bboxList=bboxList,
316 coordList=coordList, dataIds=dataIds)
317
318 goodPsf = []
319
320 for i, dataId in enumerate(dataIds):
321 if i not in goodWcs:
322 continue
323 if self.isValid(visitSummary, dataId["detector"]):
324 goodPsf.append(i)
325
326 return goodPsf
327
328 def isValid(self, visitSummary, detectorId):
329 """Should this ccd be selected based on its PSF shape information.
330
331 Parameters
332 ----------
333 visitSummary : `lsst.afw.table.ExposureCatalog`
334 Exposure catalog with per-detector summary information.
335 detectorId : `int`
336 Detector identifier.
337
338 Returns
339 -------
340 valid : `bool`
341 True if selected.
342 """
343 row = visitSummary.find(detectorId)
344 if row is None:
345 # This is not listed, so it must be bad.
346 self.log.warning("Removing detector %d because summary stats not available.", detectorId)
347 return False
348
349 medianE = np.sqrt(row["psfStarDeltaE1Median"]**2. + row["psfStarDeltaE2Median"]**2.)
350 scatterSize = row["psfStarDeltaSizeScatter"]
351 scaledScatterSize = row["psfStarScaledDeltaSizeScatter"]
352 psfTraceRadiusDelta = row["psfTraceRadiusDelta"]
353
354 valid = True
355 if self.config.maxEllipResidual and not (medianE <= self.config.maxEllipResidual):
356 self.log.info("Removing visit %d detector %d because median e residual too large: %f vs %f",
357 row["visit"], detectorId, medianE, self.config.maxEllipResidual)
358 valid = False
359 elif self.config.maxSizeScatter and not (scatterSize <= self.config.maxSizeScatter):
360 self.log.info("Removing visit %d detector %d because size scatter too large: %f vs %f",
361 row["visit"], detectorId, scatterSize, self.config.maxSizeScatter)
362 valid = False
363 elif self.config.maxScaledSizeScatter and not (scaledScatterSize <= self.config.maxScaledSizeScatter):
364 self.log.info("Removing visit %d detector %d because scaled size scatter too large: %f vs %f",
365 row["visit"], detectorId, scaledScatterSize, self.config.maxScaledSizeScatter)
366 valid = False
367 elif (
368 self.config.maxPsfTraceRadiusDelta is not None
369 and not (psfTraceRadiusDelta <= self.config.maxPsfTraceRadiusDelta)
370 ):
371 self.log.info(
372 "Removing visit %d detector %d because max-min delta of model PSF trace radius values "
373 "across the unmasked detector pixels is not finite or too large: %.3f vs %.3f (pixels)",
374 row["visit"], detectorId, psfTraceRadiusDelta, self.config.maxPsfTraceRadiusDelta
375 )
376 valid = False
377
378 return valid
379
380
381class BestSeeingSelectVisitsConnections(pipeBase.PipelineTaskConnections,
382 dimensions=("tract", "patch", "skymap", "band", "instrument"),
383 defaultTemplates={"coaddName": "goodSeeing",
384 "calexpType": ""}):
385 skyMap = pipeBase.connectionTypes.Input(
386 doc="Input definition of geometry/bbox and projection/wcs for coadded exposures",
387 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
388 storageClass="SkyMap",
389 dimensions=("skymap",),
390 )
391 visitSummaries = pipeBase.connectionTypes.Input(
392 doc="Per-visit consolidated exposure metadata",
393 name="finalVisitSummary",
394 storageClass="ExposureCatalog",
395 dimensions=("instrument", "visit",),
396 multiple=True,
397 deferLoad=True
398 )
399 goodVisits = pipeBase.connectionTypes.Output(
400 doc="Selected visits to be coadded.",
401 name="{coaddName}Visits",
402 storageClass="StructuredDataDict",
403 dimensions=("instrument", "tract", "patch", "skymap", "band"),
404 )
405
406
407class BestSeeingSelectVisitsConfig(pipeBase.PipelineTaskConfig,
408 pipelineConnections=BestSeeingSelectVisitsConnections):
409 nVisitsMax = pexConfig.RangeField(
410 dtype=int,
411 doc="Maximum number of visits to select",
412 default=12,
413 min=0
414 )
415 maxPsfFwhm = pexConfig.Field(
416 dtype=float,
417 doc="Maximum PSF FWHM (in arcseconds) to select",
418 default=1.5,
419 optional=True
420 )
421 minPsfFwhm = pexConfig.Field(
422 dtype=float,
423 doc="Minimum PSF FWHM (in arcseconds) to select",
424 default=0.,
425 optional=True
426 )
427 doConfirmOverlap = pexConfig.Field(
428 dtype=bool,
429 doc="Do remove visits that do not actually overlap the patch?",
430 default=True,
431 )
432 minMJD = pexConfig.Field(
433 dtype=float,
434 doc="Minimum visit MJD to select",
435 default=None,
436 optional=True
437 )
438 maxMJD = pexConfig.Field(
439 dtype=float,
440 doc="Maximum visit MJD to select",
441 default=None,
442 optional=True
443 )
444
445
446class BestSeeingSelectVisitsTask(pipeBase.PipelineTask):
447 """Select up to a maximum number of the best-seeing visits.
448
449 Don't exceed the FWHM range specified by configs min(max)PsfFwhm.
450 This Task is a port of the Gen2 image-selector used in the AP pipeline:
451 BestSeeingSelectImagesTask. This Task selects full visits based on the
452 average PSF of the entire visit.
453 """
454
455 ConfigClass = BestSeeingSelectVisitsConfig
456 _DefaultName = 'bestSeeingSelectVisits'
457
458 def runQuantum(self, butlerQC, inputRefs, outputRefs):
459 inputs = butlerQC.get(inputRefs)
460 quantumDataId = butlerQC.quantum.dataId
461 outputs = self.run(**inputs, dataId=quantumDataId)
462 butlerQC.put(outputs, outputRefs)
463
464 def run(self, visitSummaries, skyMap, dataId):
465 """Run task.
466
467 Parameters
468 ----------
469 visitSummary : `list` [`lsst.pipe.base.connections.DeferredDatasetRef`]
470 List of `lsst.pipe.base.connections.DeferredDatasetRef` of
471 visitSummary tables of type `lsst.afw.table.ExposureCatalog`.
472 skyMap : `lsst.skyMap.SkyMap`
473 SkyMap for checking visits overlap patch.
474 dataId : `dict` of dataId keys
475 For retrieving patch info for checking visits overlap patch.
476
477 Returns
478 -------
479 result : `lsst.pipe.base.Struct`
480 Results as a struct with attributes:
481
482 ``goodVisits``
483 A `dict` with selected visit ids as keys,
484 so that it can be be saved as a StructuredDataDict.
485 StructuredDataList's are currently limited.
486 """
487 if self.config.doConfirmOverlap:
488 patchPolygon = self.makePatchPolygon(skyMap, dataId)
489
490 inputVisits = [visitSummary.ref.dataId['visit'] for visitSummary in visitSummaries]
491 fwhmSizes = []
492 visits = []
493 for visit, visitSummary in zip(inputVisits, visitSummaries):
494 # read in one-by-one and only once. There may be hundreds
495 visitSummary = visitSummary.get()
496
497 # mjd is guaranteed to be the same for every detector in the
498 # visitSummary.
499 mjd = visitSummary[0].getVisitInfo().getDate().get(system=DateTime.MJD)
500
501 pixToArcseconds = [vs.getWcs().getPixelScale(vs.getBBox().getCenter()).asArcseconds()
502 for vs in visitSummary if vs.getWcs()]
503 # psfSigma is PSF model determinant radius at chip center in pixels
504 psfSigmas = np.array([vs['psfSigma'] for vs in visitSummary if vs.getWcs()])
505 fwhm = np.nanmean(psfSigmas * pixToArcseconds) * np.sqrt(8.*np.log(2.))
506
507 if self.config.maxPsfFwhm and fwhm > self.config.maxPsfFwhm:
508 continue
509 if self.config.minPsfFwhm and fwhm < self.config.minPsfFwhm:
510 continue
511 if self.config.minMJD and mjd < self.config.minMJD:
512 self.log.debug('MJD %f earlier than %.2f; rejecting', mjd, self.config.minMJD)
513 continue
514 if self.config.maxMJD and mjd > self.config.maxMJD:
515 self.log.debug('MJD %f later than %.2f; rejecting', mjd, self.config.maxMJD)
516 continue
517 if self.config.doConfirmOverlap and not self.doesIntersectPolygon(visitSummary, patchPolygon):
518 continue
519
520 fwhmSizes.append(fwhm)
521 visits.append(visit)
522
523 sortedVisits = [ind for (_, ind) in sorted(zip(fwhmSizes, visits))]
524 output = sortedVisits[:self.config.nVisitsMax]
525 self.log.info("%d images selected with FWHM range of %d--%d arcseconds",
526 len(output), fwhmSizes[visits.index(output[0])], fwhmSizes[visits.index(output[-1])])
527
528 # In order to store as a StructuredDataDict, convert list to dict
529 goodVisits = {key: True for key in output}
530 return pipeBase.Struct(goodVisits=goodVisits)
531
532 def makePatchPolygon(self, skyMap, dataId):
533 """Return True if sky polygon overlaps visit.
534
535 Parameters
536 ----------
538 Exposure catalog with per-detector geometry.
539 dataId : `dict` of dataId keys
540 For retrieving patch info.
541
542 Returns
543 -------
544 result : `lsst.sphgeom.ConvexPolygon.convexHull`
545 Polygon of patch's outer bbox.
546 """
547 wcs = skyMap[dataId['tract']].getWcs()
548 bbox = skyMap[dataId['tract']][dataId['patch']].getOuterBBox()
549 sphCorners = wcs.pixelToSky(lsst.geom.Box2D(bbox).getCorners())
550 result = lsst.sphgeom.ConvexPolygon.convexHull([coord.getVector() for coord in sphCorners])
551 return result
552
553 def doesIntersectPolygon(self, visitSummary, polygon):
554 """Return True if sky polygon overlaps visit.
555
556 Parameters
557 ----------
558 visitSummary : `lsst.afw.table.ExposureCatalog`
559 Exposure catalog with per-detector geometry.
560 polygon :` lsst.sphgeom.ConvexPolygon.convexHull`
561 Polygon to check overlap.
562
563 Returns
564 -------
565 doesIntersect : `bool`
566 True if the visit overlaps the polygon.
567 """
568 doesIntersect = False
569 for detectorSummary in visitSummary:
570 if (np.all(np.isfinite(detectorSummary['raCorners']))
571 and np.all(np.isfinite(detectorSummary['decCorners']))):
572 corners = [lsst.geom.SpherePoint(ra, dec, units=lsst.geom.degrees).getVector()
573 for (ra, dec) in
574 zip(detectorSummary['raCorners'], detectorSummary['decCorners'])]
575 detectorPolygon = lsst.sphgeom.ConvexPolygon.convexHull(corners)
576 if detectorPolygon.intersects(polygon):
577 doesIntersect = True
578 break
579 return doesIntersect
580
581
582class BestSeeingQuantileSelectVisitsConfig(pipeBase.PipelineTaskConfig,
583 pipelineConnections=BestSeeingSelectVisitsConnections):
584 qMin = pexConfig.RangeField(
585 doc="Lower bound of quantile range to select. Sorts visits by seeing from narrow to wide, "
586 "and select those in the interquantile range (qMin, qMax). Set qMin to 0 for Best Seeing. "
587 "This config should be changed from zero only for exploratory diffIm testing.",
588 dtype=float,
589 default=0,
590 min=0,
591 max=1,
592 )
593 qMax = pexConfig.RangeField(
594 doc="Upper bound of quantile range to select. Sorts visits by seeing from narrow to wide, "
595 "and select those in the interquantile range (qMin, qMax). Set qMax to 1 for Worst Seeing.",
596 dtype=float,
597 default=0.33,
598 min=0,
599 max=1,
600 )
601 nVisitsMin = pexConfig.Field(
602 doc="At least this number of visits selected and supercedes quantile. For example, if 10 visits "
603 "cover this patch, qMin=0.33, and nVisitsMin=5, the best 5 visits will be selected.",
604 dtype=int,
605 default=6,
606 )
607 doConfirmOverlap = pexConfig.Field(
608 dtype=bool,
609 doc="Do remove visits that do not actually overlap the patch?",
610 default=True,
611 )
612 minMJD = pexConfig.Field(
613 dtype=float,
614 doc="Minimum visit MJD to select",
615 default=None,
616 optional=True
617 )
618 maxMJD = pexConfig.Field(
619 dtype=float,
620 doc="Maximum visit MJD to select",
621 default=None,
622 optional=True
623 )
624
625
626class BestSeeingQuantileSelectVisitsTask(BestSeeingSelectVisitsTask):
627 """Select a quantile of the best-seeing visits.
628
629 Selects the best (for example, third) full visits based on the average
630 PSF width in the entire visit. It can also be used for difference imaging
631 experiments that require templates with the worst seeing visits.
632 For example, selecting the worst third can be acheived by
633 changing the config parameters qMin to 0.66 and qMax to 1.
634 """
635 ConfigClass = BestSeeingQuantileSelectVisitsConfig
636 _DefaultName = 'bestSeeingQuantileSelectVisits'
637
638 @utils.inheritDoc(BestSeeingSelectVisitsTask)
639 def run(self, visitSummaries, skyMap, dataId):
640 if self.config.doConfirmOverlap:
641 patchPolygon = self.makePatchPolygon(skyMap, dataId)
642 visits = np.array([visitSummary.ref.dataId['visit'] for visitSummary in visitSummaries])
643 radius = np.empty(len(visits))
644 intersects = np.full(len(visits), True)
645 for i, visitSummary in enumerate(visitSummaries):
646 # read in one-by-one and only once. There may be hundreds
647 visitSummary = visitSummary.get()
648 # psfSigma is PSF model determinant radius at chip center in pixels
649 psfSigma = np.nanmedian([vs['psfSigma'] for vs in visitSummary])
650 radius[i] = psfSigma
651 if self.config.doConfirmOverlap:
652 intersects[i] = self.doesIntersectPolygon(visitSummary, patchPolygon)
653 if self.config.minMJD or self.config.maxMJD:
654 # mjd is guaranteed to be the same for every detector in the
655 # visitSummary.
656 mjd = visitSummary[0].getVisitInfo().getDate().get(system=DateTime.MJD)
657 aboveMin = mjd > self.config.minMJD if self.config.minMJD else True
658 belowMax = mjd < self.config.maxMJD if self.config.maxMJD else True
659 intersects[i] = intersects[i] and aboveMin and belowMax
660
661 sortedVisits = [v for rad, v in sorted(zip(radius[intersects], visits[intersects]))]
662 lowerBound = min(int(np.round(self.config.qMin*len(visits[intersects]))),
663 max(0, len(visits[intersects]) - self.config.nVisitsMin))
664 upperBound = max(int(np.round(self.config.qMax*len(visits[intersects]))), self.config.nVisitsMin)
665
666 # In order to store as a StructuredDataDict, convert list to dict
667 goodVisits = {int(visit): True for visit in sortedVisits[lowerBound:upperBound]}
668 return pipeBase.Struct(goodVisits=goodVisits)
int min
int max
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition SkyWcs.h:117
Custom catalog class for ExposureRecord/Table.
Definition Exposure.h:311
A floating-point coordinate rectangle geometry.
Definition Box.h:413
An integer coordinate rectangle.
Definition Box.h:55
Point in an unspecified spherical coordinate system.
Definition SpherePoint.h:57
Reports arguments outside the domain of an operation.
Definition Runtime.h:57
Reports errors that are due to events beyond the control of the program.
Definition Runtime.h:104
getValidImageCorners(self, imageWcs, imageBox, patchPoly, dataId=None)
ConvexPolygon is a closed convex polygon on the unit sphere.
static ConvexPolygon convexHull(std::vector< UnitVector3d > const &points)
convexHull returns the convex hull of the given set of points if it exists and throws an exception ot...
daf::base::PropertyList * list
Definition fits.cc:928
daf::base::PropertySet * set
Definition fits.cc:927
bool isValid
Definition fits.cc:400
_extractKeyValue(dataList, keys=None)