Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of meas_base. 

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"""Definition of measurement plugins. 

23 

24This module defines and registers a series of pure-Python measurement plugins 

25which have trivial implementations. It also wraps measurement algorithms 

26defined in C++ to expose them to the measurement framework. 

27""" 

28 

29import numpy as np 

30 

31import lsst.pex.exceptions 

32import lsst.geom 

33import lsst.afw.detection 

34import lsst.afw.geom 

35 

36from .pluginRegistry import register 

37from .pluginsBase import BasePlugin 

38from .baseMeasurement import BaseMeasurementPluginConfig 

39from .sfm import SingleFramePluginConfig, SingleFramePlugin 

40from .forcedMeasurement import ForcedPluginConfig, ForcedPlugin 

41from .wrappers import wrapSimpleAlgorithm, wrapTransform, GenericPlugin 

42from .transforms import SimpleCentroidTransform 

43 

44from .apertureFlux import ApertureFluxControl, ApertureFluxTransform 

45from .transform import BaseTransform 

46from .blendedness import BlendednessAlgorithm, BlendednessControl 

47from .circularApertureFlux import CircularApertureFluxAlgorithm 

48from .gaussianFlux import GaussianFluxAlgorithm, GaussianFluxControl, GaussianFluxTransform 

49from .exceptions import MeasurementError 

50from .localBackground import LocalBackgroundControl, LocalBackgroundAlgorithm, LocalBackgroundTransform 

51from .naiveCentroid import NaiveCentroidAlgorithm, NaiveCentroidControl, NaiveCentroidTransform 

52from .peakLikelihoodFlux import PeakLikelihoodFluxAlgorithm, PeakLikelihoodFluxControl, \ 

53 PeakLikelihoodFluxTransform 

54from .pixelFlags import PixelFlagsAlgorithm, PixelFlagsControl 

55from .psfFlux import PsfFluxAlgorithm, PsfFluxControl, PsfFluxTransform 

56from .scaledApertureFlux import ScaledApertureFluxAlgorithm, ScaledApertureFluxControl, \ 

57 ScaledApertureFluxTransform 

58from .sdssCentroid import SdssCentroidAlgorithm, SdssCentroidControl, SdssCentroidTransform 

59from .sdssShape import SdssShapeAlgorithm, SdssShapeControl, SdssShapeTransform 

60 

61__all__ = ( 

62 "SingleFrameFPPositionConfig", "SingleFrameFPPositionPlugin", 

63 "SingleFrameJacobianConfig", "SingleFrameJacobianPlugin", 

64 "VarianceConfig", "SingleFrameVariancePlugin", "ForcedVariancePlugin", 

65 "InputCountConfig", "SingleFrameInputCountPlugin", "ForcedInputCountPlugin", 

66 "SingleFramePeakCentroidConfig", "SingleFramePeakCentroidPlugin", 

67 "SingleFrameSkyCoordConfig", "SingleFrameSkyCoordPlugin", 

68 "ForcedPeakCentroidConfig", "ForcedPeakCentroidPlugin", 

69 "ForcedTransformedCentroidConfig", "ForcedTransformedCentroidPlugin", 

70 "ForcedTransformedShapeConfig", "ForcedTransformedShapePlugin", 

71 "EvaluateLocalPhotoCalibPlugin", "EvaluateLocalPhotoCalibPluginConfig", 

72 "EvaluateLocalWcsPlugin", "EvaluateLocalWcsPluginConfig", 

73) 

74 

75 

76wrapSimpleAlgorithm(PsfFluxAlgorithm, Control=PsfFluxControl, 

77 TransformClass=PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER, 

78 shouldApCorr=True, hasLogName=True) 

79wrapSimpleAlgorithm(PeakLikelihoodFluxAlgorithm, Control=PeakLikelihoodFluxControl, 

80 TransformClass=PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER) 

81wrapSimpleAlgorithm(GaussianFluxAlgorithm, Control=GaussianFluxControl, 

82 TransformClass=GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER, 

83 shouldApCorr=True) 

84wrapSimpleAlgorithm(NaiveCentroidAlgorithm, Control=NaiveCentroidControl, 

85 TransformClass=NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER) 

86wrapSimpleAlgorithm(SdssCentroidAlgorithm, Control=SdssCentroidControl, 

87 TransformClass=SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER) 

