24 from builtins
import zip
25 from builtins
import object
30 import lsst.afw.detection
32 import lsst.afw.geom.ellipses
34 import lsst.pex.exceptions
36 from .sfm
import SingleFrameMeasurementTask
37 from .forcedMeasurement
import ForcedMeasurementTask
38 from .
import CentroidResultKey
40 __all__ = (
"BlendContext",
"TestDataset",
"AlgorithmTestCase",
"TransformTestCase",
41 "SingleFramePluginTransformSetupHelper",
"ForcedPluginTransformSetupHelper",
42 "FluxTransformTestCase",
"CentroidTransformTestCase")
47 A Python context manager used to add multiple overlapping sources along with a parent source 48 that represents all of them together. 50 This is used as the return value for TestDataset.addBlend(), and this is the only way it should 51 be used. The only public method is addChild(). 64 def addChild(self, flux, centroid, shape=None):
66 Add a child source to the blend, and return the truth catalog record that corresponds to it. 68 @param[in] flux Total flux of the source to be added. 69 @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D). 70 @param[in] shape 2nd moments of the source before PSF convolution 71 (lsst.afw.geom.ellipses.Quadrupole). Note that the truth catalog 72 records post-convolution moments) 74 record, image = self.
owner.addSource(flux, centroid, shape)
77 self.
children.append((record, image))
90 flux += record.get(self.
owner.keys[
"flux"])
96 w = record.get(self.
owner.keys[
"flux"])/flux
97 x += record.get(self.
owner.keys[
"centroid"].getX())*w
98 y += record.get(self.
owner.keys[
"centroid"].getY())*w
105 w = record.get(self.
owner.keys[
"flux"])/flux
106 dx = record.get(self.
owner.keys[
"centroid"].getX()) - x
107 dy = record.get(self.
owner.keys[
"centroid"].getY()) - y
108 xx += (record.get(self.
owner.keys[
"shape"].getIxx()) + dx**2)*w
109 yy += (record.get(self.
owner.keys[
"shape"].getIyy()) + dy**2)*w
110 xy += (record.get(self.
owner.keys[
"shape"].getIxy()) + dx*dy)*w
111 self.
parentRecord.set(self.
owner.keys[
"shape"], lsst.afw.geom.ellipses.Quadrupole(xx, yy, xy))
116 deblend = lsst.afw.image.MaskedImageF(self.
owner.exposure.getMaskedImage(),
True)
118 deblend.getImage().getArray()[:, :] = image.getArray()
119 heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.
parentRecord.getFootprint(), deblend)
120 record.setFootprint(heavyFootprint)
125 A simulated dataset consisting of a test image and an associated truth catalog. 127 TestDataset creates an idealized image made of pure Gaussians (including a Gaussian PSF), 128 with simple noise and idealized Footprints/HeavyFootprints that simulated the outputs 129 of detection and deblending. Multiple noise realizations can be created from the same 130 underlying sources, allowing uncertainty estimates to be verified via Monte Carlo. 134 bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(0,0), lsst.afw.geom.Point2I(100, 100)) 135 dataset = TestDataset(bbox) 136 dataset.addSource(flux=1E5, centroid=lsst.afw.geom.Point2D(25, 26)) 137 dataset.addSource(flux=2E5, centroid=lsst.afw.geom.Point2D(75, 24), 138 shape=lsst.afw.geom.ellipses.Quadrupole(8, 7, 2)) 139 with dataset.addBlend() as family: 140 family.addChild(flux=2E5, centroid=lsst.afw.geom.Point2D(50, 72)) 141 family.addChild(flux=1.5E5, centroid=lsst.afw.geom.Point2D(51, 74)) 142 exposure, catalog = dataset.realize(noise=100.0, schema=TestDataset.makeMinimalSchema()) 148 """Return the minimal schema needed to hold truth catalog fields. 150 When TestDataset.realize() is called, the schema must include at least these fields. 151 Usually it will include additional fields for measurement algorithm outputs, allowing 152 the same catalog to be used for both truth values (the fields from the minimal schema) 153 and the measurements. 155 if not hasattr(cls,
"_schema"):
156 schema = lsst.afw.table.SourceTable.makeMinimalSchema()
158 cls.
keys[
"parent"] = schema.find(
"parent").key
159 cls.
keys[
"nChild"] = schema.addField(
"deblend_nChild", type=np.int32)
160 cls.
keys[
"flux"] = schema.addField(
"truth_flux", type=np.float64, doc=
"true flux", units=
"count")
161 cls.
keys[
"centroid"] = lsst.afw.table.Point2DKey.addFields(
162 schema,
"truth",
"true simulated centroid",
"pixel" 164 cls.
keys[
"centroid_flag"] = schema.addField(
"truth_flag", type=
"Flag",
165 doc=
"set if the object is a star")
166 cls.
keys[
"shape"] = lsst.afw.table.QuadrupoleKey.addFields(
167 schema,
"truth",
"true shape after PSF convolution", lsst.afw.table.CoordinateType.PIXEL
169 cls.
keys[
"isStar"] = schema.addField(
"truth_isStar", type=
"Flag",
170 doc=
"set if the object is a star")
171 schema.getAliasMap().set(
"slot_Shape",
"truth")
172 schema.getAliasMap().set(
"slot_Centroid",
"truth")
173 schema.getAliasMap().set(
"slot_ModelFlux",
"truth")
174 schema.getCitizen().markPersistent()
176 schema = lsst.afw.table.Schema(cls.
_schema)
177 schema.disconnectAliases()
182 minRotation=None, maxRotation=None,
183 minRefShift=None, maxRefShift=None,
184 minPixShift=2.0, maxPixShift=4.0, randomSeed=None):
186 Create a new undistorted TanWcs that is similar but not identical to another, with random 187 scaling, rotation, and offset (in both pixel position and reference position). 189 The maximum and minimum arguments are interpreted as absolute values for a split 190 range that covers both positive and negative values (as this method is used 191 in testing, it is typically most important to avoid perturbations near zero). 192 Scale factors are treated somewhat differently: the actual scale factor is chosen between 193 minScaleFactor and maxScaleFactor OR (1/maxScaleFactor) and (1/minScaleFactor). 195 The default range for rotation is 30-60 degrees, and the default range for reference shift 196 is 0.5-1.0 arcseconds (these cannot be safely included directly as default values because Angle 197 objects are mutable). 199 The random number generator is primed with the seed given. If ``None``, a seed is 200 automatically chosen. 202 random_state = np.random.RandomState(randomSeed)
203 if minRotation
is None:
204 minRotation = 30.0*lsst.afw.geom.degrees
205 if maxRotation
is None:
206 maxRotation = 60.0*lsst.afw.geom.degrees
207 if minRefShift
is None:
208 minRefShift = 0.5*lsst.afw.geom.arcseconds
209 if maxRefShift
is None:
210 maxRefShift = 1.0*lsst.afw.geom.arcseconds
212 def splitRandom(min1, max1, min2=None, max2=None):
217 if random_state.uniform() > 0.5:
218 return float(random_state.uniform(min1, max1))
220 return float(random_state.uniform(min2, max2))
222 scaleFactor = splitRandom(minScaleFactor, maxScaleFactor, 1.0/maxScaleFactor, 1.0/minScaleFactor)
223 rotation = splitRandom(minRotation.asRadians(), maxRotation.asRadians())*lsst.afw.geom.radians
224 refShiftRa = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.afw.geom.radians
225 refShiftDec = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.afw.geom.radians
226 pixShiftX = splitRandom(minPixShift, maxPixShift)
227 pixShiftY = splitRandom(minPixShift, maxPixShift)
229 oldTransform = lsst.afw.geom.LinearTransform(oldWcs.getCDMatrix())
230 rTransform = lsst.afw.geom.LinearTransform.makeRotation(rotation)
231 sTransform = lsst.afw.geom.LinearTransform.makeScaling(scaleFactor)
232 newTransform = oldTransform*rTransform*sTransform
233 matrix = newTransform.getMatrix()
235 oldSkyOrigin = oldWcs.getSkyOrigin().toIcrs()
236 newSkyOrigin = lsst.afw.coord.IcrsCoord(oldSkyOrigin.getRa() + refShiftRa,
237 oldSkyOrigin.getDec() + refShiftDec)
239 oldPixOrigin = oldWcs.getPixelOrigin()
240 newPixOrigin = lsst.afw.geom.Point2D(oldPixOrigin.getX() + pixShiftX,
241 oldPixOrigin.getY() + pixShiftY)
242 return lsst.afw.image.makeWcs(newSkyOrigin, newPixOrigin,
243 matrix[0, 0], matrix[0, 1], matrix[1, 0], matrix[1, 1])
246 def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, fluxMag0=1E12):
248 Create an Exposure, with a Calib, Wcs, and Psf, but no pixel values set. 250 @param[in] bbox Bounding box of the image (image coordinates) as returned by makeCatalog. 251 @param[in] wcs New Wcs for the exposure (created from crval and cdelt if None). 252 @param[in] crval afw.coord.Coord: center of the TAN WCS attached to the image. 253 @param[in] cdelt afw.geom.Angle: pixel scale of the image 254 @param[in] psfSigma Radius (sigma) of the Gaussian PSF attached to the image 255 @param[in] psfDim Width and height of the image's Gaussian PSF attached to the image 256 @param[in] fluxMag0 Flux at magnitude zero (in e-) used to set the Calib of the exposure. 260 crval = lsst.afw.coord.IcrsCoord(45.0*lsst.afw.geom.degrees, 45.0*lsst.afw.geom.degrees)
262 cdelt = 0.2*lsst.afw.geom.arcseconds
263 crpix = lsst.afw.geom.Box2D(bbox).getCenter()
264 wcs = lsst.afw.image.makeWcs(crval, crpix, cdelt.asDegrees(), 0.0, 0.0, cdelt.asDegrees())
265 exposure = lsst.afw.image.ExposureF(bbox)
266 psf = lsst.afw.detection.GaussianPsf(psfDim, psfDim, psfSigma)
267 calib = lsst.afw.image.Calib()
268 calib.setFluxMag0(fluxMag0)
271 exposure.setCalib(calib)
277 Create an image of an elliptical Gaussian. 279 @param[in,out] bbox Bounding box of image to create. 280 @param[in] flux Total flux of the Gaussian (normalized analytically, not using pixel 282 @param[in] ellipse lsst.afw.geom.ellipses.Ellipse holding the centroid and shape. 284 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX()),
285 np.arange(bbox.getBeginY(), bbox.getEndY()))
286 t = ellipse.getGridTransform()
287 xt = t[t.XX] * x + t[t.XY] * y + t[t.X]
288 yt = t[t.YX] * x + t[t.YY] * y + t[t.Y]
289 image = lsst.afw.image.ImageF(bbox)
290 image.getArray()[:, :] = np.exp(-0.5*(xt**2 + yt**2))*flux/(2.0*ellipse.getCore().getArea())
293 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
295 Initialize the dataset. 297 @param[in] bbox Bounding box of the test image. 298 @param[in] threshold Threshold absolute value used to determine footprints for 299 simulated sources. This thresholding will be applied before noise is 300 actually added to images (or before the noise level is even known), so 301 this will necessarily produce somewhat artificial footprints. 302 @param[in] exposure lsst.afw.image.ExposureF test sources should be added to. Ownership should 303 be considered transferred from the caller to the TestDataset. 304 Must have a Gaussian Psf for truth catalog shapes to be exact. 305 @param[in] **kwds Keyword arguments forwarded to makeEmptyExposure if exposure is None. 309 self.
threshold = lsst.afw.detection.Threshold(threshold, lsst.afw.detection.Threshold.VALUE)
315 def _installFootprint(self, record, image):
316 """Create a Footprint for a simulated source and add it to its truth catalog record. 319 fpSet = lsst.afw.detection.FootprintSet(image, self.
threshold)
321 fpSet = lsst.afw.detection.FootprintSet(fpSet, int(self.
psfShape.getDeterminantRadius() + 1.0),
True)
323 fpSet.setMask(self.
exposure.getMaskedImage().getMask(),
"DETECTED")
325 if len(fpSet.getFootprints()) > 1:
326 raise RuntimeError(
"Threshold value results in multiple Footprints for a single object")
327 if len(fpSet.getFootprints()) == 0:
328 raise RuntimeError(
"Threshold value results in zero Footprints for object")
329 record.setFootprint(fpSet.getFootprints()[0])
333 Add a source to the simulation 335 @param[in] flux Total flux of the source to be added. 336 @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D). 337 @param[in] shape 2nd moments of the source before PSF convolution 338 (lsst.afw.geom.ellipses.Quadrupole). Note that the truth catalog 339 records post-convolution moments). If None, a point source 342 @return a truth catalog record and single-source image corresponding to the new source. 346 record.set(self.
keys[
"flux"], flux)
347 record.set(self.
keys[
"centroid"], centroid)
349 record.set(self.
keys[
"isStar"],
True)
352 record.set(self.
keys[
"isStar"],
False)
353 fullShape = shape.convolve(self.
psfShape)
354 record.set(self.
keys[
"shape"], fullShape)
357 lsst.afw.geom.ellipses.Ellipse(fullShape, centroid))
361 self.
exposure.getMaskedImage().getImage().getArray()[:, :] += image.getArray()
366 Return a context manager that allows a blend of multiple sources to be added. 371 with d.addBlend() as b: 372 b.addChild(flux1, centroid1) 373 b.addChild(flux2, centroid2, shape2) 376 Note that nothing stops you from creating overlapping sources just using the addSource() method, 377 but addBlend() is necesssary to create a parent object and deblended HeavyFootprints of the type 378 produced by the detection and deblending pipelines. 384 Create a copy of the dataset transformed to a new WCS, with new Psf and Calib. 386 @param[in] wcs Wcs for the new dataset. 387 @param[in] **kwds Additional keyword arguments passed on to makeEmptyExposure. If not 388 specified, these revert to the defaults for makeEmptyExposure, not the 389 values in the current dataset. 391 bboxD = lsst.afw.geom.Box2D()
392 xyt = lsst.afw.image.XYTransformFromWcsPair(wcs, self.
exposure.getWcs())
393 for corner
in lsst.afw.geom.Box2D(self.
exposure.getBBox()).getCorners():
394 bboxD.include(xyt.forwardTransform(lsst.afw.geom.Point2D(corner)))
395 bboxI = lsst.afw.geom.Box2I(bboxD)
398 newCalib = result.exposure.getCalib()
399 oldPsfShape = self.
exposure.getPsf().computeShape()
401 if record.get(self.
keys[
"nChild"]):
402 raise NotImplementedError(
"Transforming blended sources in TestDatasets is not supported")
403 magnitude = oldCalib.getMagnitude(record.get(self.
keys[
"flux"]))
404 newFlux = newCalib.getFlux(magnitude)
405 oldCentroid = record.get(self.
keys[
"centroid"])
406 newCentroid = xyt.forwardTransform(oldCentroid)
407 if record.get(self.
keys[
"isStar"]):
408 newDeconvolvedShape =
None 410 affine = xyt.linearizeForwardTransform(oldCentroid)
411 oldFullShape = record.get(self.
keys[
"shape"])
412 oldDeconvolvedShape = lsst.afw.geom.ellipses.Quadrupole(
413 oldFullShape.getIxx() - oldPsfShape.getIxx(),
414 oldFullShape.getIyy() - oldPsfShape.getIyy(),
415 oldFullShape.getIxy() - oldPsfShape.getIxy(),
418 newDeconvolvedShape = oldDeconvolvedShape.transform(affine.getLinear())
419 result.addSource(newFlux, newCentroid, newDeconvolvedShape)
422 def realize(self, noise, schema, randomSeed=None):
424 Create a simulated with noise and a simulated post-detection catalog with (Heavy)Footprints. 426 @param[in] noise Standard deviation of noise to be added to the exposure. The noise will be 427 Gaussian and constant, appropriate for the sky-limited regime. 428 @param[in] schema Schema of the new catalog to be created. Must start with self.schema (i.e. 429 schema.contains(self.schema) must be True), but typically contains fields for 430 already-configured measurement algorithms as well. 431 @param[in] randomSeed Seed for the random number generator. If None, a seed is chosen automatically. 433 @return a tuple of (exposure, catalog) 435 random_state = np.random.RandomState(randomSeed)
436 assert schema.contains(self.
schema)
437 mapper = lsst.afw.table.SchemaMapper(self.
schema)
438 mapper.addMinimalSchema(self.
schema,
True)
440 exposure.getMaskedImage().getVariance().getArray()[:, :] = noise**2
441 exposure.getMaskedImage().getImage().getArray()[:, :] \
442 += random_state.randn(exposure.getHeight(), exposure.getWidth())*noise
443 catalog = lsst.afw.table.SourceCatalog(schema)
444 catalog.extend(self.
catalog, mapper=mapper)
447 for record
in catalog:
449 if record.getParent() == 0:
452 parent = catalog.find(record.getParent())
453 footprint = parent.getFootprint()
454 parentFluxArrayNoNoise = np.zeros(footprint.getArea(), dtype=np.float32)
455 footprint.spans.flatten(parentFluxArrayNoNoise,
456 self.
exposure.getMaskedImage().getImage().getArray(),
458 parentFluxArrayNoisy = np.zeros(footprint.getArea(), dtype=np.float32)
459 footprint.spans.flatten(parentFluxArrayNoisy,
460 exposure.getMaskedImage().getImage().getArray(),
462 oldHeavy = record.getFootprint()
463 fraction = (oldHeavy.getImageArray() / parentFluxArrayNoNoise)
466 newHeavy = lsst.afw.detection.HeavyFootprintF(oldHeavy)
467 newHeavy.getImageArray()[:] = parentFluxArrayNoisy*fraction
468 newHeavy.getMaskArray()[:] = oldHeavy.getMaskArray()
469 newHeavy.getVarianceArray()[:] = oldHeavy.getVarianceArray()
470 record.setFootprint(newHeavy)
471 return exposure, catalog
477 """Convenience function to create a Config instance for SingleFrameMeasurementTask 479 The plugin and its dependencies will be the only plugins run, while the Centroid, Shape, 480 and ModelFlux slots will be set to the truth fields generated by the TestDataset class. 482 config = SingleFrameMeasurementTask.ConfigClass()
483 config.slots.centroid =
"truth" 484 config.slots.shape =
"truth" 485 config.slots.modelFlux =
None 486 config.slots.apFlux =
None 487 config.slots.psfFlux =
None 488 config.slots.instFlux =
None 489 config.slots.calibFlux =
None 490 config.plugins.names = (plugin,) + tuple(dependencies)
495 """Convenience function to create a SingleFrameMeasurementTask with a simple configuration. 499 raise ValueError(
"Either plugin or config argument must not be None")
502 schema = TestDataset.makeMinimalSchema()
504 schema.setAliasMap(
None)
505 if algMetadata
is None:
506 algMetadata = lsst.daf.base.PropertyList()
510 """Convenience function to create a Config instance for ForcedMeasurementTask 512 In addition to the plugins specified in the plugin and dependencies arguments, 513 the TransformedCentroid and TransformedShape plugins will be run and used as the 514 Centroid and Shape slots; these simply transform the reference catalog centroid 515 and shape to the measurement coordinate system. 517 config = ForcedMeasurementTask.ConfigClass()
518 config.slots.centroid =
"base_TransformedCentroid" 519 config.slots.shape =
"base_TransformedShape" 520 config.slots.modelFlux =
None 521 config.slots.apFlux =
None 522 config.slots.psfFlux =
None 523 config.slots.instFlux =
None 524 config.plugins.names = (plugin,) + tuple(dependencies) + (
"base_TransformedCentroid",
525 "base_TransformedShape")
530 """Convenience function to create a ForcedMeasurementTask with a simple configuration. 534 raise ValueError(
"Either plugin or config argument must not be None")
536 if refSchema
is None:
537 refSchema = TestDataset.makeMinimalSchema()
538 if algMetadata
is None:
539 algMetadata = lsst.daf.base.PropertyList()
545 Base class for testing measurement transformations. 547 We test both that the transform itself operates successfully (fluxes are 548 converted to magnitudes, flags are propagated properly) and that the 549 transform is registered as the default for the appropriate measurement 552 In the simple case of one-measurement-per-transformation, the developer 553 need not directly write any tests themselves: simply customizing the class 554 variables is all that is required. More complex measurements (e.g. 555 multiple aperture fluxes) require extra effort. 560 name =
"MeasurementTransformTest" 564 algorithmClass =
None 565 transformClass =
None 569 flagNames = (
"flag",)
574 singleFramePlugins = ()
578 bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(0, 0), lsst.afw.geom.Point2I(200, 200))
579 self.
calexp = TestDataset.makeEmptyExposure(bbox)
580 self._setupTransform()
589 def _populateCatalog(self, baseNames):
591 for flagValue
in (
True,
False):
592 records.append(self.inputCat.addNew())
593 for baseName
in baseNames:
595 if records[-1].schema.join(baseName, flagName)
in records[-1].schema:
596 records[-1].set(records[-1].schema.join(baseName, flagName), flagValue)
597 self._setFieldsInRecords(records, baseName)
599 def _checkOutput(self, baseNames):
600 for inSrc, outSrc
in zip(self.inputCat, self.outputCat):
601 for baseName
in baseNames:
602 self._compareFieldsInRecords(inSrc, outSrc, baseName)
604 keyName = outSrc.schema.join(baseName, flagName)
605 if keyName
in inSrc.schema:
606 self.assertEqual(outSrc.get(keyName), inSrc.get(keyName))
608 self.assertFalse(keyName
in outSrc.schema)
610 def _runTransform(self, doExtend=True):
612 self.outputCat.extend(self.inputCat, mapper=self.mapper)
613 self.transform(self.inputCat, self.outputCat, self.
calexp.getWcs(), self.
calexp.getCalib())
617 Test the operation of the transformation on a catalog containing random data. 621 * An appropriate exception is raised on an attempt to transform between catalogs with different 623 * Otherwise, all appropriate conversions are properly appled and that flags have been propagated. 625 The `baseNames` argument requires some explanation. This should be an iterable of the leading parts of 626 the field names for each measurement; that is, everything that appears before `_flux`, `_flag`, etc. 627 In the simple case of a single measurement per plugin, this is simply equal to `self.name` (thus 628 measurements are stored as `self.name + "_flux"`, etc). More generally, the developer may specify 629 whatever iterable they require. For example, to handle multiple apertures, we could have 630 `(self.name + "_0", self.name + "_1", ...)`. 632 @param[in] baseNames Iterable of the initial parts of measurement field names. 634 baseNames = baseNames
or [self.
name]
636 self.assertRaises(lsst.pex.exceptions.LengthError, self.
_runTransform,
False)
640 def _checkRegisteredTransform(self, registry, name):
643 self.assertEqual(registry[name].PluginClass.getTransformClass(), self.
transformClass)
647 Test that the transformation is appropriately registered with the relevant measurement algorithms. 657 def _setupTransform(self):
659 inputSchema = lsst.afw.table.SourceTable.makeMinimalSchema()
663 inputSchema.getAliasMap().set(
"slot_Centroid",
"dummy")
664 inputSchema.getAliasMap().set(
"slot_Shape",
"dummy")
665 self.algorithmClass(self.
control, self.name, inputSchema)
666 inputSchema.getAliasMap().erase(
"slot_Centroid")
667 inputSchema.getAliasMap().erase(
"slot_Shape")
668 self.
inputCat = lsst.afw.table.SourceCatalog(inputSchema)
669 self.
mapper = lsst.afw.table.SchemaMapper(inputSchema)
676 def _setupTransform(self):
678 inputMapper = lsst.afw.table.SchemaMapper(lsst.afw.table.SourceTable.makeMinimalSchema(),
679 lsst.afw.table.SourceTable.makeMinimalSchema())
683 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"dummy")
684 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Shape",
"dummy")
685 self.algorithmClass(self.
control, self.name, inputMapper, lsst.daf.base.PropertyList())
686 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Centroid")
687 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Shape")
688 self.
inputCat = lsst.afw.table.SourceCatalog(inputMapper.getOutputSchema())
689 self.
mapper = lsst.afw.table.SchemaMapper(inputMapper.getOutputSchema())
696 def _setFieldsInRecords(self, records, name):
697 for record
in records:
698 record[record.schema.join(name,
'flux')] = np.random.random()
699 record[record.schema.join(name,
'fluxSigma')] = np.random.random()
702 assert len(records) > 1
703 records[0][record.schema.join(name,
'flux')] = -1
705 def _compareFieldsInRecords(self, inSrc, outSrc, name):
706 fluxName, fluxSigmaName = inSrc.schema.join(name,
'flux'), inSrc.schema.join(name,
'fluxSigma')
707 if inSrc[fluxName] > 0:
708 mag, magErr = self.
calexp.getCalib().getMagnitude(inSrc[fluxName], inSrc[fluxSigmaName])
709 self.assertEqual(outSrc[outSrc.schema.join(name,
'mag')], mag)
710 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], magErr)
712 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'mag')]))
713 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'magErr')]))
718 def _setFieldsInRecords(self, records, name):
719 for record
in records:
720 record[record.schema.join(name,
'x')] = np.random.random()
721 record[record.schema.join(name,
'y')] = np.random.random()
724 for fieldSuffix
in (
'xSigma',
'ySigma',
'x_y_Cov'):
725 fieldName = record.schema.join(name, fieldSuffix)
726 if fieldName
in record.schema:
727 record[fieldName] = np.random.random()
729 def _compareFieldsInRecords(self, inSrc, outSrc, name):
730 centroidResultKey = CentroidResultKey(inSrc.schema[self.
name])
731 centroidResult = centroidResultKey.get(inSrc)
733 coord = lsst.afw.table.CoordKey(outSrc.schema[self.
name]).get(outSrc)
734 coordTruth = self.
calexp.getWcs().pixelToSky(centroidResult.getCentroid())
735 self.assertEqual(coordTruth, coord)
740 coordErr = lsst.afw.table.CovarianceMatrix2fKey(outSrc.schema[self.
name],
741 [
"ra",
"dec"]).get(outSrc)
742 except lsst.pex.exceptions.NotFoundError:
743 self.assertFalse(centroidResultKey.getCentroidErr().isValid())
745 transform = self.
calexp.getWcs().linearizePixelToSky(coordTruth, lsst.afw.geom.radians)
746 coordErrTruth = np.dot(np.dot(transform.getLinear().getMatrix(),
747 centroidResult.getCentroidErr()),
748 transform.getLinear().getMatrix().transpose())
749 np.testing.assert_array_almost_equal(np.array(coordErrTruth), coordErr)
def realize(self, noise, schema, randomSeed=None)
Create a simulated with noise and a simulated post-detection catalog with (Heavy)Footprints.
def makeSingleFrameMeasurementConfig(self, plugin=None, dependencies=())
def transform(self, wcs, kwds)
Create a copy of the dataset transformed to a new WCS, with new Psf and Calib.
def makePerturbedWcs(oldWcs, minScaleFactor=1.2, maxScaleFactor=1.5, minRotation=None, maxRotation=None, minRefShift=None, maxRefShift=None, minPixShift=2.0, maxPixShift=4.0, randomSeed=None)
Create a new undistorted TanWcs that is similar but not identical to another, with random scaling...
def __init__(self, bbox, threshold=10.0, exposure=None, kwds)
Initialize the dataset.
A subtask for measuring the properties of sources on a single exposure, using an existing "reference"...
A subtask for measuring the properties of sources on a single exposure.
def addBlend(self)
Return a context manager that allows a blend of multiple sources to be added.
def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, fluxMag0=1E12)
Create an Exposure, with a Calib, Wcs, and Psf, but no pixel values set.
def makeSingleFrameMeasurementTask(self, plugin=None, dependencies=(), config=None, schema=None, algMetadata=None)
def __init__(self, owner)
def drawGaussian(bbox, flux, ellipse)
Create an image of an elliptical Gaussian.
def _installFootprint(self, record, image)
def addSource(self, flux, centroid, shape=None)
Add a source to the simulation.
A simulated dataset consisting of a test image and an associated truth catalog.
def makeForcedMeasurementTask(self, plugin=None, dependencies=(), config=None, refSchema=None, algMetadata=None)
def addChild(self, flux, centroid, shape=None)
Add a child source to the blend, and return the truth catalog record that corresponds to it...
def __exit__(self, type_, value, tb)
def makeForcedMeasurementConfig(self, plugin=None, dependencies=())
def makeMinimalSchema(cls)
A Python context manager used to add multiple overlapping sources along with a parent source that rep...