LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
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
266
267class PsfWcsSelectImagesTask(WcsSelectImagesTask):
268 """Select images using their Wcs and cuts on the PSF properties.
269
270 The PSF quality criteria are based on the size and ellipticity
271 residuals from the adaptive second moments of the star and the PSF.
272
273 The criteria are:
274 - the median of the ellipticty residuals.
275 - the robust scatter of the size residuals (using the median absolute
276 deviation).
277 - the robust scatter of the size residuals scaled by the square of
278 the median size.
279 """
280
281 ConfigClass = PsfWcsSelectImagesConfig
282 _DefaultName = "PsfWcsSelectImages"
283
284 def run(self, wcsList, bboxList, coordList, visitSummary, dataIds=None, **kwargs):
285 """Return indices of provided lists that meet the selection criteria.
286
287 Parameters
288 ----------
289 wcsList : `list` [`lsst.afw.geom.SkyWcs`]
290 Specifying the WCS's of the input ccds to be selected.
291 bboxList : `list` [`lsst.geom.Box2I`]
292 Specifying the bounding boxes of the input ccds to be selected.
293 coordList : `list` [`lsst.geom.SpherePoint`]
294 ICRS coordinates specifying boundary of the patch.
295 visitSummary : `list` [`lsst.afw.table.ExposureCatalog`]
296 containing the PSF shape information for the input ccds to be
297 selected.
298 dataIds : iterable [`lsst.daf.butler.dataId`] or `None`, optional
299 An iterable object of dataIds which point to reference catalogs.
300 **kwargs
301 Additional keyword arguments.
302
303 Returns
304 -------
305 goodPsf : `list` [`int`]
306 The indices of selected ccds.
307 """
308 goodWcs = super(PsfWcsSelectImagesTask, self).run(wcsList=wcsList, bboxList=bboxList,
309 coordList=coordList, dataIds=dataIds)
310
311 goodPsf = []
312
313 for i, dataId in enumerate(dataIds):
314 if i not in goodWcs:
315 continue
316 if self.isValid(visitSummary, dataId["detector"]):
317 goodPsf.append(i)
318
319 return goodPsf
320
321 def isValid(self, visitSummary, detectorId):
322 """Should this ccd be selected based on its PSF shape information.
323
324 Parameters
325 ----------
326 visitSummary : `lsst.afw.table.ExposureCatalog`
327 Exposure catalog with per-detector summary information.
328 detectorId : `int`
329 Detector identifier.
330
331 Returns
332 -------
333 valid : `bool`
334 True if selected.
335 """
336 row = visitSummary.find(detectorId)
337 if row is None:
338 # This is not listed, so it must be bad.
339 self.log.warning("Removing detector %d because summary stats not available.", detectorId)
340 return False
341
342 medianE = np.sqrt(row["psfStarDeltaE1Median"]**2. + row["psfStarDeltaE2Median"]**2.)
343 scatterSize = row["psfStarDeltaSizeScatter"]
344 scaledScatterSize = row["psfStarScaledDeltaSizeScatter"]
345
346 valid = True
347 if self.config.maxEllipResidual and medianE > self.config.maxEllipResidual:
348 self.log.info("Removing visit %d detector %d because median e residual too large: %f vs %f",
349 row["visit"], detectorId, medianE, self.config.maxEllipResidual)
350 valid = False
351 elif self.config.maxSizeScatter and scatterSize > self.config.maxSizeScatter:
352 self.log.info("Removing visit %d detector %d because size scatter too large: %f vs %f",
353 row["visit"], detectorId, scatterSize, self.config.maxSizeScatter)
354 valid = False
355 elif self.config.maxScaledSizeScatter and scaledScatterSize > self.config.maxScaledSizeScatter:
356 self.log.info("Removing visit %d detector %d because scaled size scatter too large: %f vs %f",
357 row["visit"], detectorId, scaledScatterSize, self.config.maxScaledSizeScatter)
358 valid = False
359
360 return valid
361
362
363class BestSeeingSelectVisitsConnections(pipeBase.PipelineTaskConnections,
364 dimensions=("tract", "patch", "skymap", "band", "instrument"),
365 defaultTemplates={"coaddName": "goodSeeing"}):
366 skyMap = pipeBase.connectionTypes.Input(
367 doc="Input definition of geometry/bbox and projection/wcs for coadded exposures",
368 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
369 storageClass="SkyMap",
370 dimensions=("skymap",),
371 )
372 visitSummaries = pipeBase.connectionTypes.Input(
373 doc="Per-visit consolidated exposure metadata from ConsolidateVisitSummaryTask",
374 name="visitSummary",
375 storageClass="ExposureCatalog",
376 dimensions=("instrument", "visit",),
377 multiple=True,
378 deferLoad=True
379 )
380 goodVisits = pipeBase.connectionTypes.Output(
381 doc="Selected visits to be coadded.",
382 name="{coaddName}Visits",
383 storageClass="StructuredDataDict",
384 dimensions=("instrument", "tract", "patch", "skymap", "band"),
385 )
386
387
388class BestSeeingSelectVisitsConfig(pipeBase.PipelineTaskConfig,
389 pipelineConnections=BestSeeingSelectVisitsConnections):
390 nVisitsMax = pexConfig.RangeField(
391 dtype=int,
392 doc="Maximum number of visits to select",
393 default=12,
394 min=0
395 )
396 maxPsfFwhm = pexConfig.Field(
397 dtype=float,
398 doc="Maximum PSF FWHM (in arcseconds) to select",
399 default=1.5,
400 optional=True
401 )
402 minPsfFwhm = pexConfig.Field(
403 dtype=float,
404 doc="Minimum PSF FWHM (in arcseconds) to select",
405 default=0.,
406 optional=True
407 )
408 doConfirmOverlap = pexConfig.Field(
409 dtype=bool,
410 doc="Do remove visits that do not actually overlap the patch?",
411 default=True,
412 )
413 minMJD = pexConfig.Field(
414 dtype=float,
415 doc="Minimum visit MJD to select",
416 default=None,
417 optional=True
418 )
419 maxMJD = pexConfig.Field(
420 dtype=float,
421 doc="Maximum visit MJD to select",
422 default=None,
423 optional=True
424 )
425
426
427class BestSeeingSelectVisitsTask(pipeBase.PipelineTask):
428 """Select up to a maximum number of the best-seeing visits.
429
430 Don't exceed the FWHM range specified by configs min(max)PsfFwhm.
431 This Task is a port of the Gen2 image-selector used in the AP pipeline:
432 BestSeeingSelectImagesTask. This Task selects full visits based on the
433 average PSF of the entire visit.
434 """
435
436 ConfigClass = BestSeeingSelectVisitsConfig
437 _DefaultName = 'bestSeeingSelectVisits'
438
439 def runQuantum(self, butlerQC, inputRefs, outputRefs):
440 inputs = butlerQC.get(inputRefs)
441 quantumDataId = butlerQC.quantum.dataId
442 outputs = self.run(**inputs, dataId=quantumDataId)
443 butlerQC.put(outputs, outputRefs)
444
445 def run(self, visitSummaries, skyMap, dataId):
446 """Run task.
447
448 Parameters
449 ----------
450 visitSummary : `list` [`lsst.pipe.base.connections.DeferredDatasetRef`]
451 List of `lsst.pipe.base.connections.DeferredDatasetRef` of
452 visitSummary tables of type `lsst.afw.table.ExposureCatalog`.
453 skyMap : `lsst.skyMap.SkyMap`
454 SkyMap for checking visits overlap patch.
455 dataId : `dict` of dataId keys
456 For retrieving patch info for checking visits overlap patch.
457
458 Returns
459 -------
460 result : `lsst.pipe.base.Struct`
461 Results as a struct with attributes:
462
463 ``goodVisits``
464 A `dict` with selected visit ids as keys,
465 so that it can be be saved as a StructuredDataDict.
466 StructuredDataList's are currently limited.
467 """
468 if self.config.doConfirmOverlap:
469 patchPolygon = self.makePatchPolygon(skyMap, dataId)
470
471 inputVisits = [visitSummary.ref.dataId['visit'] for visitSummary in visitSummaries]
472 fwhmSizes = []
473 visits = []
474 for visit, visitSummary in zip(inputVisits, visitSummaries):
475 # read in one-by-one and only once. There may be hundreds
476 visitSummary = visitSummary.get()
477
478 # mjd is guaranteed to be the same for every detector in the
479 # visitSummary.
480 mjd = visitSummary[0].getVisitInfo().getDate().get(system=DateTime.MJD)
481
482 pixToArcseconds = [vs.getWcs().getPixelScale(vs.getBBox().getCenter()).asArcseconds()
483 for vs in visitSummary if vs.getWcs()]
484 # psfSigma is PSF model determinant radius at chip center in pixels
485 psfSigmas = np.array([vs['psfSigma'] for vs in visitSummary if vs.getWcs()])
486 fwhm = np.nanmean(psfSigmas * pixToArcseconds) * np.sqrt(8.*np.log(2.))
487
488 if self.config.maxPsfFwhm and fwhm > self.config.maxPsfFwhm:
489 continue
490 if self.config.minPsfFwhm and fwhm < self.config.minPsfFwhm:
491 continue
492 if self.config.minMJD and mjd < self.config.minMJD:
493 self.log.debug('MJD %f earlier than %.2f; rejecting', mjd, self.config.minMJD)
494 continue
495 if self.config.maxMJD and mjd > self.config.maxMJD:
496 self.log.debug('MJD %f later than %.2f; rejecting', mjd, self.config.maxMJD)
497 continue
498 if self.config.doConfirmOverlap and not self.doesIntersectPolygon(visitSummary, patchPolygon):
499 continue
500
501 fwhmSizes.append(fwhm)
502 visits.append(visit)
503
504 sortedVisits = [ind for (_, ind) in sorted(zip(fwhmSizes, visits))]
505 output = sortedVisits[:self.config.nVisitsMax]
506 self.log.info("%d images selected with FWHM range of %d--%d arcseconds",
507 len(output), fwhmSizes[visits.index(output[0])], fwhmSizes[visits.index(output[-1])])
508
509 # In order to store as a StructuredDataDict, convert list to dict
510 goodVisits = {key: True for key in output}
511 return pipeBase.Struct(goodVisits=goodVisits)
512
513 def makePatchPolygon(self, skyMap, dataId):
514 """Return True if sky polygon overlaps visit.
515
516 Parameters
517 ----------
519 Exposure catalog with per-detector geometry.
520 dataId : `dict` of dataId keys
521 For retrieving patch info.
522
523 Returns
524 -------
525 result : `lsst.sphgeom.ConvexPolygon.convexHull`
526 Polygon of patch's outer bbox.
527 """
528 wcs = skyMap[dataId['tract']].getWcs()
529 bbox = skyMap[dataId['tract']][dataId['patch']].getOuterBBox()
530 sphCorners = wcs.pixelToSky(lsst.geom.Box2D(bbox).getCorners())
531 result = lsst.sphgeom.ConvexPolygon.convexHull([coord.getVector() for coord in sphCorners])
532 return result
533
534 def doesIntersectPolygon(self, visitSummary, polygon):
535 """Return True if sky polygon overlaps visit.
536
537 Parameters
538 ----------
539 visitSummary : `lsst.afw.table.ExposureCatalog`
540 Exposure catalog with per-detector geometry.
541 polygon :` lsst.sphgeom.ConvexPolygon.convexHull`
542 Polygon to check overlap.
543
544 Returns
545 -------
546 doesIntersect : `bool`
547 True if the visit overlaps the polygon.
548 """
549 doesIntersect = False
550 for detectorSummary in visitSummary:
551 if (np.all(np.isfinite(detectorSummary['raCorners']))
552 and np.all(np.isfinite(detectorSummary['decCorners']))):
553 corners = [lsst.geom.SpherePoint(ra, decl, units=lsst.geom.degrees).getVector()
554 for (ra, decl) in
555 zip(detectorSummary['raCorners'], detectorSummary['decCorners'])]
556 detectorPolygon = lsst.sphgeom.ConvexPolygon.convexHull(corners)
557 if detectorPolygon.intersects(polygon):
558 doesIntersect = True
559 break
560 return doesIntersect
561
562
563class BestSeeingQuantileSelectVisitsConfig(pipeBase.PipelineTaskConfig,
564 pipelineConnections=BestSeeingSelectVisitsConnections):
565 qMin = pexConfig.RangeField(
566 doc="Lower bound of quantile range to select. Sorts visits by seeing from narrow to wide, "
567 "and select those in the interquantile range (qMin, qMax). Set qMin to 0 for Best Seeing. "
568 "This config should be changed from zero only for exploratory diffIm testing.",
569 dtype=float,
570 default=0,
571 min=0,
572 max=1,
573 )
574 qMax = pexConfig.RangeField(
575 doc="Upper bound of quantile range to select. Sorts visits by seeing from narrow to wide, "
576 "and select those in the interquantile range (qMin, qMax). Set qMax to 1 for Worst Seeing.",
577 dtype=float,
578 default=0.33,
579 min=0,
580 max=1,
581 )
582 nVisitsMin = pexConfig.Field(
583 doc="At least this number of visits selected and supercedes quantile. For example, if 10 visits "
584 "cover this patch, qMin=0.33, and nVisitsMin=5, the best 5 visits will be selected.",
585 dtype=int,
586 default=6,
587 )
588 doConfirmOverlap = pexConfig.Field(
589 dtype=bool,
590 doc="Do remove visits that do not actually overlap the patch?",
591 default=True,
592 )
593 minMJD = pexConfig.Field(
594 dtype=float,
595 doc="Minimum visit MJD to select",
596 default=None,
597 optional=True
598 )
599 maxMJD = pexConfig.Field(
600 dtype=float,
601 doc="Maximum visit MJD to select",
602 default=None,
603 optional=True
604 )
605
606
607class BestSeeingQuantileSelectVisitsTask(BestSeeingSelectVisitsTask):
608 """Select a quantile of the best-seeing visits.
609
610 Selects the best (for example, third) full visits based on the average
611 PSF width in the entire visit. It can also be used for difference imaging
612 experiments that require templates with the worst seeing visits.
613 For example, selecting the worst third can be acheived by
614 changing the config parameters qMin to 0.66 and qMax to 1.
615 """
616 ConfigClass = BestSeeingQuantileSelectVisitsConfig
617 _DefaultName = 'bestSeeingQuantileSelectVisits'
618
619 @utils.inheritDoc(BestSeeingSelectVisitsTask)
620 def run(self, visitSummaries, skyMap, dataId):
621 if self.config.doConfirmOverlap:
622 patchPolygon = self.makePatchPolygon(skyMap, dataId)
623 visits = np.array([visitSummary.ref.dataId['visit'] for visitSummary in visitSummaries])
624 radius = np.empty(len(visits))
625 intersects = np.full(len(visits), True)
626 for i, visitSummary in enumerate(visitSummaries):
627 # read in one-by-one and only once. There may be hundreds
628 visitSummary = visitSummary.get()
629 # psfSigma is PSF model determinant radius at chip center in pixels
630 psfSigma = np.nanmedian([vs['psfSigma'] for vs in visitSummary])
631 radius[i] = psfSigma
632 if self.config.doConfirmOverlap:
633 intersects[i] = self.doesIntersectPolygon(visitSummary, patchPolygon)
634 if self.config.minMJD or self.config.maxMJD:
635 # mjd is guaranteed to be the same for every detector in the
636 # visitSummary.
637 mjd = visitSummary[0].getVisitInfo().getDate().get(system=DateTime.MJD)
638 aboveMin = mjd > self.config.minMJD if self.config.minMJD else True
639 belowMax = mjd < self.config.maxMJD if self.config.maxMJD else True
640 intersects[i] = intersects[i] and aboveMin and belowMax
641
642 sortedVisits = [v for rad, v in sorted(zip(radius[intersects], visits[intersects]))]
643 lowerBound = min(int(np.round(self.config.qMin*len(visits[intersects]))),
644 max(0, len(visits[intersects]) - self.config.nVisitsMin))
645 upperBound = max(int(np.round(self.config.qMax*len(visits[intersects]))), self.config.nVisitsMin)
646
647 # In order to store as a StructuredDataDict, convert list to dict
648 goodVisits = {int(visit): True for visit in sortedVisits[lowerBound:upperBound]}
649 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
def __init__(self, dataId, coordList)
Definition: selectImages.py:74
def __init__(self, dataRef, wcs, bbox)
def getValidImageCorners(self, imageWcs, imageBox, patchPoly, dataId=None)
ConvexPolygon is a closed convex polygon on the unit sphere.
Definition: ConvexPolygon.h:57
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...
Definition: ConvexPolygon.h:65
daf::base::PropertyList * list
Definition: fits.cc:928
daf::base::PropertySet * set
Definition: fits.cc:927
bool isValid
Definition: fits.cc:400