88wrapSimpleAlgorithm(PixelFlagsAlgorithm, Control=PixelFlagsControl, 

89 executionOrder=BasePlugin.FLUX_ORDER) 

90wrapSimpleAlgorithm(SdssShapeAlgorithm, Control=SdssShapeControl, 

91 TransformClass=SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER) 

92wrapSimpleAlgorithm(ScaledApertureFluxAlgorithm, Control=ScaledApertureFluxControl, 

93 TransformClass=ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER) 

94 

95wrapSimpleAlgorithm(CircularApertureFluxAlgorithm, needsMetadata=True, Control=ApertureFluxControl, 

96 TransformClass=ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER) 

97wrapSimpleAlgorithm(BlendednessAlgorithm, Control=BlendednessControl, 

98 TransformClass=BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER) 

99 

100wrapSimpleAlgorithm(LocalBackgroundAlgorithm, Control=LocalBackgroundControl, 

101 TransformClass=LocalBackgroundTransform, executionOrder=BasePlugin.FLUX_ORDER) 

102 

103wrapTransform(PsfFluxTransform) 

104wrapTransform(PeakLikelihoodFluxTransform) 

105wrapTransform(GaussianFluxTransform) 

106wrapTransform(NaiveCentroidTransform) 

107wrapTransform(SdssCentroidTransform) 

108wrapTransform(SdssShapeTransform) 

109wrapTransform(ScaledApertureFluxTransform) 

110wrapTransform(ApertureFluxTransform) 

111wrapTransform(LocalBackgroundTransform) 

112 

113 

114class SingleFrameFPPositionConfig(SingleFramePluginConfig): 

115 """Configuration for the focal plane position measurment algorithm. 

116 """ 

117 

118 pass 

119 

120 

121@register("base_FPPosition") 

122class SingleFrameFPPositionPlugin(SingleFramePlugin): 

123 """Algorithm to calculate the position of a centroid on the focal plane. 

124 

125 Parameters 

126 ---------- 

127 config : `SingleFrameFPPositionConfig` 

128 Plugin configuraion. 

129 name : `str` 

130 Plugin name. 

131 schema : `lsst.afw.table.Schema` 

132 The schema for the measurement output catalog. New fields will be 

133 added to hold measurements produced by this plugin. 

134 metadata : `lsst.daf.base.PropertySet` 

135 Plugin metadata that will be attached to the output catalog 

136 """ 

137 

138 ConfigClass = SingleFrameFPPositionConfig 

139 

140 @classmethod 

141 def getExecutionOrder(cls): 

142 return cls.SHAPE_ORDER 

143 

144 def __init__(self, config, name, schema, metadata): 

145 SingleFramePlugin.__init__(self, config, name, schema, metadata) 

146 self.focalValue = lsst.afw.table.Point2DKey.addFields(schema, name, "Position on the focal plane", 

147 "mm") 

148 self.focalFlag = schema.addField(name + "_flag", type="Flag", doc="Set to True for any fatal failure") 

149 self.detectorFlag = schema.addField(name + "_missingDetector_flag", type="Flag", 

150 doc="Set to True if detector object is missing") 

151 

152 def measure(self, measRecord, exposure): 

153 det = exposure.getDetector() 

154 if not det: 

155 measRecord.set(self.detectorFlag, True) 

156 fp = lsst.geom.Point2D(np.nan, np.nan) 

157 else: 

158 center = measRecord.getCentroid() 

159 fp = det.transform(center, lsst.afw.cameraGeom.PIXELS, lsst.afw.cameraGeom.FOCAL_PLANE) 

160 measRecord.set(self.focalValue, fp) 

161 

162 def fail(self, measRecord, error=None): 

163 measRecord.set(self.focalFlag, True) 

164 

165 

166class SingleFrameJacobianConfig(SingleFramePluginConfig): 

167 """Configuration for the Jacobian calculation plugin. 

168 """ 

169 

170 pixelScale = lsst.pex.config.Field(dtype=float, default=0.5, doc="Nominal pixel size (arcsec)") 

171 

172 

173@register("base_Jacobian") 

174class SingleFrameJacobianPlugin(SingleFramePlugin): 

