22__all__ = [
"BaseSelectImagesTask",
"BaseExposureInfo",
"WcsSelectImagesTask",
"PsfWcsSelectImagesTask",
23 "DatabaseSelectImagesConfig",
"BestSeeingSelectVisitsTask",
24 "BestSeeingQuantileSelectVisitsTask"]
34from lsst.utils.timer
import timeMethod
38 """Check if the visitSummary meets minimum values in visitSummaryMinValues.
44 visitSummary : `lsst.afw.table.ExposureCatalog`
45 Exposure catalog with per-detector summary information.
46 visitSummaryMinValues : `dict`
47 Dictionary with column names as keys and minimum allowed values as values.
48 logger : `lsst.log.Logger`
49 Logger to log debug and warning messages.
54 True if all columns in visitSummary meet the minimum values specified
55 in visitSummaryMinValues, False otherwise.
57 for columnName, minThreshold
in visitSummaryMinValues.items():
59 values = np.asarray([vs[columnName]
for vs
in visitSummary
if vs.getWcs()])
60 meanValue = np.nanmean(values)
61 if meanValue < minThreshold:
62 if logger
is not None:
63 logger.info(f
'Rejecting visit {visit}: mean {columnName} ({meanValue:.3f}) '
64 f
'is below the minimum threshold ({minThreshold:.3f}).')
70 """Base configuration for subclasses of BaseSelectImagesTask that use a
74 host = pexConfig.Field(
75 doc=
"Database server host name",
78 port = pexConfig.Field(
79 doc=
"Database server port",
82 database = pexConfig.Field(
83 doc=
"Name of database",
86 maxExposures = pexConfig.Field(
87 doc=
"maximum exposures to select; intended for debugging; ignored if None",
94 """Data about a selected exposure.
99 Data ID keys of exposure.
100 coordList : `list` [`lsst.afw.geom.SpherePoint`]
101 ICRS coordinates of the corners of the exposure
102 plus any others items that are desired.
106 super(BaseExposureInfo, self).
__init__(dataId=dataId, coordList=coordList)
110 """Base task for selecting images suitable for coaddition.
113 ConfigClass = pexConfig.Config
114 _DefaultName =
"selectImages"
118 """Select images suitable for coaddition in a particular region.
122 coordList : `list` [`lsst.geom.SpherePoint`] or `None`
123 List of coordinates defining region of interest; if `None`, then
124 select all images subclasses may add additional keyword arguments,
129 result : `pipeBase.Struct`
130 Results as a struct with attributes:
133 A list of exposure information objects (subclasses of
134 BaseExposureInfo), which have at least the following fields:
135 - dataId: Data ID dictionary (`dict`).
136 - coordList: ICRS coordinates of the corners of the exposure.
137 (`list` [`lsst.geom.SpherePoint`])
139 raise NotImplementedError()
143 """Extract the keys and values from a list of dataIds.
145 The input dataList is a list of objects that have 'dataId' members.
146 This allows it to be used for both a list of data references and a
147 list of ExposureInfo.
162 Raised if DataId keys are inconsistent.
164 assert len(dataList) > 0
166 keys = sorted(dataList[0].dataId.keys())
169 for data
in dataList:
170 thisKeys = set(data.dataId.keys())
171 if thisKeys != keySet:
172 raise RuntimeError(
"DataId keys inconsistent: %s vs %s" % (keySet, thisKeys))
173 values.append(tuple(data.dataId[k]
for k
in keys))
178 """A container for data to be passed to the WcsSelectImagesTask.
184 wcs : `lsst.afw.geom.SkyWcs`
185 Coordinate system definition (wcs).
186 bbox : `lsst.geom.box.Box2I`
187 Integer bounding box for image.
191 super(SelectStruct, self).
__init__(dataRef=dataRef, wcs=wcs, bbox=bbox)
195 excludeDetectors = pexConfig.ListField(
198 doc=
"Detectors to exclude from selection.",
204 """Select images using their Wcs.
206 We use the "convexHull" method of lsst.sphgeom.ConvexPolygon to define
207 polygons on the celestial sphere, and test the polygon of the
208 patch for overlap with the polygon of the image.
210 We use "convexHull" instead of generating a ConvexPolygon
211 directly because the standard for the inputs to ConvexPolygon
212 are pretty high and we don't want to be responsible for reaching them.
215 ConfigClass = WcsSelectImagesConfig
216 _DefaultName =
"WcsSelectImages"
218 def run(self, wcsList, bboxList, coordList, dataIds=None, **kwargs):
219 """Return indices of provided lists that meet the selection criteria.
223 wcsList : `list` [`lsst.afw.geom.SkyWcs`]
224 Specifying the WCS's of the input ccds to be selected.
225 bboxList : `list` [`lsst.geom.Box2I`]
226 Specifying the bounding boxes of the input ccds to be selected.
227 coordList : `list` [`lsst.geom.SpherePoint`]
228 ICRS coordinates specifying boundary of the patch.
229 dataIds : iterable [`lsst.daf.butler.dataId`] or `None`, optional
230 An iterable object of dataIds which point to reference catalogs.
232 Additional keyword arguments.
236 result : `list` [`int`]
237 The indices of selected ccds.
240 dataIds = [
None] * len(wcsList)
241 patchVertices = [coord.getVector()
for coord
in coordList]
244 for i, (imageWcs, imageBox, dataId)
in enumerate(zip(wcsList, bboxList, dataIds)):
245 if dataId
and (
"detector" in dataId)
and (dataId[
"detector"]
in self.config.excludeDetectors):
246 self.log.info(
"De-selecting exposure %s because detector %s is exluded from processing",
247 dataId, dataId[
"detector"])
248 elif imageWcs
is None:
249 self.log.info(
"De-selecting exposure %s: Exposure has no WCS.", dataId)
257 """Return corners or `None` if bad.
263 patchPoly : `Unknown`
267 imageCorners = [imageWcs.pixelToSky(pix)
for pix
in geom.Box2D(imageBox).getCorners()]
268 except (pexExceptions.DomainError, pexExceptions.RuntimeError)
as e:
270 self.log.debug(
"WCS error in testing calexp %s (%s): deselecting", dataId, e)
274 if imagePoly
is None:
275 self.log.debug(
"Unable to create polygon from image %s: deselecting", dataId)
278 if patchPoly.intersects(imagePoly):
280 self.log.info(
"Selecting calexp %s", dataId)
287 dimensions=(
"tract",
"patch",
"skymap",
"instrument",
"visit"),
288 defaultTemplates={
"coaddName":
"deep"}):
292class PsfWcsSelectImagesConfig(pipeBase.PipelineTaskConfig,
293 pipelineConnections=PsfWcsSelectImagesConnections):
294 maxEllipResidual = pexConfig.Field(
295 doc=
"Maximum median ellipticity residual",
300 maxSizeScatter = pexConfig.Field(
301 doc=
"Maximum scatter in the size residuals",
305 maxScaledSizeScatter = pexConfig.Field(
306 doc=
"Maximum scatter in the size residuals, scaled by the median size",
311 maxPsfTraceRadiusDelta = pexConfig.Field(
312 doc=
"Maximum delta (max - min) of model PSF trace radius values evaluated on a grid on "
313 "the unmasked detector pixels (pixel).",
318 maxPsfApFluxDelta = pexConfig.Field(
319 doc=
"Maximum delta (max - min) of model PSF aperture flux (with aperture radius of "
320 "max(2, 3*psfSigma)) values evaluated on a grid on the unmasked detector pixels (based "
321 "on a normalized-to-one flux).",
326 maxPsfApCorrSigmaScaledDelta = pexConfig.Field(
327 doc=
"Maximum delta (max - min) of model PSF aperture correction values evaluated on a grid "
328 "on the unmasked detector pixels scaled (divided) by the measured model psfSigma.",
333 minNPsfStarPerBand = pexConfig.DictField(
345 doc=
"Minimum number of PSF stars for the final PSF model to be considered "
346 "well-constrained and suitible for inclusion in the coadd. This number should "
347 "take into consideration the spatial order used for the PSF model. If the current "
348 "band for the exposure is not included as a key in this dict, the value associated "
349 "with the \"fallback\" key will be used.",
351 excludeDetectors = pexConfig.ListField(
354 doc=
"Detectors to exclude from selection.",
360 if "fallback" not in self.minNPsfStarPerBand:
361 msg = (
"Must include a \"fallback\" key in the config.minNPsfStarPerBand config dict. "
362 f
"It is currenly: {self.minNPsfStarPerBand}.")
363 raise pexConfig.FieldValidationError(self.__class__.minNPsfStarPerBand, self, msg)
367 """Select images using their Wcs and cuts on the PSF properties.
369 The PSF quality criteria are based on the size and ellipticity
370 residuals from the adaptive second moments of the star and the PSF.
373 - the median of the ellipticty residuals.
374 - the robust scatter of the size residuals (using the median absolute
376 - the robust scatter of the size residuals scaled by the square of
380 ConfigClass = PsfWcsSelectImagesConfig
381 _DefaultName =
"PsfWcsSelectImages"
383 def run(self, wcsList, bboxList, coordList, visitSummary, dataIds=None, **kwargs):
384 """Return indices of provided lists that meet the selection criteria.
388 wcsList : `list` [`lsst.afw.geom.SkyWcs`]
389 Specifying the WCS's of the input ccds to be selected.
390 bboxList : `list` [`lsst.geom.Box2I`]
391 Specifying the bounding boxes of the input ccds to be selected.
392 coordList : `list` [`lsst.geom.SpherePoint`]
393 ICRS coordinates specifying boundary of the patch.
394 visitSummary : `list` [`lsst.afw.table.ExposureCatalog`]
395 containing the PSF shape information for the input ccds to be
397 dataIds : iterable [`lsst.daf.butler.dataId`] or `None`, optional
398 An iterable object of dataIds which point to reference catalogs.
400 Additional keyword arguments.
404 goodPsf : `list` [`int`]
405 The indices of selected ccds.
407 goodWcs = super(PsfWcsSelectImagesTask, self).run(wcsList=wcsList, bboxList=bboxList,
408 coordList=coordList, dataIds=dataIds)
412 for i, dataId
in enumerate(dataIds):
415 if self.
isValid(visitSummary, dataId[
"detector"]):
421 """Should this ccd be selected based on its PSF shape information.
425 visitSummary : `lsst.afw.table.ExposureCatalog`
426 Exposure catalog with per-detector summary information.
435 row = visitSummary.find(detectorId)
438 self.log.warning(
"Removing detector %d because summary stats not available.", detectorId)
442 if band
in self.config.minNPsfStarPerBand:
443 minNPsfStar = self.config.minNPsfStarPerBand[band]
445 minNPsfStar = self.config.minNPsfStarPerBand[
"fallback"]
446 nPsfStar = row[
"nPsfStar"]
447 medianE = np.sqrt(row[
"psfStarDeltaE1Median"]**2. + row[
"psfStarDeltaE2Median"]**2.)
448 scatterSize = row[
"psfStarDeltaSizeScatter"]
449 scaledScatterSize = row[
"psfStarScaledDeltaSizeScatter"]
450 psfTraceRadiusDelta = row[
"psfTraceRadiusDelta"]
451 psfApFluxDelta = row[
"psfApFluxDelta"]
452 psfApCorrSigmaScaledDelta = row[
"psfApCorrSigmaScaledDelta"]
455 if self.config.maxEllipResidual
and not (medianE <= self.config.maxEllipResidual):
456 self.log.info(
"Removing visit %d detector %d because median e residual too large: %f vs %f",
457 row[
"visit"], detectorId, medianE, self.config.maxEllipResidual)
459 elif self.config.maxSizeScatter
and not (scatterSize <= self.config.maxSizeScatter):
460 self.log.info(
"Removing visit %d detector %d because size scatter too large: %f vs %f",
461 row[
"visit"], detectorId, scatterSize, self.config.maxSizeScatter)
463 elif self.config.maxScaledSizeScatter
and not (scaledScatterSize <= self.config.maxScaledSizeScatter):
464 self.log.info(
"Removing visit %d detector %d because scaled size scatter too large: %f vs %f",
465 row[
"visit"], detectorId, scaledScatterSize, self.config.maxScaledSizeScatter)
468 self.config.maxPsfTraceRadiusDelta
is not None
469 and not (psfTraceRadiusDelta <= self.config.maxPsfTraceRadiusDelta)
472 "Removing visit %d detector %d because max-min delta of model PSF trace radius values "
473 "across the unmasked detector pixels is not finite or too large: %.3f vs %.3f (pixels)",
474 row[
"visit"], detectorId, psfTraceRadiusDelta, self.config.maxPsfTraceRadiusDelta
478 self.config.maxPsfApFluxDelta
is not None
479 and not (psfApFluxDelta <= self.config.maxPsfApFluxDelta)
482 "Removing visit %d detector %d because max-min delta of model PSF aperture flux values "
483 "across the unmasked detector pixels is not finite or too large: %.3f vs %.3f (pixels)",
484 row[
"visit"], detectorId, psfApFluxDelta, self.config.maxPsfApFluxDelta
488 self.config.maxPsfApCorrSigmaScaledDelta
is not None
489 and not (psfApCorrSigmaScaledDelta <= self.config.maxPsfApCorrSigmaScaledDelta)
492 "Removing visit %d detector %d because max-min delta of the model PSF apterture correction "
493 "values scaled (divided) by the psfSigma across the unmasked detector pixels is not "
494 "finite or too large: %.3f vs %.3f (fatcor)",
495 row[
"visit"], detectorId, psfApCorrSigmaScaledDelta, self.config.maxPsfApCorrSigmaScaledDelta
498 elif minNPsfStar
and (nPsfStar < minNPsfStar):
500 "Removing visit %d detector %d because the PSF model had too few stars: %d vs %d",
501 row[
"visit"], detectorId, nPsfStar, minNPsfStar
509 dimensions=(
"tract",
"patch",
"skymap",
"band",
"instrument"),
510 defaultTemplates={
"coaddName":
"goodSeeing",
512 skyMap = pipeBase.connectionTypes.Input(
513 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
514 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
515 storageClass=
"SkyMap",
516 dimensions=(
"skymap",),
518 visitSummaries = pipeBase.connectionTypes.Input(
519 doc=
"Per-visit consolidated exposure metadata",
520 name=
"finalVisitSummary",
521 storageClass=
"ExposureCatalog",
522 dimensions=(
"instrument",
"visit",),
526 goodVisits = pipeBase.connectionTypes.Output(
527 doc=
"Selected visits to be coadded.",
528 name=
"{coaddName}Visits",
529 storageClass=
"StructuredDataDict",
530 dimensions=(
"instrument",
"tract",
"patch",
"skymap",
"band"),
534class BestSeeingSelectVisitsConfig(pipeBase.PipelineTaskConfig,
535 pipelineConnections=BestSeeingSelectVisitsConnections):
536 nVisitsMax = pexConfig.RangeField(
538 doc=
"Maximum number of visits to select; use -1 to select all.",
542 maxPsfFwhm = pexConfig.Field(
544 doc=
"Maximum PSF FWHM (in arcseconds) to select",
548 minPsfFwhm = pexConfig.Field(
550 doc=
"Minimum PSF FWHM (in arcseconds) to select",
554 doConfirmOverlap = pexConfig.Field(
556 doc=
"Do remove visits that do not actually overlap the patch?",
559 minMJD = pexConfig.Field(
561 doc=
"Minimum visit MJD to select",
565 maxMJD = pexConfig.Field(
567 doc=
"Maximum visit MJD to select",
571 visitSummaryMinValues = pexConfig.DictField(
574 doc=(
"Optional thresholds for visit selection. Keys are visit_summary column names"
575 "(e.g. 'effTimeZeroPointScale'), and values are minimum allowed values."),
581class BestSeeingSelectVisitsTask(pipeBase.PipelineTask):
582 """Select up to a maximum number of the best-seeing visits.
584 Don't exceed the FWHM range specified by configs min(max)PsfFwhm.
585 This Task is a port of the Gen2 image-selector used in the AP pipeline:
586 BestSeeingSelectImagesTask. This Task selects full visits based on the
587 average PSF of the entire visit.
590 ConfigClass = BestSeeingSelectVisitsConfig
591 _DefaultName =
'bestSeeingSelectVisits'
593 def runQuantum(self, butlerQC, inputRefs, outputRefs):
594 inputs = butlerQC.get(inputRefs)
595 quantumDataId = butlerQC.quantum.dataId
596 outputs = self.run(**inputs, dataId=quantumDataId)
597 butlerQC.put(outputs, outputRefs)
599 def run(self, visitSummaries, skyMap, dataId):
604 visitSummary : `list` [`lsst.pipe.base.connections.DeferredDatasetRef`]
605 List of `lsst.pipe.base.connections.DeferredDatasetRef` of
606 visitSummary tables of type `lsst.afw.table.ExposureCatalog`.
607 skyMap : `lsst.skyMap.SkyMap`
608 SkyMap for checking visits overlap patch.
609 dataId : `dict` of dataId keys
610 For retrieving patch info for checking visits overlap patch.
614 result : `lsst.pipe.base.Struct`
615 Results as a struct with attributes:
618 A `dict` with selected visit ids as keys,
619 so that it can be be saved as a StructuredDataDict.
620 StructuredDataList's are currently limited.
622 if self.config.doConfirmOverlap:
623 patchPolygon = self.makePatchPolygon(skyMap, dataId)
625 inputVisits = [visitSummary.ref.dataId[
'visit']
for visitSummary
in visitSummaries]
628 for visit, visitSummary
in zip(inputVisits, visitSummaries):
630 visitSummary = visitSummary.get()
634 mjd = visitSummary[0].getVisitInfo().getDate().get(system=DateTime.MJD)
636 pixelScales = np.array([vs[
'pixelScale']
for vs
in visitSummary
if vs.getWcs()])
638 psfSigmas = np.array([vs[
'psfSigma']
for vs
in visitSummary
if vs.getWcs()])
639 fwhm = np.nanmean(psfSigmas * pixelScales) * np.sqrt(8.*np.log(2.))
641 if self.config.maxPsfFwhm
and fwhm > self.config.maxPsfFwhm:
643 if self.config.minPsfFwhm
and fwhm < self.config.minPsfFwhm:
645 if self.config.minMJD
and mjd < self.config.minMJD:
646 self.log.debug(
'MJD %f earlier than %.2f; rejecting', mjd, self.config.minMJD)
648 if self.config.maxMJD
and mjd > self.config.maxMJD:
649 self.log.debug(
'MJD %f later than %.2f; rejecting', mjd, self.config.maxMJD)
651 if self.config.doConfirmOverlap
and not self.doesIntersectPolygon(visitSummary, patchPolygon):
653 if (self.config.visitSummaryMinValues
658 fwhmSizes.append(fwhm)
661 sortedVisits = [ind
for (_, ind)
in sorted(zip(fwhmSizes, visits))]
662 if self.config.nVisitsMax < 0:
663 output = sortedVisits
665 output = sortedVisits[:self.config.nVisitsMax]
668 self.log.info(
"All images rejected in BestSeeingSelectVisitsTask.")
669 raise pipeBase.NoWorkFound(f
"No good images found for {dataId}")
672 "%d images selected with FWHM range of %f--%f arcseconds",
674 fwhmSizes[visits.index(output[0])],
675 fwhmSizes[visits.index(output[-1])],
679 goodVisits = {key:
True for key
in output}
680 return pipeBase.Struct(goodVisits=goodVisits)
682 def makePatchPolygon(self, skyMap, dataId):
683 """Return True if sky polygon overlaps visit.
687 skyMap : `lsst.afw.table.ExposureCatalog`
688 Exposure catalog with per-detector geometry.
689 dataId : `dict` of dataId keys
690 For retrieving patch info.
694 result : `lsst.sphgeom.ConvexPolygon.convexHull`
695 Polygon of patch's outer bbox.
697 wcs = skyMap[dataId[
'tract']].getWcs()
698 bbox = skyMap[dataId[
'tract']][dataId[
'patch']].getOuterBBox()
703 def doesIntersectPolygon(self, visitSummary, polygon):
704 """Return True if sky polygon overlaps visit.
708 visitSummary : `lsst.afw.table.ExposureCatalog`
709 Exposure catalog with per-detector geometry.
710 polygon :` lsst.sphgeom.ConvexPolygon.convexHull`
711 Polygon to check overlap.
715 doesIntersect : `bool`
716 True if the visit overlaps the polygon.
718 doesIntersect =
False
719 for detectorSummary
in visitSummary:
720 if (np.all(np.isfinite(detectorSummary[
'raCorners']))
721 and np.all(np.isfinite(detectorSummary[
'decCorners']))):
724 zip(detectorSummary[
'raCorners'], detectorSummary[
'decCorners'])]
726 if detectorPolygon.intersects(polygon):
732class BestSeeingQuantileSelectVisitsConfig(pipeBase.PipelineTaskConfig,
733 pipelineConnections=BestSeeingSelectVisitsConnections):
734 qMin = pexConfig.RangeField(
735 doc=
"Lower bound of quantile range to select. Sorts visits by seeing from narrow to wide, "
736 "and select those in the interquantile range (qMin, qMax). Set qMin to 0 for Best Seeing. "
737 "This config should be changed from zero only for exploratory diffIm testing.",
743 qMax = pexConfig.RangeField(
744 doc=
"Upper bound of quantile range to select. Sorts visits by seeing from narrow to wide, "
745 "and select those in the interquantile range (qMin, qMax). Set qMax to 1 for Worst Seeing.",
751 nVisitsMin = pexConfig.Field(
752 doc=
"The minimum number of visits to select, if qMin and qMax alone would have "
753 "selected fewer. In regimes with many visits, at least this number of visits will be "
754 "selected, superceding quantile when necessary. "
755 "For example, if 10 visits cover this patch, qMin=0, qMax=0.33, and nVisitsMin=5, "
756 "the best 5 visits will be selected, even though 5 > 0.33*10. "
757 "In regimes with few visits, all available visits will be selected. "
758 "For example, if 2 visits cover this patch and nVisitsMin=12, "
759 "both visits will be selected regardless of qMin and qMax.",
763 doConfirmOverlap = pexConfig.Field(
765 doc=
"Do remove visits that do not actually overlap the patch?",
768 minMJD = pexConfig.Field(
770 doc=
"Minimum visit MJD to select",
774 maxMJD = pexConfig.Field(
776 doc=
"Maximum visit MJD to select",
780 visitSummaryMinValues = pexConfig.DictField(
783 doc=(
"Optional thresholds for visit selection. Keys are visit_summary column names"
784 "(e.g. 'effTimeZeroPointScale'), and values are minimum allowed values."),
790class BestSeeingQuantileSelectVisitsTask(BestSeeingSelectVisitsTask):
791 """Select a quantile of the best-seeing visits.
793 Selects the best (for example, third) full visits based on the average
794 PSF width in the entire visit. It can also be used for difference imaging
795 experiments that require templates with the worst seeing visits.
796 For example, selecting the worst third can be acheived by
797 changing the config parameters qMin to 0.66 and qMax to 1.
799 ConfigClass = BestSeeingQuantileSelectVisitsConfig
800 _DefaultName =
'bestSeeingQuantileSelectVisits'
802 def run(self, visitSummaries, skyMap, dataId):
803 if self.config.doConfirmOverlap:
804 patchPolygon = self.makePatchPolygon(skyMap, dataId)
805 visits = np.array([visitSummary.ref.dataId[
'visit']
for visitSummary
in visitSummaries])
806 radius = np.empty(len(visits))
807 intersects = np.full(len(visits),
True)
808 for i, visitSummary
in enumerate(visitSummaries):
810 visitSummary = visitSummary.get()
812 psfSigma = np.nanmedian([vs[
'psfSigma']
for vs
in visitSummary])
814 if self.config.doConfirmOverlap:
815 intersects[i] = self.doesIntersectPolygon(visitSummary, patchPolygon)
816 if self.config.minMJD
or self.config.maxMJD:
819 mjd = visitSummary[0].getVisitInfo().getDate().get(system=DateTime.MJD)
820 aboveMin = mjd > self.config.minMJD
if self.config.minMJD
else True
821 belowMax = mjd < self.config.maxMJD
if self.config.maxMJD
else True
822 intersects[i] = intersects[i]
and aboveMin
and belowMax
823 if (self.config.visitSummaryMinValues
825 self.config.visitSummaryMinValues, self.log)):
826 intersects[i] =
False
828 sortedVisits = [v
for rad, v
in sorted(zip(radius[intersects], visits[intersects]))]
829 lowerBound = min(int(np.round(self.config.qMin*len(visits[intersects]))),
830 max(0, len(visits[intersects]) - self.config.nVisitsMin))
831 upperBound = max(int(np.round(self.config.qMax*len(visits[intersects]))), self.config.nVisitsMin)
834 goodVisits = {int(visit):
True for visit
in sortedVisits[lowerBound:upperBound]}
835 return pipeBase.Struct(goodVisits=goodVisits)
A floating-point coordinate rectangle geometry.
Point in an unspecified spherical coordinate system.
__init__(self, dataId, coordList)
run(self, wcsList, bboxList, coordList, visitSummary, dataIds=None, **kwargs)
isValid(self, visitSummary, detectorId)
__init__(self, dataRef, wcs, bbox)
run(self, wcsList, bboxList, coordList, dataIds=None, **kwargs)
getValidImageCorners(self, imageWcs, imageBox, patchPoly, dataId=None)
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...
_extractKeyValue(dataList, keys=None)
_meetsVisitSummaryMinValues(visit, visitSummary, visitSummaryMinValues, logger=None)