34 """Config class for the QuickFrameMeasurementTask.
36 installPsf = pexConfig.ConfigurableField(
37 target=InstallGaussianPsfTask,
38 doc=
"Task for installing an initial PSF",
40 maxNonRoundness = pexConfig.Field(
42 doc=
"Ratio of xx to yy (or vice versa) above which to cut, in order to exclude spectra",
45 maxExtendedness = pexConfig.Field(
47 doc=
"Max absolute value of xx and yy above which to cut, in order to exclude large/things",
50 doExtendednessCut = pexConfig.Field(
52 doc=
"Apply the extendeness cut, as definted by maxExtendedness",
55 centroidPixelPercentile = pexConfig.Field(
57 doc=
"The image's percentile value which the centroid must be greater than to pass the final peak"
58 " check. Ignored if doCheckCentroidPixelValue is False",
61 doCheckCentroidPixelValue = pexConfig.Field(
63 doc=
"Check that the centroid found is actually in the centroidPixelPercentile percentile of the"
64 " image? Set to False for donut images.",
67 initialPsfWidth = pexConfig.Field(
69 doc=
"Guess at the initial PSF FWHM in pixels.",
72 nSigmaDetection = pexConfig.Field(
74 doc=
"Number of sigma for the detection limit.",
77 nPixMinDetection = pexConfig.Field(
79 doc=
"Minimum number of pixels in a detected source.",
89 """WARNING: An experimental new task with changable API! Do not rely on yet!
91 This task finds the centroid of the brightest source in a given CCD-image
92 and returns its centroid and a rough estimate of the seeing/PSF.
94 It is designed for speed, such that it can be used in observing scripts
95 to provide pointing offsets, allowing subsequent pointings to place
96 a source at an exact pixel position.
98 The approach taken here is deliberately sub-optimal in the detection and
99 measurement sense, with all optimisation being done for speed and robustness
102 A small set of unit tests exist for this task, which run automatically
103 if afwdata is setup. These, however, are stricky unit tests, and will not
104 catch algorithmic regressions. TODO: DM-29038 exists to merge a regression
105 real test which runs against 1,000 LATISS images, but is therefore slow
106 and requires access to the data.
110 config : lsst.pipe.tasks.quickFrameMeasurement.QuickFrameMeasurementTaskConfig
111 Configuration class for the QuickFrameMeasurementTask.
113 display : lsst.afw.display.Display, optional
114 The display to use for showing the images, detections and centroids.
118 result : lsst.pipe.base.Struct()
119 Return strucure containing whether the task was successful, the main
120 source's centroid, its the aperture fluxes, the ixx and iyy of the
121 source, and the median ixx, iyy of the detections in the exposure.
122 See run() method for further details.
126 This task should *never* raise, as the run() method is enclosed in an
127 except Exception block, so that it will never fail during observing.
128 Failure modes should be limited to returning a return Struct() with the same
129 structure as the success case, with all value set to np.nan but with
130 result.success=False.
132 ConfigClass = QuickFrameMeasurementTaskConfig
133 _DefaultName =
'quickFrameMeasurementTask'
135 def __init__(self, config, *, display=None, **kwargs):
136 super().
__init__(config=config, **kwargs)
137 self.makeSubtask(
"installPsf")
145 self.
schemaschema = afwTable.SourceTable.makeMinimalSchema()
148 self.
controlcontrol = measBase.SdssCentroidControl()
161 """Run a very basic but fast threshold-based object detection on an exposure
162 Return the footPrintSet for the objects in a postISR exposure.
166 exp : lsst.afw.image.Exposure
167 Image in which to detect objects.
170 nSigma above image's stddev at which to set the detection threshold.
173 Minimum number of pixels for detection.
176 Grow the detected footprint by this many pixels.
180 footPrintSet : lsst.afw.detection.FootprintSet
181 FootprintSet containing the detections.
192 """Perform a final check that centroid location is actually bright.
196 exp : lsst.afw.image.Exposure
197 centroid : `tuple` of `float`
198 Location of the centroid in pixel coordinates
201 Number of the source in the source catalog. Only used if the check
202 is failed, for debug purposes.
205 Image's percentile above which the pixel containing the centroid
206 must be in order to pass the check.
211 Raised if the centroid's pixel is not above the percentile threshold
213 threshold = np.percentile(exp.image.array, percentile)
214 pixelValue = exp.image[centroid]
215 if pixelValue < threshold:
216 msg = (f
"Final centroid pixel value check failed: srcNum {srcNum} at {centroid}"
217 f
" has central pixel = {pixelValue:3f} <"
218 f
" {percentile} percentile of image = {threshold:3f}")
219 raise ValueError(msg)
223 def _calcMedianXxYy(objData):
224 """Return the median ixx and iyy for object in the image.
226 medianXx = np.nanmedian([element[
'xx']
for element
in objData.values()])
227 medianYy = np.nanmedian([element[
'xx']
for element
in objData.values()])
228 return medianXx, medianYy
230 def _calcBrightestObjSrcNum(self, objData):
231 """Find the brightest source which passes the cuts among the sources.
235 objData : `dict` of `dict`
236 Dictionary, keyed by source number, containing the measurements.
241 The source number of the brightest source which passes the cuts.
243 max70, max70srcNum = -1, -1
244 max25, max25srcNum = -1, -1
246 for srcNum
in sorted(objData.keys()):
250 xx = objData[srcNum][
'xx']
251 yy = objData[srcNum][
'yy']
256 if self.config.doExtendednessCut:
257 if xx > self.config.maxExtendedness
or yy > self.config.maxExtendedness:
261 nonRoundness =
max(nonRoundness, 1/nonRoundness)
262 if nonRoundness > self.config.maxNonRoundness:
265 if self.log.isDebugEnabled():
266 text = f
"src {srcNum}: {objData[srcNum]['xCentroid']:.0f}, {objData[srcNum]['yCentroid']:.0f}"
267 text += f
" - xx={xx:.1f}, yy={yy:.1f}, nonRound={nonRoundness:.1f}"
268 text += f
" - ap70={objData[srcNum]['apFlux70']:,.0f}"
269 text += f
" - ap25={objData[srcNum]['apFlux25']:,.0f}"
270 text += f
" - skip={skip}"
276 ap70 = objData[srcNum][
'apFlux70']
277 ap25 = objData[srcNum][
'apFlux25']
284 if max70srcNum != max25srcNum:
285 self.log.
warn(
"WARNING! Max apFlux70 for different object than with max apFlux25")
291 def _measureFp(self, fp, exp):
292 """Run the measurements on a footprint.
296 fp : lsst.afw.detection.Footprint
297 The footprint to measure.
299 exp : lsst.afw.image.Exposure
300 The footprint's parent exposure.
304 src : lsst.afw.table.SourceRecord
305 The source record containing the measurements.
307 src = self.
tabletable.makeRecord()
314 def _getDataFromSrcRecord(self, src):
315 """Extract the shapes and centroids from a source record.
319 src : lsst.afw.table.SourceRecord
320 The source record from which to extract the measurements.
324 srcData : lsst.pipe.base.Struct
325 The struct containing the extracted measurements.
328 xx = np.sqrt(src[
'base_SdssShape_xx'])*2.355*pScale
329 yy = np.sqrt(src[
'base_SdssShape_yy'])*2.355*pScale
330 xCentroid = src[
'base_SdssCentroid_x']
331 yCentroid = src[
'base_SdssCentroid_y']
333 apFlux70 = src[
'aperFlux_70_0_instFlux']
334 apFlux25 = src[
'aperFlux_25_0_instFlux']
335 return pipeBase.Struct(xx=xx,
343 def _getDataFromFootprintOnly(fp, exp):
344 """Get the shape, centroid and flux from a footprint.
348 fp : lsst.afw.detection.Footprint
349 The footprint to measure.
350 exp : lsst.afw.image.Exposure
351 The footprint's parent exposure.
355 srcData : lsst.pipe.base.Struct
356 The struct containing the extracted measurements.
358 xx = fp.getShape().getIxx()
359 yy = fp.getShape().getIyy()
360 xCentroid, yCentroid = fp.getCentroid()
361 apFlux70 = np.sum(exp[fp.getBBox()].image.array)
362 apFlux25 = np.sum(exp[fp.getBBox()].image.array)
363 return pipeBase.Struct(xx=xx,
371 def _measurementResultToDict(measurementResult):
372 """Convenience function to repackage measurement results to a dict.
376 measurementResult : lsst.afw.table.SourceRecord
377 The source record to convert to a dict.
382 The dict containing the extracted data.
385 objData[
'xx'] = measurementResult.xx
386 objData[
'yy'] = measurementResult.yy
387 objData[
'xCentroid'] = measurementResult.xCentroid
388 objData[
'yCentroid'] = measurementResult.yCentroid
389 objData[
'apFlux70'] = measurementResult.apFlux70
390 objData[
'apFlux25'] = measurementResult.apFlux25
394 def _makeEmptyReturnStruct():
395 """Make the default/template return struct, with defaults to False/nan.
399 objData : lsst.pipe.base.Struct
400 The default template return structure.
402 result = pipeBase.Struct()
403 result.success =
False
404 result.brightestObjCentroid = (np.nan, np.nan)
405 result.brightestObj_xXyY = (np.nan, np.nan)
406 result.brightestObjApFlux70 = np.nan
407 result.brightestObjApFlux25 = np.nan
408 result.medianXxYy = (np.nan, np.nan)
411 def run(self, exp, doDisplay=False):
412 """Calculate position, flux and shape of the brightest star in an image.
414 Given an an assembled (and at least minimally ISRed exposure),
415 quickly and robustly calculate the centroid of the
416 brightest star in the image.
420 exp : lsst.afw.image.Exposure
421 The exposure in which to find and measure the brightest star.
424 Display the image and found sources. A diplay object must have
425 been passed to the task constructor.
429 result : lsst.pipe.base.Struct
431 Whether the task ran successfully and found the object (bool)
432 The object's centroid (float, float)
433 The object's ixx, iyy (float, float)
434 The object's 70 pixel aperture flux (float)
435 The object's 25 pixel aperture flux (float)
436 The images's median ixx, iyy (float, float)
437 If unsuccessful, the success field is False and all other results
438 are np.nan of the expected shape.
442 Because of this task's involvement in observing scripts, the run method
443 should *never* raise. Failure modes are noted by returning a Struct with
444 the same structure as the success case, with all value set to np.nan and
445 result.success=False.
448 result = self.
_run_run(exp=exp, doDisplay=doDisplay)
450 except Exception
as e:
451 self.log.
warn(f
"Failed to find main source centroid {e}")
455 def _run(self, exp, doDisplay=False):
456 """The actual run method, called by run()
458 Behaviour is documented in detail in the main run().
460 self.
plateScaleplateScale = exp.getWcs().getPixelScale().asArcseconds()
461 median = np.nanmedian(exp.image.array)
463 self.installPsf.
run(exp)
464 sources = self.
detectObjectsInExpdetectObjectsInExp(exp, nSigma=self.config.nSigmaDetection,
465 nPixMin=self.config.nPixMinDetection)
468 if self.
displaydisplay
is None:
469 raise RuntimeError(
"Display failed as no display provided during init()")
472 fpSet = sources.getFootprints()
473 self.log.
info(f
"Found {len(fpSet)} sources in exposure")
478 for srcNum, fp
in enumerate(fpSet):
482 except MeasurementError:
486 except MeasurementError
as e:
487 self.log.
info(f
"Skipped measuring source {srcNum}: {e}")
492 self.log.
info(f
"Measured {nMeasured} of {len(fpSet)} sources in exposure")
497 if brightestObjSrcNum
is None:
498 raise RuntimeError(
"No sources in image passed cuts")
500 x = objData[brightestObjSrcNum][
'xCentroid']
501 y = objData[brightestObjSrcNum][
'yCentroid']
502 brightestObjCentroid = (x, y)
503 xx = objData[brightestObjSrcNum][
'xx']
504 yy = objData[brightestObjSrcNum][
'yy']
505 brightestObjApFlux70 = objData[brightestObjSrcNum][
'apFlux70']
506 brightestObjApFlux25 = objData[brightestObjSrcNum][
'apFlux25']
509 if self.config.doCheckCentroidPixelValue:
510 self.
checkResultcheckResult(exp, brightestObjCentroid, brightestObjSrcNum,
511 self.config.centroidPixelPercentile)
514 result.success =
True
515 result.brightestObjCentroid = brightestObjCentroid
516 result.brightestObj_xXyY = (xx, yy)
517 result.brightestObjApFlux70 = brightestObjApFlux70
518 result.brightestObjApFlux25 = brightestObjApFlux25
519 result.medianXxYy = medianXxYy
A Threshold is used to pass a threshold value to detection algorithms.
Class for storing generic metadata.
def _getDataFromSrcRecord(self, src)
def _calcBrightestObjSrcNum(self, objData)
def _run(self, exp, doDisplay=False)
def _measureFp(self, fp, exp)
def _getDataFromFootprintOnly(fp, exp)
def _calcMedianXxYy(objData)
def run(self, exp, doDisplay=False)
def checkResult(exp, centroid, srcNum, percentile)
def detectObjectsInExp(exp, nSigma, nPixMin, grow=0)
def _measurementResultToDict(measurementResult)
def _makeEmptyReturnStruct()
def __init__(self, config, *display=None, **kwargs)
daf::base::PropertySet * set
def mtv(data, frame=None, title="", wcs=None, *args, **kwargs)
def measure(mi, x, y, size, statistic, stats)