175 """Compute the Jacobian and its ratio with a nominal pixel area. 

176 

177 This enables one to compare relative, rather than absolute, pixel areas. 

178 

179 Parameters 

180 ---------- 

181 config : `SingleFrameJacobianConfig` 

182 Plugin configuraion. 

183 name : `str` 

184 Plugin name. 

185 schema : `lsst.afw.table.Schema` 

186 The schema for the measurement output catalog. New fields will be 

187 added to hold measurements produced by this plugin. 

188 metadata : `lsst.daf.base.PropertySet` 

189 Plugin metadata that will be attached to the output catalog 

190 """ 

191 

192 ConfigClass = SingleFrameJacobianConfig 

193 

194 @classmethod 

195 def getExecutionOrder(cls): 

196 return cls.SHAPE_ORDER 

197 

198 def __init__(self, config, name, schema, metadata): 

199 SingleFramePlugin.__init__(self, config, name, schema, metadata) 

200 self.jacValue = schema.addField(name + '_value', type="D", doc="Jacobian correction") 

201 self.jacFlag = schema.addField(name + '_flag', type="Flag", doc="Set to 1 for any fatal failure") 

202 # Calculate one over the area of a nominal reference pixel, where area is in arcsec^2 

203 self.scale = pow(self.config.pixelScale, -2) 

204 

205 def measure(self, measRecord, exposure): 

206 center = measRecord.getCentroid() 

207 # Compute the area of a pixel at a source record's centroid, and take 

208 # the ratio of that with the defined reference pixel area. 

209 result = np.abs(self.scale*exposure.getWcs().linearizePixelToSky( 

210 center, 

211 lsst.geom.arcseconds).getLinear().computeDeterminant()) 

212 measRecord.set(self.jacValue, result) 

213 

214 def fail(self, measRecord, error=None): 

215 measRecord.set(self.jacFlag, True) 

216 

217 

218class VarianceConfig(BaseMeasurementPluginConfig): 

219 """Configuration for the variance calculation plugin. 

220 """ 

221 scale = lsst.pex.config.Field(dtype=float, default=5.0, optional=True, 

222 doc="Scale factor to apply to shape for aperture") 

223 mask = lsst.pex.config.ListField(doc="Mask planes to ignore", dtype=str, 

224 default=["DETECTED", "DETECTED_NEGATIVE", "BAD", "SAT"]) 

225 

226 

227class VariancePlugin(GenericPlugin): 

228 """Compute the median variance corresponding to a footprint. 

229 

230 The aim here is to measure the background variance, rather than that of 

231 the object itself. In order to achieve this, the variance is calculated 

232 over an area scaled up from the shape of the input footprint. 

233 

234 Parameters 

235 ---------- 

236 config : `VarianceConfig` 

237 Plugin configuraion. 

238 name : `str` 

239 Plugin name. 

240 schema : `lsst.afw.table.Schema` 

241 The schema for the measurement output catalog. New fields will be 

242 added to hold measurements produced by this plugin. 

243 metadata : `lsst.daf.base.PropertySet` 

244 Plugin metadata that will be attached to the output catalog 

245 """ 

246 

247 ConfigClass = VarianceConfig 

248 

249 FAILURE_BAD_CENTROID = 1 

250 """Denotes failures due to bad centroiding (`int`). 

251 """ 

252 

253 FAILURE_EMPTY_FOOTPRINT = 2 

254 """Denotes failures due to a lack of usable pixels (`int`). 

255 """ 

256 

257 @classmethod 

258 def getExecutionOrder(cls): 

259 return BasePlugin.FLUX_ORDER 

260 

261 def __init__(self, config, name, schema, metadata): 

262 GenericPlugin.__init__(self, config, name, schema, metadata) 

263 self.varValue = schema.addField(name + '_value', type="D", doc="Variance at object position") 

264 self.emptyFootprintFlag = schema.addField(name + '_flag_emptyFootprint', type="Flag", 

265 doc="Set to True when the footprint has no usable pixels") 

266 

267 # Alias the badCentroid flag to that which is defined for the target 

268 # of the centroid slot. We do not simply rely on the alias because 

269 # that could be changed post-measurement. 

270 schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag")) 

271 

272 def measure(self, measRecord, exposure, center): 

273 # Create an aperture and grow it by scale value defined in config to 

274 # ensure there are enough pixels around the object to get decent 

275 # statistics 

276 if not np.all(np.isfinite(measRecord.getCentroid())): 

277 raise MeasurementError("Bad centroid and/or shape", self.FAILURE_BAD_CENTROID) 

