24 from builtins
import zip
25 from builtins
import object
34 from .sfm
import SingleFrameMeasurementTask
35 from .forcedMeasurement
import ForcedMeasurementTask
36 from .
import CentroidResultKey
38 __all__ = (
"BlendContext",
"TestDataset",
"AlgorithmTestCase",
"TransformTestCase",
39 "SingleFramePluginTransformSetupHelper",
"ForcedPluginTransformSetupHelper",
40 "FluxTransformTestCase",
"CentroidTransformTestCase")
45 A Python context manager used to add multiple overlapping sources along with a parent source 46 that represents all of them together. 48 This is used as the return value for TestDataset.addBlend(), and this is the only way it should 49 be used. The only public method is addChild(). 62 def addChild(self, flux, centroid, shape=None):
64 Add a child source to the blend, and return the truth catalog record that corresponds to it. 66 @param[in] flux Total flux of the source to be added. 67 @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D). 68 @param[in] shape 2nd moments of the source before PSF convolution 69 (lsst.afw.geom.Quadrupole). Note that the truth catalog 70 records post-convolution moments) 72 record, image = self.
owner.addSource(flux, centroid, shape)
75 self.
children.append((record, image))
88 flux += record.get(self.
owner.keys[
"flux"])
94 w = record.get(self.
owner.keys[
"flux"])/flux
95 x += record.get(self.
owner.keys[
"centroid"].getX())*w
96 y += record.get(self.
owner.keys[
"centroid"].getY())*w
103 w = record.get(self.
owner.keys[
"flux"])/flux
104 dx = record.get(self.
owner.keys[
"centroid"].getX()) - x
105 dy = record.get(self.
owner.keys[
"centroid"].getY()) - y
106 xx += (record.get(self.
owner.keys[
"shape"].getIxx()) + dx**2)*w
107 yy += (record.get(self.
owner.keys[
"shape"].getIyy()) + dy**2)*w
108 xy += (record.get(self.
owner.keys[
"shape"].getIxy()) + dx*dy)*w
109 self.
parentRecord.set(self.
owner.keys[
"shape"], lsst.afw.geom.Quadrupole(xx, yy, xy))
114 deblend = lsst.afw.image.MaskedImageF(self.
owner.exposure.getMaskedImage(),
True)
116 deblend.getImage().getArray()[:, :] = image.getArray()
117 heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.
parentRecord.getFootprint(), deblend)
118 record.setFootprint(heavyFootprint)
123 A simulated dataset consisting of a test image and an associated truth catalog. 125 TestDataset creates an idealized image made of pure Gaussians (including a Gaussian PSF), 126 with simple noise and idealized Footprints/HeavyFootprints that simulated the outputs 127 of detection and deblending. Multiple noise realizations can be created from the same 128 underlying sources, allowing uncertainty estimates to be verified via Monte Carlo. 132 bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(0,0), lsst.afw.geom.Point2I(100, 100)) 133 dataset = TestDataset(bbox) 134 dataset.addSource(flux=1E5, centroid=lsst.afw.geom.Point2D(25, 26)) 135 dataset.addSource(flux=2E5, centroid=lsst.afw.geom.Point2D(75, 24), 136 shape=lsst.afw.geom.Quadrupole(8, 7, 2)) 137 with dataset.addBlend() as family: 138 family.addChild(flux=2E5, centroid=lsst.afw.geom.Point2D(50, 72)) 139 family.addChild(flux=1.5E5, centroid=lsst.afw.geom.Point2D(51, 74)) 140 exposure, catalog = dataset.realize(noise=100.0, schema=TestDataset.makeMinimalSchema()) 146 """Return the minimal schema needed to hold truth catalog fields. 148 When TestDataset.realize() is called, the schema must include at least these fields. 149 Usually it will include additional fields for measurement algorithm outputs, allowing 150 the same catalog to be used for both truth values (the fields from the minimal schema) 151 and the measurements. 153 if not hasattr(cls,
"_schema"):
156 cls.
keys[
"parent"] = schema.find(
"parent").key
157 cls.
keys[
"nChild"] = schema.addField(
"deblend_nChild", type=np.int32)
158 cls.
keys[
"flux"] = schema.addField(
"truth_flux", type=np.float64, doc=
"true flux", units=
"count")
159 cls.
keys[
"centroid"] = lsst.afw.table.Point2DKey.addFields(
160 schema,
"truth",
"true simulated centroid",
"pixel" 162 cls.
keys[
"centroid_sigma"] = lsst.afw.table.CovarianceMatrix2fKey.addFields(
163 schema,
"truth", [
'x',
'y'],
"pixel" 165 cls.
keys[
"centroid_flag"] = schema.addField(
"truth_flag", type=
"Flag",
166 doc=
"set if the object is a star")
168 schema,
"truth",
"true shape after PSF convolution", lsst.afw.table.CoordinateType.PIXEL
170 cls.
keys[
"isStar"] = schema.addField(
"truth_isStar", type=
"Flag",
171 doc=
"set if the object is a star")
172 schema.getAliasMap().set(
"slot_Shape",
"truth")
173 schema.getAliasMap().set(
"slot_Centroid",
"truth")
174 schema.getAliasMap().set(
"slot_ModelFlux",
"truth")
175 schema.getCitizen().markPersistent()
178 schema.disconnectAliases()
183 minRotation=None, maxRotation=None,
184 minRefShift=None, maxRefShift=None,
185 minPixShift=2.0, maxPixShift=4.0, randomSeed=1):
187 Create a new undistorted TAN WCS that is similar but not identical to another, with random 188 scaling, rotation, and offset (in both pixel position and reference position). 190 The maximum and minimum arguments are interpreted as absolute values for a split 191 range that covers both positive and negative values (as this method is used 192 in testing, it is typically most important to avoid perturbations near zero). 193 Scale factors are treated somewhat differently: the actual scale factor is chosen between 194 minScaleFactor and maxScaleFactor OR (1/maxScaleFactor) and (1/minScaleFactor). 196 The default range for rotation is 30-60 degrees, and the default range for reference shift 197 is 0.5-1.0 arcseconds (these cannot be safely included directly as default values because Angle 198 objects are mutable). 200 The random number generator is primed with the seed given. If ``None``, a seed is 201 automatically chosen. 203 random_state = np.random.RandomState(randomSeed)
204 if minRotation
is None:
205 minRotation = 30.0*lsst.afw.geom.degrees
206 if maxRotation
is None:
207 maxRotation = 60.0*lsst.afw.geom.degrees
208 if minRefShift
is None:
209 minRefShift = 0.5*lsst.afw.geom.arcseconds
210 if maxRefShift
is None:
211 maxRefShift = 1.0*lsst.afw.geom.arcseconds
213 def splitRandom(min1, max1, min2=None, max2=None):
218 if random_state.uniform() > 0.5:
219 return float(random_state.uniform(min1, max1))
221 return float(random_state.uniform(min2, max2))
223 scaleFactor = splitRandom(minScaleFactor, maxScaleFactor, 1.0/maxScaleFactor, 1.0/minScaleFactor)
224 rotation = splitRandom(minRotation.asRadians(), maxRotation.asRadians())*lsst.afw.geom.radians
225 refShiftRa = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.afw.geom.radians
226 refShiftDec = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.afw.geom.radians
227 pixShiftX = splitRandom(minPixShift, maxPixShift)
228 pixShiftY = splitRandom(minPixShift, maxPixShift)
233 newTransform = oldTransform*rTransform*sTransform
234 matrix = newTransform.getMatrix()
236 oldSkyOrigin = oldWcs.getSkyOrigin()
238 oldSkyOrigin.getDec() + refShiftDec)
240 oldPixOrigin = oldWcs.getPixelOrigin()
242 oldPixOrigin.getY() + pixShiftY)
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.geom.SpherePoint: ICRS 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. 262 cdelt = 0.2*lsst.afw.geom.arcseconds
266 exposure = lsst.afw.image.ExposureF(bbox)
269 calib.setFluxMag0(fluxMag0)
272 exposure.setCalib(calib)
278 Create an image of an elliptical Gaussian. 280 @param[in,out] bbox Bounding box of image to create. 281 @param[in] flux Total flux of the Gaussian (normalized analytically, not using pixel 283 @param[in] ellipse lsst.afw.geom.Ellipse holding the centroid and shape. 285 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX()),
286 np.arange(bbox.getBeginY(), bbox.getEndY()))
287 t = ellipse.getGridTransform()
288 xt = t[t.XX] * x + t[t.XY] * y + t[t.X]
289 yt = t[t.YX] * x + t[t.YY] * y + t[t.Y]
290 image = lsst.afw.image.ImageF(bbox)
291 image.getArray()[:, :] = np.exp(-0.5*(xt**2 + yt**2))*flux/(2.0*ellipse.getCore().getArea())
294 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
296 Initialize the dataset. 298 @param[in] bbox Bounding box of the test image. 299 @param[in] threshold Threshold absolute value used to determine footprints for 300 simulated sources. This thresholding will be applied before noise is 301 actually added to images (or before the noise level is even known), so 302 this will necessarily produce somewhat artificial footprints. 303 @param[in] exposure lsst.afw.image.ExposureF test sources should be added to. Ownership should 304 be considered transferred from the caller to the TestDataset. 305 Must have a Gaussian Psf for truth catalog shapes to be exact. 306 @param[in] **kwds Keyword arguments forwarded to makeEmptyExposure if exposure is None. 316 def _installFootprint(self, record, image):
317 """Create a Footprint for a simulated source and add it to its truth catalog record. 324 fpSet.setMask(self.
exposure.getMaskedImage().getMask(),
"DETECTED")
326 if len(fpSet.getFootprints()) > 1:
327 raise RuntimeError(
"Threshold value results in multiple Footprints for a single object")
328 if len(fpSet.getFootprints()) == 0:
329 raise RuntimeError(
"Threshold value results in zero Footprints for object")
330 record.setFootprint(fpSet.getFootprints()[0])
334 Add a source to the simulation 336 @param[in] flux Total flux of the source to be added. 337 @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D). 338 @param[in] shape 2nd moments of the source before PSF convolution 339 (lsst.afw.geom.Quadrupole). Note that the truth catalog 340 records post-convolution moments). If None, a point source 343 @return a truth catalog record and single-source image corresponding to the new source. 347 record.set(self.
keys[
"flux"], flux)
348 record.set(self.
keys[
"centroid"], centroid)
349 covariance = np.random.normal(0, 0.1, 4).reshape(2, 2)
350 covariance[0, 1] = covariance[1, 0]
351 record.set(self.
keys[
"centroid_sigma"], covariance.astype(np.float32))
353 record.set(self.
keys[
"isStar"],
True)
356 record.set(self.
keys[
"isStar"],
False)
357 fullShape = shape.convolve(self.
psfShape)
358 record.set(self.
keys[
"shape"], fullShape)
361 lsst.afw.geom.Ellipse(fullShape, centroid))
365 self.
exposure.getMaskedImage().getImage().getArray()[:, :] += image.getArray()
370 Return a context manager that allows a blend of multiple sources to be added. 375 with d.addBlend() as b: 376 b.addChild(flux1, centroid1) 377 b.addChild(flux2, centroid2, shape2) 380 Note that nothing stops you from creating overlapping sources just using the addSource() method, 381 but addBlend() is necesssary to create a parent object and deblended HeavyFootprints of the type 382 produced by the detection and deblending pipelines. 388 Create a copy of the dataset transformed to a new WCS, with new Psf and Calib. 390 @param[in] wcs Wcs for the new dataset. 391 @param[in] **kwds Additional keyword arguments passed on to makeEmptyExposure. If not 392 specified, these revert to the defaults for makeEmptyExposure, not the 393 values in the current dataset. 402 newCalib = result.exposure.getCalib()
403 oldPsfShape = self.
exposure.getPsf().computeShape()
405 if record.get(self.
keys[
"nChild"]):
406 raise NotImplementedError(
"Transforming blended sources in TestDatasets is not supported")
407 magnitude = oldCalib.getMagnitude(record.get(self.
keys[
"flux"]))
408 newFlux = newCalib.getFlux(magnitude)
409 oldCentroid = record.get(self.
keys[
"centroid"])
410 newCentroid = xyt.applyForward(oldCentroid)
411 if record.get(self.
keys[
"isStar"]):
412 newDeconvolvedShape =
None 415 oldFullShape = record.get(self.
keys[
"shape"])
416 oldDeconvolvedShape = lsst.afw.geom.Quadrupole(
417 oldFullShape.getIxx() - oldPsfShape.getIxx(),
418 oldFullShape.getIyy() - oldPsfShape.getIyy(),
419 oldFullShape.getIxy() - oldPsfShape.getIxy(),
422 newDeconvolvedShape = oldDeconvolvedShape.transform(affine.getLinear())
423 result.addSource(newFlux, newCentroid, newDeconvolvedShape)
426 def realize(self, noise, schema, randomSeed=1):
428 Create a simulated with noise and a simulated post-detection catalog with (Heavy)Footprints. 430 @param[in] noise Standard deviation of noise to be added to the exposure. The noise will be 431 Gaussian and constant, appropriate for the sky-limited regime. 432 @param[in] schema Schema of the new catalog to be created. Must start with self.schema (i.e. 433 schema.contains(self.schema) must be True), but typically contains fields for 434 already-configured measurement algorithms as well. 435 @param[in] randomSeed Seed for the random number generator. If None, a seed is chosen automatically. 437 @return a tuple of (exposure, catalog) 439 random_state = np.random.RandomState(randomSeed)
440 assert schema.contains(self.
schema)
442 mapper.addMinimalSchema(self.
schema,
True)
444 exposure.getMaskedImage().getVariance().getArray()[:, :] = noise**2
445 exposure.getMaskedImage().getImage().getArray()[:, :] \
446 += random_state.randn(exposure.getHeight(), exposure.getWidth())*noise
448 catalog.extend(self.
catalog, mapper=mapper)
451 for record
in catalog:
453 if record.getParent() == 0:
456 parent = catalog.find(record.getParent())
457 footprint = parent.getFootprint()
458 parentFluxArrayNoNoise = np.zeros(footprint.getArea(), dtype=np.float32)
459 footprint.spans.flatten(parentFluxArrayNoNoise,
460 self.
exposure.getMaskedImage().getImage().getArray(),
462 parentFluxArrayNoisy = np.zeros(footprint.getArea(), dtype=np.float32)
463 footprint.spans.flatten(parentFluxArrayNoisy,
464 exposure.getMaskedImage().getImage().getArray(),
466 oldHeavy = record.getFootprint()
467 fraction = (oldHeavy.getImageArray() / parentFluxArrayNoNoise)
470 newHeavy = lsst.afw.detection.HeavyFootprintF(oldHeavy)
471 newHeavy.getImageArray()[:] = parentFluxArrayNoisy*fraction
472 newHeavy.getMaskArray()[:] = oldHeavy.getMaskArray()
473 newHeavy.getVarianceArray()[:] = oldHeavy.getVarianceArray()
474 record.setFootprint(newHeavy)
475 return exposure, catalog
481 """Convenience function to create a Config instance for SingleFrameMeasurementTask 483 The plugin and its dependencies will be the only plugins run, while the Centroid, Shape, 484 and ModelFlux slots will be set to the truth fields generated by the TestDataset class. 486 config = SingleFrameMeasurementTask.ConfigClass()
487 config.slots.centroid =
"truth" 488 config.slots.shape =
"truth" 489 config.slots.modelFlux =
None 490 config.slots.apFlux =
None 491 config.slots.psfFlux =
None 492 config.slots.instFlux =
None 493 config.slots.calibFlux =
None 494 config.plugins.names = (plugin,) + tuple(dependencies)
499 """Convenience function to create a SingleFrameMeasurementTask with a simple configuration. 503 raise ValueError(
"Either plugin or config argument must not be None")
506 schema = TestDataset.makeMinimalSchema()
508 schema.setAliasMap(
None)
509 if algMetadata
is None:
514 """Convenience function to create a Config instance for ForcedMeasurementTask 516 In addition to the plugins specified in the plugin and dependencies arguments, 517 the TransformedCentroid and TransformedShape plugins will be run and used as the 518 Centroid and Shape slots; these simply transform the reference catalog centroid 519 and shape to the measurement coordinate system. 521 config = ForcedMeasurementTask.ConfigClass()
522 config.slots.centroid =
"base_TransformedCentroid" 523 config.slots.shape =
"base_TransformedShape" 524 config.slots.modelFlux =
None 525 config.slots.apFlux =
None 526 config.slots.psfFlux =
None 527 config.slots.instFlux =
None 528 config.plugins.names = (plugin,) + tuple(dependencies) + (
"base_TransformedCentroid",
529 "base_TransformedShape")
534 """Convenience function to create a ForcedMeasurementTask with a simple configuration. 538 raise ValueError(
"Either plugin or config argument must not be None")
540 if refSchema
is None:
541 refSchema = TestDataset.makeMinimalSchema()
542 if algMetadata
is None:
549 Base class for testing measurement transformations. 551 We test both that the transform itself operates successfully (fluxes are 552 converted to magnitudes, flags are propagated properly) and that the 553 transform is registered as the default for the appropriate measurement 556 In the simple case of one-measurement-per-transformation, the developer 557 need not directly write any tests themselves: simply customizing the class 558 variables is all that is required. More complex measurements (e.g. 559 multiple aperture fluxes) require extra effort. 564 name =
"MeasurementTransformTest" 568 algorithmClass =
None 569 transformClass =
None 573 flagNames = (
"flag",)
578 singleFramePlugins = ()
583 self.
calexp = TestDataset.makeEmptyExposure(bbox)
584 self._setupTransform()
593 def _populateCatalog(self, baseNames):
595 for flagValue
in (
True,
False):
596 records.append(self.inputCat.addNew())
597 for baseName
in baseNames:
599 if records[-1].schema.join(baseName, flagName)
in records[-1].schema:
600 records[-1].set(records[-1].schema.join(baseName, flagName), flagValue)
601 self._setFieldsInRecords(records, baseName)
603 def _checkOutput(self, baseNames):
604 for inSrc, outSrc
in zip(self.inputCat, self.outputCat):
605 for baseName
in baseNames:
606 self._compareFieldsInRecords(inSrc, outSrc, baseName)
608 keyName = outSrc.schema.join(baseName, flagName)
609 if keyName
in inSrc.schema:
610 self.assertEqual(outSrc.get(keyName), inSrc.get(keyName))
612 self.assertFalse(keyName
in outSrc.schema)
614 def _runTransform(self, doExtend=True):
616 self.outputCat.extend(self.inputCat, mapper=self.mapper)
617 self.transform(self.inputCat, self.outputCat, self.
calexp.getWcs(), self.
calexp.getCalib())
621 Test the operation of the transformation on a catalog containing random data. 625 * An appropriate exception is raised on an attempt to transform between catalogs with different 627 * Otherwise, all appropriate conversions are properly appled and that flags have been propagated. 629 The `baseNames` argument requires some explanation. This should be an iterable of the leading parts of 630 the field names for each measurement; that is, everything that appears before `_flux`, `_flag`, etc. 631 In the simple case of a single measurement per plugin, this is simply equal to `self.name` (thus 632 measurements are stored as `self.name + "_flux"`, etc). More generally, the developer may specify 633 whatever iterable they require. For example, to handle multiple apertures, we could have 634 `(self.name + "_0", self.name + "_1", ...)`. 636 @param[in] baseNames Iterable of the initial parts of measurement field names. 638 baseNames = baseNames
or [self.
name]
644 def _checkRegisteredTransform(self, registry, name):
647 self.assertEqual(registry[name].PluginClass.getTransformClass(), self.
transformClass)
651 Test that the transformation is appropriately registered with the relevant measurement algorithms. 661 def _setupTransform(self):
667 inputSchema.getAliasMap().set(
"slot_Centroid",
"dummy")
668 inputSchema.getAliasMap().set(
"slot_Shape",
"dummy")
669 self.algorithmClass(self.
control, self.name, inputSchema)
670 inputSchema.getAliasMap().erase(
"slot_Centroid")
671 inputSchema.getAliasMap().erase(
"slot_Shape")
680 def _setupTransform(self):
687 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"dummy")
688 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Shape",
"dummy")
690 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Centroid")
691 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Shape")
700 def _setFieldsInRecords(self, records, name):
701 for record
in records:
702 record[record.schema.join(name,
'flux')] = np.random.random()
703 record[record.schema.join(name,
'fluxSigma')] = np.random.random()
706 assert len(records) > 1
707 records[0][record.schema.join(name,
'flux')] = -1
709 def _compareFieldsInRecords(self, inSrc, outSrc, name):
710 fluxName, fluxSigmaName = inSrc.schema.join(name,
'flux'), inSrc.schema.join(name,
'fluxSigma')
711 if inSrc[fluxName] > 0:
712 mag, magErr = self.
calexp.getCalib().getMagnitude(inSrc[fluxName], inSrc[fluxSigmaName])
713 self.assertEqual(outSrc[outSrc.schema.join(name,
'mag')], mag)
714 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], magErr)
716 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'mag')]))
717 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'magErr')]))
722 def _setFieldsInRecords(self, records, name):
723 for record
in records:
724 record[record.schema.join(name,
'x')] = np.random.random()
725 record[record.schema.join(name,
'y')] = np.random.random()
728 for fieldSuffix
in (
'xSigma',
'ySigma',
'x_y_Cov'):
729 fieldName = record.schema.join(name, fieldSuffix)
730 if fieldName
in record.schema:
731 record[fieldName] = np.random.random()
733 def _compareFieldsInRecords(self, inSrc, outSrc, name):
734 centroidResultKey = CentroidResultKey(inSrc.schema[self.
name])
735 centroidResult = centroidResultKey.get(inSrc)
738 coordTruth = self.
calexp.getWcs().pixelToSky(centroidResult.getCentroid())
739 self.assertEqual(coordTruth, coord)
744 coordErr = lsst.afw.table.CovarianceMatrix2fKey(outSrc.schema[self.
name],
745 [
"ra",
"dec"]).get(outSrc)
747 self.assertFalse(centroidResultKey.getCentroidErr().isValid())
749 transform = self.
calexp.getWcs().linearizePixelToSky(coordTruth, lsst.afw.geom.radians)
750 coordErrTruth = np.dot(np.dot(transform.getLinear().getMatrix(),
751 centroidResult.getCentroidErr()),
752 transform.getLinear().getMatrix().transpose())
753 np.testing.assert_array_almost_equal(np.array(coordErrTruth), coordErr)
def makeSingleFrameMeasurementConfig(self, plugin=None, dependencies=())
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
def transform(self, wcs, kwds)
Create a copy of the dataset transformed to a new WCS, with new Psf and Calib.
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 realize(self, noise, schema, randomSeed=1)
Create a simulated with noise and a simulated post-detection catalog with (Heavy)Footprints.
AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, Point2D const &inPoint)
def makePerturbedWcs(oldWcs, minScaleFactor=1.2, maxScaleFactor=1.5, minRotation=None, maxRotation=None, minRefShift=None, maxRefShift=None, minPixShift=2.0, maxPixShift=4.0, randomSeed=1)
Create a new undistorted TAN WCS that is similar but not identical to another, with random scaling...
def __init__(self, owner)
static QuadrupoleKey addFields(Schema &schema, std::string const &name, std::string const &doc, CoordinateType coordType=CoordinateType::PIXEL)
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.
static Schema makeMinimalSchema()
std::shared_ptr< SkyWcs > makeSkyWcs(Point2D const &crpix, SpherePoint const &crval, Eigen::Matrix2d const &cdMatrix, std::string const &projection="TAN")
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...
Eigen::Matrix2d makeCdMatrix(Angle const &scale, Angle const &orientation=0 *degrees, bool flipX=false)
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...