24import lsst.utils
as utils
31from lsst.utils.timer
import timeMethod
33__all__ = [
"BaseSelectImagesTask",
"BaseExposureInfo",
"WcsSelectImagesTask",
"PsfWcsSelectImagesTask",
34 "DatabaseSelectImagesConfig",
"BestSeeingSelectVisitsTask",
35 "BestSeeingQuantileSelectVisitsTask"]
39 """Base configuration for subclasses of BaseSelectImagesTask that use a database"""
40 host = pexConfig.Field(
41 doc=
"Database server host name",
44 port = pexConfig.Field(
45 doc=
"Database server port",
48 database = pexConfig.Field(
49 doc=
"Name of database",
52 maxExposures = pexConfig.Field(
53 doc=
"maximum exposures to select; intended for debugging; ignored if None",
60 """Data about a selected exposure
64 """Create exposure information that can be used to generate data references
66 The object has the following fields:
67 - dataId: data ID of exposure (a dict)
69 plus any others items that are desired
71 super(BaseExposureInfo, self).__init__(dataId=dataId, coordList=coordList)
75 """Base task for selecting images suitable for coaddition
77 ConfigClass = pexConfig.Config
78 _DefaultName = "selectImages"
81 def run(self, coordList):
82 """Select images suitable for coaddition in a particular region
84 @param[
in] coordList: list of coordinates defining region of interest;
if None then select all images
85 subclasses may add additional keyword arguments,
as required
87 @return a pipeBase Struct containing:
88 - exposureInfoList: a list of exposure information objects (subclasses of BaseExposureInfo),
89 which have at least the following fields:
90 - dataId: data ID dictionary
93 raise NotImplementedError()
95 def _runArgDictFromDataId(self, dataId):
96 """Extract keyword arguments for run (other than coordList) from a data ID
98 @return keyword arguments
for run (other than coordList),
as a dict
100 raise NotImplementedError()
102 def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]):
103 """Run based on a data reference
105 This delegates to run() and _runArgDictFromDataId() to do the actual
106 selection. In the event that the selectDataList
is non-empty, this will
107 be used to further restrict the selection, providing the user
with
108 additional control over the selection.
110 @param[
in] dataRef: data reference; must contain any extra keys needed by the subclass
111 @param[
in] coordList: list of coordinates defining region of interest;
if None, search the whole sky
112 @param[
in] makeDataRefList:
if True,
return dataRefList
113 @param[
in] selectDataList: List of SelectStruct
with dataRefs to consider
for selection
114 @return a pipeBase Struct containing:
115 - exposureInfoList: a list of objects derived
from ExposureInfo
116 - dataRefList: a list of data references (
None if makeDataRefList
False)
119 exposureInfoList = self.run(coordList, **runArgDict).exposureInfoList
121 if len(selectDataList) > 0
and len(exposureInfoList) > 0:
123 ccdKeys, ccdValues = _extractKeyValue(exposureInfoList)
124 inKeys, inValues = _extractKeyValue([s.dataRef
for s
in selectDataList], keys=ccdKeys)
125 inValues = set(inValues)
126 newExposureInfoList = []
127 for info, ccdVal
in zip(exposureInfoList, ccdValues):
128 if ccdVal
in inValues:
129 newExposureInfoList.append(info)
131 self.log.info(
"De-selecting exposure %s: not in selectDataList", info.dataId)
132 exposureInfoList = newExposureInfoList
135 butler = dataRef.butlerSubset.butler
136 dataRefList = [butler.dataRef(datasetType=
"calexp",
137 dataId=expInfo.dataId,
138 )
for expInfo
in exposureInfoList]
142 return pipeBase.Struct(
143 dataRefList=dataRefList,
144 exposureInfoList=exposureInfoList,
148def _extractKeyValue(dataList, keys=None):
149 """Extract the keys and values from a list of dataIds
151 The input dataList is a list of objects that have
'dataId' members.
152 This allows it to be used
for both a list of data references
and a
155 assert len(dataList) > 0
157 keys = sorted(dataList[0].dataId.keys())
160 for data
in dataList:
161 thisKeys = set(data.dataId.keys())
162 if thisKeys != keySet:
163 raise RuntimeError(
"DataId keys inconsistent: %s vs %s" % (keySet, thisKeys))
164 values.append(tuple(data.dataId[k]
for k
in keys))
169 """A container for data to be passed to the WcsSelectImagesTask"""
172 super(SelectStruct, self).
__init__(dataRef=dataRef, wcs=wcs, bbox=bbox)
176 """Select images using their Wcs
179 polygons on the celestial sphere,
and test the polygon of the
180 patch
for overlap
with the polygon of the image.
182 We use
"convexHull" instead of generating a ConvexPolygon
183 directly because the standard
for the inputs to ConvexPolygon
184 are pretty high
and we don
't want to be responsible for reaching them.
187 def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]):
188 """Select images in the selectDataList that overlap the patch
190 This method is the old entry point
for the Gen2 commandline tasks
and drivers
191 Will be deprecated
in v22.
193 @param dataRef: Data reference
for coadd/tempExp (
with tract, patch)
195 @param makeDataRefList: Construct a list of data references?
196 @param selectDataList: List of SelectStruct, to consider
for selection
199 exposureInfoList = []
201 patchVertices = [coord.getVector() for coord
in coordList]
204 for data
in selectDataList:
205 dataRef = data.dataRef
211 dataRefList.append(dataRef)
214 return pipeBase.Struct(
215 dataRefList=dataRefList
if makeDataRefList
else None,
216 exposureInfoList=exposureInfoList,
219 def run(self, wcsList, bboxList, coordList, dataIds=None, **kwargs):
220 """Return indices of provided lists that meet the selection criteria
225 specifying the WCS's of the input ccds to be selected
227 specifying the bounding boxes of the input ccds to be selected
229 ICRS coordinates specifying boundary of the patch.
233 result: `list` of `int`
234 of indices of selected ccds
237 dataIds = [
None] * len(wcsList)
238 patchVertices = [coord.getVector()
for coord
in coordList]
241 for i, (imageWcs, imageBox, dataId)
in enumerate(zip(wcsList, bboxList, dataIds)):
248 "Return corners or None if bad"
250 imageCorners = [imageWcs.pixelToSky(pix)
for pix
in geom.Box2D(imageBox).getCorners()]
251 except (pexExceptions.DomainError, pexExceptions.RuntimeError)
as e:
253 self.log.debug(
"WCS error in testing calexp %s (%s): deselecting", dataId, e)
257 if imagePoly
is None:
258 self.log.debug(
"Unable to create polygon from image %s: deselecting", dataId)
261 if patchPoly.intersects(imagePoly):
263 self.log.info(
"Selecting calexp %s", dataId)
268 "Return median absolute deviation scaled to normally distributed data"
269 return 1.4826*np.median(np.abs(array - np.median(array)))
273 dimensions=(
"tract",
"patch",
"skymap",
"instrument",
"visit"),
274 defaultTemplates={
"coaddName":
"deep"}):
278class PsfWcsSelectImagesConfig(pipeBase.PipelineTaskConfig,
279 pipelineConnections=PsfWcsSelectImagesConnections):
280 maxEllipResidual = pexConfig.Field(
281 doc=
"Maximum median ellipticity residual",
286 maxSizeScatter = pexConfig.Field(
287 doc=
"Maximum scatter in the size residuals",
291 maxScaledSizeScatter = pexConfig.Field(
292 doc=
"Maximum scatter in the size residuals, scaled by the median size",
297 starSelection = pexConfig.Field(
298 doc=
"select star with this field",
300 default=
'calib_psf_used',
301 deprecated=(
'This field has been moved to ComputeExposureSummaryStatsTask and '
302 'will be removed after v24.')
304 starShape = pexConfig.Field(
305 doc=
"name of star shape",
307 default=
'base_SdssShape',
308 deprecated=(
'This field has been moved to ComputeExposureSummaryStatsTask and '
309 'will be removed after v24.')
311 psfShape = pexConfig.Field(
312 doc=
"name of psf shape",
314 default=
'base_SdssShape_psf',
315 deprecated=(
'This field has been moved to ComputeExposureSummaryStatsTask and '
316 'will be removed after v24.')
318 doLegacyStarSelectionComputation = pexConfig.Field(
319 doc=
"Perform the legacy star selection computations (for backwards compatibility)",
322 deprecated=(
"This field is here for backwards compatibility and will be "
323 "removed after v24.")
325 maxPsfTraceRadiusDelta = pexConfig.Field(
326 doc=
"Maximum delta (max - min) of model PSF trace radius values evaluated on a grid on "
327 "the unmasked detector pixels (pixel).",
335 """Select images using their Wcs and cuts on the PSF properties
337 The PSF quality criteria are based on the size and ellipticity residuals
from the
338 adaptive second moments of the star
and the PSF.
341 - the median of the ellipticty residuals
342 - the robust scatter of the size residuals (using the median absolute deviation)
343 - the robust scatter of the size residuals scaled by the square of
347 ConfigClass = PsfWcsSelectImagesConfig
348 _DefaultName = "PsfWcsSelectImages"
350 def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]):
351 """Select images in the selectDataList that overlap the patch and satisfy PSF quality critera.
353 This method is the old entry point
for the Gen2 commandline tasks
and drivers
354 Will be deprecated
in v22.
356 @param dataRef: Data reference
for coadd/tempExp (
with tract, patch)
358 @param makeDataRefList: Construct a list of data references?
359 @param selectDataList: List of SelectStruct, to consider
for selection
361 result = super(PsfWcsSelectImagesTask, self).runDataRef(dataRef, coordList, makeDataRefList,
365 exposureInfoList = []
366 for dataRef, exposureInfo
in zip(result.dataRefList, result.exposureInfoList):
367 butler = dataRef.butlerSubset.butler
368 srcCatalog = butler.get(
'src', dataRef.dataId)
369 valid = self.isValidLegacy(srcCatalog, dataRef.dataId)
373 dataRefList.append(dataRef)
374 exposureInfoList.append(exposureInfo)
376 return pipeBase.Struct(
377 dataRefList=dataRefList,
378 exposureInfoList=exposureInfoList,
381 def run(self, wcsList, bboxList, coordList, visitSummary, dataIds=None, srcList=None, **kwargs):
382 """Return indices of provided lists that meet the selection criteria
387 specifying the WCS's of the input ccds to be selected
389 specifying the bounding boxes of the input ccds to be selected
391 ICRS coordinates specifying boundary of the patch.
393 containing the PSF shape information for the input ccds to be selected.
395 containing the PSF shape information
for the input ccds to be selected.
396 This
is only used
if ``config.doLegacyStarSelectionComputation``
is
401 goodPsf: `list` of `int`
402 of indices of selected ccds
404 goodWcs = super(PsfWcsSelectImagesTask, self).run(wcsList=wcsList, bboxList=bboxList,
405 coordList=coordList, dataIds=dataIds)
409 if not self.config.doLegacyStarSelectionComputation:
411 if 'nPsfStar' not in visitSummary[0].schema.getNames():
412 raise RuntimeError(
"Old calexps detected. "
413 "Please set config.doLegacyStarSelectionComputation=True for "
414 "backwards compatibility.")
416 for i, dataId
in enumerate(dataIds):
419 if self.isValid(visitSummary, dataId[
"detector"]):
423 dataIds = [
None] * len(srcList)
424 for i, (srcCatalog, dataId)
in enumerate(zip(srcList, dataIds)):
427 if self.isValidLegacy(srcCatalog, dataId):
432 def isValid(self, visitSummary, detectorId):
433 """Should this ccd be selected based on its PSF shape information.
446 row = visitSummary.find(detectorId)
449 self.log.warning(
"Removing detector %d because summary stats not available.", detectorId)
452 medianE = np.sqrt(row[
"psfStarDeltaE1Median"]**2. + row[
"psfStarDeltaE2Median"]**2.)
453 scatterSize = row[
"psfStarDeltaSizeScatter"]
454 scaledScatterSize = row[
"psfStarScaledDeltaSizeScatter"]
455 psfTraceRadiusDelta = row[
"psfTraceRadiusDelta"]
458 if self.config.maxEllipResidual
and not (medianE <= self.config.maxEllipResidual):
459 self.log.info(
"Removing visit %d detector %d because median e residual too large: %f vs %f",
460 row[
"visit"], detectorId, medianE, self.config.maxEllipResidual)
462 elif self.config.maxSizeScatter
and not (scatterSize <= self.config.maxSizeScatter):
463 self.log.info(
"Removing visit %d detector %d because size scatter too large: %f vs %f",
464 row[
"visit"], detectorId, scatterSize, self.config.maxSizeScatter)
466 elif self.config.maxScaledSizeScatter
and not (scaledScatterSize <= self.config.maxScaledSizeScatter):
467 self.log.info(
"Removing visit %d detector %d because scaled size scatter too large: %f vs %f",
468 row[
"visit"], detectorId, scaledScatterSize, self.config.maxScaledSizeScatter)
471 self.config.maxPsfTraceRadiusDelta
is not None
472 and not (psfTraceRadiusDelta <= self.config.maxPsfTraceRadiusDelta)
475 "Removing visit %d detector %d because max-min delta of model PSF trace radius values "
476 "across the unmasked detector pixels is not finite or too large: %.3f vs %.3f (pixels)",
477 row[
"visit"], detectorId, psfTraceRadiusDelta, self.config.maxPsfTraceRadiusDelta
483 def isValidLegacy(self, srcCatalog, dataId=None):
484 """Should this ccd be selected based on its PSF shape information.
486 This routine is only used
in legacy processing (gen2
and
487 backwards compatible old calexps)
and should be removed after v24.
492 dataId : `dict` of dataId keys, optional.
493 Used only
for logging. Defaults to
None.
500 mask = srcCatalog[self.config.starSelection]
502 starXX = srcCatalog[self.config.starShape+'_xx'][mask]
503 starYY = srcCatalog[self.config.starShape+
'_yy'][mask]
504 starXY = srcCatalog[self.config.starShape+
'_xy'][mask]
505 psfXX = srcCatalog[self.config.psfShape+
'_xx'][mask]
506 psfYY = srcCatalog[self.config.psfShape+
'_yy'][mask]
507 psfXY = srcCatalog[self.config.psfShape+
'_xy'][mask]
509 starSize = np.power(starXX*starYY - starXY**2, 0.25)
510 starE1 = (starXX - starYY)/(starXX + starYY)
511 starE2 = 2*starXY/(starXX + starYY)
512 medianSize = np.median(starSize)
514 psfSize = np.power(psfXX*psfYY - psfXY**2, 0.25)
515 psfE1 = (psfXX - psfYY)/(psfXX + psfYY)
516 psfE2 = 2*psfXY/(psfXX + psfYY)
518 medianE1 = np.abs(np.median(starE1 - psfE1))
519 medianE2 = np.abs(np.median(starE2 - psfE2))
520 medianE = np.sqrt(medianE1**2 + medianE2**2)
522 scatterSize =
sigmaMad(starSize - psfSize)
523 scaledScatterSize = scatterSize/medianSize**2
526 if self.config.maxEllipResidual
and medianE > self.config.maxEllipResidual:
527 self.log.info(
"Removing visit %s because median e residual too large: %f vs %f",
528 dataId, medianE, self.config.maxEllipResidual)
530 elif self.config.maxSizeScatter
and scatterSize > self.config.maxSizeScatter:
531 self.log.info(
"Removing visit %s because size scatter is too large: %f vs %f",
532 dataId, scatterSize, self.config.maxSizeScatter)
534 elif self.config.maxScaledSizeScatter
and scaledScatterSize > self.config.maxScaledSizeScatter:
535 self.log.info(
"Removing visit %s because scaled size scatter is too large: %f vs %f",
536 dataId, scaledScatterSize, self.config.maxScaledSizeScatter)
542class BestSeeingSelectVisitsConnections(pipeBase.PipelineTaskConnections,
543 dimensions=(
"tract",
"patch",
"skymap",
"band",
"instrument"),
544 defaultTemplates={
"coaddName":
"goodSeeing"}):
545 skyMap = pipeBase.connectionTypes.Input(
546 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
547 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
548 storageClass=
"SkyMap",
549 dimensions=(
"skymap",),
551 visitSummaries = pipeBase.connectionTypes.Input(
552 doc=
"Per-visit consolidated exposure metadata",
553 name=
"finalVisitSummary",
554 storageClass=
"ExposureCatalog",
555 dimensions=(
"instrument",
"visit",),
559 goodVisits = pipeBase.connectionTypes.Output(
560 doc=
"Selected visits to be coadded.",
561 name=
"{coaddName}Visits",
562 storageClass=
"StructuredDataDict",
563 dimensions=(
"instrument",
"tract",
"patch",
"skymap",
"band"),
567class BestSeeingSelectVisitsConfig(pipeBase.PipelineTaskConfig,
568 pipelineConnections=BestSeeingSelectVisitsConnections):
569 nVisitsMax = pexConfig.RangeField(
571 doc=
"Maximum number of visits to select",
575 maxPsfFwhm = pexConfig.Field(
577 doc=
"Maximum PSF FWHM (in arcseconds) to select",
581 minPsfFwhm = pexConfig.Field(
583 doc=
"Minimum PSF FWHM (in arcseconds) to select",
587 doConfirmOverlap = pexConfig.Field(
589 doc=
"Do remove visits that do not actually overlap the patch?",
592 minMJD = pexConfig.Field(
594 doc=
"Minimum visit MJD to select",
598 maxMJD = pexConfig.Field(
600 doc=
"Maximum visit MJD to select",
606class BestSeeingSelectVisitsTask(pipeBase.PipelineTask):
607 """Select up to a maximum number of the best-seeing visits
609 Don't exceed the FWHM range specified by configs min(max)PsfFwhm.
610 This Task is a port of the Gen2 image-selector used
in the AP pipeline:
611 BestSeeingSelectImagesTask. This Task selects full visits based on the
612 average PSF of the entire visit.
614 ConfigClass = BestSeeingSelectVisitsConfig
615 _DefaultName = 'bestSeeingSelectVisits'
617 def runQuantum(self, butlerQC, inputRefs, outputRefs):
618 inputs = butlerQC.get(inputRefs)
619 quantumDataId = butlerQC.quantum.dataId
620 outputs = self.run(**inputs, dataId=quantumDataId)
621 butlerQC.put(outputs, outputRefs)
623 def run(self, visitSummaries, skyMap, dataId):
628 visitSummary : `list`
629 List of `lsst.pipe.base.connections.DeferredDatasetRef` of
631 skyMap : `lsst.skyMap.SkyMap`
632 SkyMap for checking visits overlap patch
633 dataId : `dict` of dataId keys
634 For retrieving patch info
for checking visits overlap patch
638 result : `lsst.pipe.base.Struct`
639 Result struct
with components:
641 - `goodVisits`: `dict`
with selected visit ids
as keys,
642 so that it can be be saved
as a StructuredDataDict.
643 StructuredDataList
's are currently limited.
646 if self.config.doConfirmOverlap:
647 patchPolygon = self.makePatchPolygon(skyMap, dataId)
649 inputVisits = [visitSummary.ref.dataId[
'visit']
for visitSummary
in visitSummaries]
652 for visit, visitSummary
in zip(inputVisits, visitSummaries):
654 visitSummary = visitSummary.get()
657 mjd = visitSummary[0].getVisitInfo().getDate().get(system=DateTime.MJD)
659 pixToArcseconds = [vs.getWcs().getPixelScale(vs.getBBox().getCenter()).asArcseconds()
660 for vs
in visitSummary]
662 psfSigmas = np.array([vs[
'psfSigma']
for vs
in visitSummary])
663 fwhm = np.nanmean(psfSigmas * pixToArcseconds) * np.sqrt(8.*np.log(2.))
665 if self.config.maxPsfFwhm
and fwhm > self.config.maxPsfFwhm:
667 if self.config.minPsfFwhm
and fwhm < self.config.minPsfFwhm:
669 if self.config.minMJD
and mjd < self.config.minMJD:
670 self.log.debug(
'MJD %f earlier than %.2f; rejecting', mjd, self.config.minMJD)
672 if self.config.maxMJD
and mjd > self.config.maxMJD:
673 self.log.debug(
'MJD %f later than %.2f; rejecting', mjd, self.config.maxMJD)
675 if self.config.doConfirmOverlap
and not self.doesIntersectPolygon(visitSummary, patchPolygon):
678 fwhmSizes.append(fwhm)
681 sortedVisits = [ind
for (_, ind)
in sorted(zip(fwhmSizes, visits))]
682 output = sortedVisits[:self.config.nVisitsMax]
683 self.log.info(
"%d images selected with FWHM range of %d--%d arcseconds",
684 len(output), fwhmSizes[visits.index(output[0])], fwhmSizes[visits.index(output[-1])])
687 goodVisits = {key:
True for key
in output}
688 return pipeBase.Struct(goodVisits=goodVisits)
690 def makePatchPolygon(self, skyMap, dataId):
691 """Return True if sky polygon overlaps visit
696 Exposure catalog with per-detector geometry
697 dataId : `dict` of dataId keys
698 For retrieving patch info
702 result :` lsst.sphgeom.ConvexPolygon.convexHull`
703 Polygon of patch
's outer bbox
705 wcs = skyMap[dataId['tract']].getWcs()
706 bbox = skyMap[dataId[
'tract']][dataId[
'patch']].getOuterBBox()
711 def doesIntersectPolygon(self, visitSummary, polygon):
712 """Return True if sky polygon overlaps visit
717 Exposure catalog with per-detector geometry
718 polygon :` lsst.sphgeom.ConvexPolygon.convexHull`
719 Polygon to check overlap
723 doesIntersect: `bool`
724 Does the visit overlap the polygon
726 doesIntersect = False
727 for detectorSummary
in visitSummary:
729 zip(detectorSummary[
'raCorners'], detectorSummary[
'decCorners'])]
731 if detectorPolygon.intersects(polygon):
737class BestSeeingQuantileSelectVisitsConfig(pipeBase.PipelineTaskConfig,
738 pipelineConnections=BestSeeingSelectVisitsConnections):
739 qMin = pexConfig.RangeField(
740 doc=
"Lower bound of quantile range to select. Sorts visits by seeing from narrow to wide, "
741 "and select those in the interquantile range (qMin, qMax). Set qMin to 0 for Best Seeing. "
742 "This config should be changed from zero only for exploratory diffIm testing.",
748 qMax = pexConfig.RangeField(
749 doc=
"Upper bound of quantile range to select. Sorts visits by seeing from narrow to wide, "
750 "and select those in the interquantile range (qMin, qMax). Set qMax to 1 for Worst Seeing.",
756 nVisitsMin = pexConfig.Field(
757 doc=
"At least this number of visits selected and supercedes quantile. For example, if 10 visits "
758 "cover this patch, qMin=0.33, and nVisitsMin=5, the best 5 visits will be selected.",
762 doConfirmOverlap = pexConfig.Field(
764 doc=
"Do remove visits that do not actually overlap the patch?",
767 minMJD = pexConfig.Field(
769 doc=
"Minimum visit MJD to select",
773 maxMJD = pexConfig.Field(
775 doc=
"Maximum visit MJD to select",
781class BestSeeingQuantileSelectVisitsTask(BestSeeingSelectVisitsTask):
782 """Select a quantile of the best-seeing visits
784 Selects the best (for example, third) full visits based on the average
785 PSF width
in the entire visit. It can also be used
for difference imaging
786 experiments that require templates
with the worst seeing visits.
787 For example, selecting the worst third can be acheived by
788 changing the config parameters qMin to 0.66
and qMax to 1.
790 ConfigClass = BestSeeingQuantileSelectVisitsConfig
791 _DefaultName = 'bestSeeingQuantileSelectVisits'
793 @utils.inheritDoc(BestSeeingSelectVisitsTask)
794 def run(self, visitSummaries, skyMap, dataId):
795 if self.config.doConfirmOverlap:
796 patchPolygon = self.makePatchPolygon(skyMap, dataId)
797 visits = np.array([visitSummary.ref.dataId[
'visit']
for visitSummary
in visitSummaries])
798 radius = np.empty(len(visits))
799 intersects = np.full(len(visits),
True)
800 for i, visitSummary
in enumerate(visitSummaries):
802 visitSummary = visitSummary.get()
804 psfSigma = np.nanmedian([vs[
'psfSigma']
for vs
in visitSummary])
806 if self.config.doConfirmOverlap:
807 intersects[i] = self.doesIntersectPolygon(visitSummary, patchPolygon)
808 if self.config.minMJD
or self.config.maxMJD:
810 mjd = visitSummary[0].getVisitInfo().getDate().get(system=DateTime.MJD)
811 aboveMin = mjd > self.config.minMJD
if self.config.minMJD
else True
812 belowMax = mjd < self.config.maxMJD
if self.config.maxMJD
else True
813 intersects[i] = intersects[i]
and aboveMin
and belowMax
815 sortedVisits = [v
for rad, v
in sorted(zip(radius[intersects], visits[intersects]))]
816 lowerBound = min(int(np.round(self.config.qMin*len(visits[intersects]))),
817 max(0, len(visits[intersects]) - self.config.nVisitsMin))
818 upperBound = max(int(np.round(self.config.qMax*len(visits[intersects]))), self.config.nVisitsMin)
821 goodVisits = {int(visit):
True for visit
in sortedVisits[lowerBound:upperBound]}
822 return pipeBase.Struct(goodVisits=goodVisits)
def __init__(self, dataId, coordList)
def _runArgDictFromDataId(self, dataId)
def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[])
def __init__(self, dataRef, wcs, bbox)
def getValidImageCorners(self, imageWcs, imageBox, patchPoly, dataId=None)
def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[])
def run(self, wcsList, bboxList, coordList, dataIds=None, **kwargs)
static ConvexPolygon convexHull(std::vector< UnitVector3d > const &points)