278 aperture = lsst.afw.geom.Ellipse(measRecord.getShape(), measRecord.getCentroid()) 

279 aperture.scale(self.config.scale) 

280 ellipse = lsst.afw.geom.SpanSet.fromShape(aperture) 

281 foot = lsst.afw.detection.Footprint(ellipse) 

282 foot.clipTo(exposure.getBBox(lsst.afw.image.PARENT)) 

283 # Filter out any pixels which have mask bits set corresponding to the 

284 # planes to be excluded (defined in config.mask) 

285 maskedImage = exposure.getMaskedImage() 

286 pixels = lsst.afw.detection.makeHeavyFootprint(foot, maskedImage) 

287 maskBits = maskedImage.getMask().getPlaneBitMask(self.config.mask) 

288 logicalMask = np.logical_not(pixels.getMaskArray() & maskBits) 

289 # Compute the median variance value for each pixel not excluded by the 

290 # mask and write the record. Numpy median is used here instead of 

291 # afw.math makeStatistics because of an issue with data types being 

292 # passed into the C++ layer (DM-2379). 

293 if np.any(logicalMask): 

294 medVar = np.median(pixels.getVarianceArray()[logicalMask]) 

295 measRecord.set(self.varValue, medVar) 

296 else: 

297 raise MeasurementError("Footprint empty, or all pixels are masked, can't compute median", 

298 self.FAILURE_EMPTY_FOOTPRINT) 

299 

300 def fail(self, measRecord, error=None): 

301 # Check that we have an error object and that it is of type 

302 # MeasurementError 

303 if isinstance(error, MeasurementError): 

304 assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_EMPTY_FOOTPRINT) 

305 # FAILURE_BAD_CENTROID handled by alias to centroid record. 

306 if error.getFlagBit() == self.FAILURE_EMPTY_FOOTPRINT: 

307 measRecord.set(self.emptyFootprintFlag, True) 

308 measRecord.set(self.varValue, np.nan) 

309 GenericPlugin.fail(self, measRecord, error) 

310 

311 

312SingleFrameVariancePlugin = VariancePlugin.makeSingleFramePlugin("base_Variance") 

313"""Single-frame version of `VariancePlugin`. 

314""" 

315 

316ForcedVariancePlugin = VariancePlugin.makeForcedPlugin("base_Variance") 

317"""Forced version of `VariancePlugin`. 

318""" 

319 

320 

321class InputCountConfig(BaseMeasurementPluginConfig): 

322 """Configuration for the input image counting plugin. 

323 """ 

324 pass 

325 

326 

327class InputCountPlugin(GenericPlugin): 

328 """Count the number of input images which contributed to a a source. 

329 

330 Parameters 

331 ---------- 

332 config : `InputCountConfig` 

333 Plugin configuraion. 

334 name : `str` 

335 Plugin name. 

336 schema : `lsst.afw.table.Schema` 

337 The schema for the measurement output catalog. New fields will be 

338 added to hold measurements produced by this plugin. 

339 metadata : `lsst.daf.base.PropertySet` 

340 Plugin metadata that will be attached to the output catalog 

341 

342 Notes 

343 ----- 

344 Information is derived from the image's `~lsst.afw.image.CoaddInputs`. 

345 Note these limitation: 

346 

347 - This records the number of images which contributed to the pixel in the 

348 center of the source footprint, rather than to any or all pixels in the 

349 source. 

350 - Clipping in the coadd is not taken into account. 

351 """ 

352 

353 ConfigClass = InputCountConfig 

354 

355 FAILURE_BAD_CENTROID = 1 

356 """Denotes failures due to bad centroiding (`int`). 

357 """ 

358 

359 FAILURE_NO_INPUTS = 2 

360 """Denotes failures due to the image not having coadd inputs. (`int`) 

361 """ 

362 

363 @classmethod 

364 def getExecutionOrder(cls): 

365 return BasePlugin.SHAPE_ORDER 

366 

367 def __init__(self, config, name, schema, metadata): 

368 GenericPlugin.__init__(self, config, name, schema, metadata) 

369 self.numberKey = schema.addField(name + '_value', type="I", 

370 doc="Number of images contributing at center, not including any" 

371 "clipping") 

372 self.noInputsFlag = schema.addField(name + '_flag_noInputs', type="Flag", 

373 doc="No coadd inputs available") 

374 # Alias the badCentroid flag to that which is defined for the target of the centroid slot. 

375 # We do not simply rely on the alias because that could be changed post-measurement. 

376 schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag")) 

377 

378 def measure(self, measRecord, exposure, center): 

379 if not exposure.getInfo().getCoaddInputs(): 

380 raise MeasurementError("No coadd inputs defined.", self.FAILURE_NO_INPUTS) 

381 if not np.all(np.isfinite(center)): 

382 raise MeasurementError("Source has a bad centroid.", self.FAILURE_BAD_CENTROID) 

383 

384 ccds = exposure.getInfo().getCoaddInputs().ccds 

385 measRecord.set(self.numberKey, len(ccds.subsetContaining(center, exposure.getWcs()))) 

386 

387 def fail(self, measRecord, error=None): 

388 if error is not None: 

389 assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_NO_INPUTS) 

390 # FAILURE_BAD_CENTROID handled by alias to centroid record. 

391 if error.getFlagBit() == self.FAILURE_NO_INPUTS: 

392 measRecord.set(self.noInputsFlag, True) 

393 GenericPlugin.fail(self, measRecord, error) 

394 

395 

396SingleFrameInputCountPlugin = InputCountPlugin.makeSingleFramePlugin("base_InputCount") 

397"""Single-frame version of `InputCoutPlugin`. 

398""" 

399 

400ForcedInputCountPlugin = InputCountPlugin.makeForcedPlugin("base_InputCount") 

401"""Forced version of `InputCoutPlugin`. 

402""" 

403 

404 

405class EvaluateLocalPhotoCalibPluginConfig(BaseMeasurementPluginConfig): 

406 """Configuration for the variance calculation plugin. 

407 """ 

408 pass 

409 

410 

411class EvaluateLocalPhotoCalibPlugin(GenericPlugin): 

412 """Evaluate the local value of the Photometric Calibration in the exposure. 

413 

414 The aim is to store the local calib value within the catalog for later 

415 use in the Science Data Model functors. 

416 """ 

417 ConfigClass = EvaluateLocalPhotoCalibPluginConfig 

418 

419 @classmethod 

420 def getExecutionOrder(cls): 

421 return BasePlugin.FLUX_ORDER 

422 

423 def __init__(self, config, name, schema, metadata): 

424 GenericPlugin.__init__(self, config, name, schema, metadata) 

425 self.photoKey = schema.addField( 

426 name, 

427 type="D", 

428 doc="Local approximation of the PhotoCalib calibration factor at " 

429 "the location of the src.") 

430 self.photoErrKey = schema.addField( 

431 "%sErr" % name, 

432 type="D", 

433 doc="Error on the local approximation of the PhotoCalib " 

434 "calibration factor at the location of the src.") 

435 

436 def measure(self, measRecord, exposure, center): 

437 

438 photoCalib = exposure.getPhotoCalib() 

439 calib = photoCalib.getLocalCalibration(center) 

440 measRecord.set(self.photoKey, calib) 

441 

442 calibErr = photoCalib.getCalibrationErr() 

443 measRecord.set(self.photoErrKey, calibErr) 

444 

445 

446SingleFrameEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeSingleFramePlugin( 

447 "base_LocalPhotoCalib") 

448"""Single-frame version of `EvaluatePhotoCalibPlugin`. 

449""" 

450 

451ForcedEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeForcedPlugin( 

452 "base_LocalPhotoCalib") 

453"""Forced version of `EvaluatePhotoCalibPlugin`. 

454""" 

455 

456 

457class EvaluateLocalWcsPluginConfig(BaseMeasurementPluginConfig): 

458 """Configuration for the variance calculation plugin. 

459 """ 

460 pass 

461 

462 

463class EvaluateLocalWcsPlugin(GenericPlugin): 

464 """Evaluate the local, linear approximation of the Wcs. 

465 

466 The aim is to store the local calib value within the catalog for later 

467 use in the Science Data Model functors. 

468 """ 

469 ConfigClass = EvaluateLocalWcsPluginConfig 

470 

471 @classmethod 

472 def getExecutionOrder(cls): 

473 return BasePlugin.FLUX_ORDER 

474 

475 def __init__(self, config, name, schema, metadata): 

476 GenericPlugin.__init__(self, config, name, schema, metadata) 

477 self.cdMatrix11Key = schema.addField( 

478 "%s_CDMatrix_1_1" % name, 

479 type="D", 

480 doc="(1, 1) element of the CDMatrix for the linear approximation " 

481 "of the WCS at the src location.") 

482 self.cdMatrix12Key = schema.addField( 

483 "%s_CDMatrix_1_2" % name, 

484 type="D", 

485 doc="(1, 2) element of the CDMatrix for the linear approximation " 

486 "of the WCS at the src location.") 

487 self.cdMatrix21Key = schema.addField( 

488 "%s_CDMatrix_2_1" % name, 

489 type="D", 

490 doc="(2, 1) element of the CDMatrix for the linear approximation " 

491 "of the WCS at the src location.") 

492 self.cdMatrix22Key = schema.addField( 

493 "%s_CDMatrix_2_2" % name, 

494 type="D", 

495 doc="(2, 2) element of the CDMatrix for the linear approximation " 

496 "of the WCS at the src location.") 

497 

498 def measure(self, measRecord, exposure, center): 

499 localCDMatrix = exposure.getWcs().getCdMatrix(center) 

500 measRecord.set(self.cdMatrix11Key, localCDMatrix[0, 0]) 

501 measRecord.set(self.cdMatrix12Key, localCDMatrix[0, 1]) 

502 measRecord.set(self.cdMatrix21Key, localCDMatrix[1, 0]) 

503 measRecord.set(self.cdMatrix22Key, localCDMatrix[1, 1]) 

504 

505 

506SingleFrameEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeSingleFramePlugin("base_LocalWcs") 

507"""Single-frame version of `EvaluateLocalWcsPlugin`. 

508""" 

509 

510ForcedEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeForcedPlugin("base_LocalWcs") 

511"""Forced version of `EvaluateLocalWcsPlugin`. 

512""" 

513 

514 

515class SingleFramePeakCentroidConfig(SingleFramePluginConfig): 

516 """Configuration for the single frame peak centroiding algorithm. 

517 """ 

518 pass 

519 

520 

521@register("base_PeakCentroid") 

522class SingleFramePeakCentroidPlugin(SingleFramePlugin): 

523 """Record the highest peak in a source footprint as its centroid. 

524 

525 This is of course a relatively poor measure of the true centroid of the 

526 object; this algorithm is provided mostly for testing and debugging. 

527 

528 Parameters 

529 ---------- 

530 config : `SingleFramePeakCentroidConfig` 

531 Plugin configuraion. 

532 name : `str` 

533 Plugin name. 

534 schema : `lsst.afw.table.Schema` 

535 The schema for the measurement output catalog. New fields will be 

536 added to hold measurements produced by this plugin. 

537 metadata : `lsst.daf.base.PropertySet` 

538 Plugin metadata that will be attached to the output catalog 

539 """ 

540 

541 ConfigClass = SingleFramePeakCentroidConfig 

542 

543 @classmethod 

544 def getExecutionOrder(cls): 

545 return cls.CENTROID_ORDER 

546 

547 def __init__(self, config, name, schema, metadata): 

548 SingleFramePlugin.__init__(self, config, name, schema, metadata) 

549 self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel") 

550 self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel") 

551 self.flag = schema.addField(name + "_flag", type="Flag", doc="Centroiding failed") 

552 

553 def measure(self, measRecord, exposure): 

554 peak = measRecord.getFootprint().getPeaks()[0] 

555 measRecord.set(self.keyX, peak.getFx()) 

556 measRecord.set(self.keyY, peak.getFy()) 

557 

558 def fail(self, measRecord, error=None): 

559 measRecord.set(self.flag, True) 

560 

561 @staticmethod 

562 def getTransformClass(): 

563 return SimpleCentroidTransform 

564 

565 

566class SingleFrameSkyCoordConfig(SingleFramePluginConfig): 

567 """Configuration for the sky coordinates algorithm. 

568 """ 

569 pass 

570 

571 

572@register("base_SkyCoord") 

573class SingleFrameSkyCoordPlugin(SingleFramePlugin): 

574 """Record the sky position of an object based on its centroid slot and WCS. 

575 

576 The position is record in the ``coord`` field, which is part of the 

577 `~lsst.afw.table.SourceCatalog` minimal schema. 

578 

579 Parameters 

580 ---------- 

581 config : `SingleFrameSkyCoordConfig` 

582 Plugin configuraion. 

583 name : `str` 

584 Plugin name. 

585 schema : `lsst.afw.table.Schema` 

586 The schema for the measurement output catalog. New fields will be 

587 added to hold measurements produced by this plugin. 

588 metadata : `lsst.daf.base.PropertySet` 

589 Plugin metadata that will be attached to the output catalog 

590 """ 

591 

592 ConfigClass = SingleFrameSkyCoordConfig 

593 

594 @classmethod 

595 def getExecutionOrder(cls): 

596 return cls.SHAPE_ORDER 

597 

598 def measure(self, measRecord, exposure): 

599 # There should be a base class method for handling this exception. Put 

600 # this on a later ticket. Also, there should be a python Exception of 

601 # the appropriate type for this error 

602 if not exposure.hasWcs(): 

603 raise Exception("Wcs not attached to exposure. Required for " + self.name + " algorithm") 

604 measRecord.updateCoord(exposure.getWcs()) 

605 

606 def fail(self, measRecord, error=None): 

607 # Override fail() to do nothing in the case of an exception: this is 

608 # not ideal, but we don't have a place to put failures because we 

609 # don't allocate any fields. Should consider fixing as part of 

610 # DM-1011 

611 pass 

612 

613 

614class ForcedPeakCentroidConfig(ForcedPluginConfig): 

615 """Configuration for the forced peak centroid algorithm. 

616 """ 

617 pass 

618 

619 

620@register("base_PeakCentroid") 

621class ForcedPeakCentroidPlugin(ForcedPlugin): 

622 """Record the highest peak in a source footprint as its centroid. 

623 

624 This is of course a relatively poor measure of the true centroid of the 

625 object; this algorithm is provided mostly for testing and debugging. 

626 

627 This is similar to `SingleFramePeakCentroidPlugin`, except that transforms 

628 the peak coordinate from the original (reference) coordinate system to the 

629 coordinate system of the exposure being measured. 

630 

631 Parameters 

632 ---------- 

633 config : `ForcedPeakCentroidConfig` 

634 Plugin configuraion. 

635 name : `str` 

636 Plugin name. 

637 schemaMapper : `lsst.afw.table.SchemaMapper` 

638 A mapping from reference catalog fields to output 

639 catalog fields. Output fields are added to the output schema. 

640 metadata : `lsst.daf.base.PropertySet` 

641 Plugin metadata that will be attached to the output catalog. 

642 """ 

643 

644 ConfigClass = ForcedPeakCentroidConfig 

645 

646 @classmethod 

647 def getExecutionOrder(cls): 

648 return cls.CENTROID_ORDER 

649 

650 def __init__(self, config, name, schemaMapper, metadata): 

651 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata) 

652 schema = schemaMapper.editOutputSchema() 

653 self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel") 

654 self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel") 

655 

656 def measure(self, measRecord, exposure, refRecord, refWcs): 

657 targetWcs = exposure.getWcs() 

658 peak = refRecord.getFootprint().getPeaks()[0] 

659 result = lsst.geom.Point2D(peak.getFx(), peak.getFy()) 

660 result = targetWcs.skyToPixel(refWcs.pixelToSky(result)) 

661 measRecord.set(self.keyX, result.getX()) 

662 measRecord.set(self.keyY, result.getY()) 

663 

664 @staticmethod 

665 def getTransformClass(): 

666 return SimpleCentroidTransform 

667 

668 

669class ForcedTransformedCentroidConfig(ForcedPluginConfig): 

670 """Configuration for the forced transformed centroid algorithm. 

671 """ 

672 pass 

673 

674 

675@register("base_TransformedCentroid") 

676class ForcedTransformedCentroidPlugin(ForcedPlugin): 

677 """Record the transformation of the reference catalog centroid. 

678 

679 The centroid recorded in the reference catalog is tranformed to the 

680 measurement coordinate system and stored. 

681 

682 Parameters 

683 ---------- 

684 config : `ForcedTransformedCentroidConfig` 

685 Plugin configuration 

686 name : `str` 

687 Plugin name 

688 schemaMapper : `lsst.afw.table.SchemaMapper` 

689 A mapping from reference catalog fields to output 

690 catalog fields. Output fields are added to the output schema. 

691 metadata : `lsst.daf.base.PropertySet` 

692 Plugin metadata that will be attached to the output catalog. 

693 

694 Notes 

695 ----- 

696 This is used as the slot centroid by default in forced measurement, 

697 allowing subsequent measurements to simply refer to the slot value just as 

698 they would in single-frame measurement. 

699 """ 

700 

701 ConfigClass = ForcedTransformedCentroidConfig 

702 

703 @classmethod 

704 def getExecutionOrder(cls): 

705 return cls.CENTROID_ORDER 

706 

707 def __init__(self, config, name, schemaMapper, metadata): 

708 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata) 

709 schema = schemaMapper.editOutputSchema() 

710 # Allocate x and y fields, join these into a single FunctorKey for ease-of-use. 

711 xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column", 

712 units="pixel") 

713 yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row", 

714 units="pixel") 

715 self.centroidKey = lsst.afw.table.Point2DKey(xKey, yKey) 

716 # Because we're taking the reference position as given, we don't bother transforming its 

717 # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate 

718 # the flag field, if it exists. 

719 if "slot_Centroid_flag" in schemaMapper.getInputSchema(): 

720 self.flagKey = schema.addField(name + "_flag", type="Flag", 

721 doc="whether the reference centroid is marked as bad") 

722 else: 

723 self.flagKey = None 

724 

725 def measure(self, measRecord, exposure, refRecord, refWcs): 

726 targetWcs = exposure.getWcs() 

727 if not refWcs == targetWcs: 

728 targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid())) 

729 measRecord.set(self.centroidKey, targetPos) 

730 else: 

731 measRecord.set(self.centroidKey, refRecord.getCentroid()) 

732 if self.flagKey is not None: 

733 measRecord.set(self.flagKey, refRecord.getCentroidFlag()) 

734 

735 

736class ForcedTransformedShapeConfig(ForcedPluginConfig): 

737 """Configuration for the forced transformed shape algorithm. 

738 """ 

739 pass 

740 

741 

742@register("base_TransformedShape") 

743class ForcedTransformedShapePlugin(ForcedPlugin): 

744 """Record the transformation of the reference catalog shape. 

745 

746 The shape recorded in the reference catalog is tranformed to the 

747 measurement coordinate system and stored. 

748 

749 Parameters 

750 ---------- 

751 config : `ForcedTransformedShapeConfig` 

752 Plugin configuration 

753 name : `str` 

754 Plugin name 

755 schemaMapper : `lsst.afw.table.SchemaMapper` 

756 A mapping from reference catalog fields to output 

757 catalog fields. Output fields are added to the output schema. 

758 metadata : `lsst.daf.base.PropertySet` 

759 Plugin metadata that will be attached to the output catalog. 

760 

761 Notes 

762 ----- 

763 This is used as the slot shape by default in forced measurement, allowing 

764 subsequent measurements to simply refer to the slot value just as they 

765 would in single-frame measurement. 

766 """ 

767 

768 ConfigClass = ForcedTransformedShapeConfig 

769 

770 @classmethod 

771 def getExecutionOrder(cls): 

772 return cls.SHAPE_ORDER 

773 

774 def __init__(self, config, name, schemaMapper, metadata): 

775 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata) 

776 schema = schemaMapper.editOutputSchema() 

777 # Allocate xx, yy, xy fields, join these into a single FunctorKey for ease-of-use. 

778 xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment", 

779 units="pixel^2") 

780 yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment", 

781 units="pixel^2") 

782 xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment", 

783 units="pixel^2") 

784 self.shapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey) 

785 # Because we're taking the reference position as given, we don't bother transforming its 

786 # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate 

787 # the flag field, if it exists. 

788 if "slot_Shape_flag" in schemaMapper.getInputSchema(): 

789 self.flagKey = schema.addField(name + "_flag", type="Flag", 

790 doc="whether the reference shape is marked as bad") 

791 else: 

792 self.flagKey = None 

793 

794 def measure(self, measRecord, exposure, refRecord, refWcs): 

795 targetWcs = exposure.getWcs() 

796 if not refWcs == targetWcs: 

797 fullTransform = lsst.afw.geom.makeWcsPairTransform(refWcs, targetWcs) 

798 localTransform = lsst.afw.geom.linearizeTransform(fullTransform, refRecord.getCentroid()) 

799 measRecord.set(self.shapeKey, refRecord.getShape().transform(localTransform.getLinear())) 

800 else: 

801 measRecord.set(self.shapeKey, refRecord.getShape()) 

802 if self.flagKey is not None: 

803 measRecord.set(self.flagKey, refRecord.getShapeFlag())