31 from .sfm
import SingleFrameMeasurementTask
32 from .forcedMeasurement
import ForcedMeasurementTask
33 from .
import CentroidResultKey
35 __all__ = (
"BlendContext",
"TestDataset",
"AlgorithmTestCase",
"TransformTestCase",
36 "SingleFramePluginTransformSetupHelper",
"ForcedPluginTransformSetupHelper",
37 "FluxTransformTestCase",
"CentroidTransformTestCase")
41 """Context manager which adds multiple overlapping sources and a parent.
45 This is used as the return value for `TestDataset.addBlend`, and this is
46 the only way it should be used.
59 def addChild(self, instFlux, centroid, shape=None):
60 """Add a child to the blend; return corresponding truth catalog record.
63 Total instFlux of the source to be added.
64 centroid : `lsst.geom.Point2D`
65 Position of the source to be added.
66 shape : `lsst.afw.geom.Quadrupole`
67 Second moments of the source before PSF convolution. Note that
68 the truth catalog records post-convolution moments)
70 record, image = self.
owner.addSource(instFlux, centroid, shape)
89 instFlux += record.get(self.
owner.keys[
"instFlux"])
95 w = record.get(self.
owner.keys[
"instFlux"])/instFlux
96 x += record.get(self.
owner.keys[
"centroid"].getX())*w
97 y += record.get(self.
owner.keys[
"centroid"].getY())*w
104 w = record.get(self.
owner.keys[
"instFlux"])/instFlux
105 dx = record.get(self.
owner.keys[
"centroid"].getX()) - x
106 dy = record.get(self.
owner.keys[
"centroid"].getY()) - y
107 xx += (record.get(self.
owner.keys[
"shape"].getIxx()) + dx**2)*w
108 yy += (record.get(self.
owner.keys[
"shape"].getIyy()) + dy**2)*w
109 xy += (record.get(self.
owner.keys[
"shape"].getIxy()) + dx*dy)*w
115 deblend = lsst.afw.image.MaskedImageF(self.
owner.exposure.getMaskedImage(),
True)
117 deblend.getImage().getArray()[:, :] = image.getArray()
118 heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.
parentRecord.getFootprint(), deblend)
119 record.setFootprint(heavyFootprint)
123 """A simulated dataset consisuting of test image and truth catalog.
125 TestDataset creates an idealized image made of pure Gaussians (including a
126 Gaussian PSF), with simple noise and idealized Footprints/HeavyFootprints
127 that simulated the outputs of detection and deblending. Multiple noise
128 realizations can be created from the same underlying sources, allowing
129 uncertainty estimates to be verified via Monte Carlo.
133 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
134 Bounding box of the test image.
136 Threshold absolute value used to determine footprints for
137 simulated sources. This thresholding will be applied before noise is
138 actually added to images (or before the noise level is even known), so
139 this will necessarily produce somewhat artificial footprints.
140 exposure : `lsst.afw.image.ExposureF`
141 The image to which test sources should be added. Ownership should
142 be considered transferred from the caller to the TestDataset.
143 Must have a Gaussian PSF for truth catalog shapes to be exact.
145 Keyword arguments forwarded to makeEmptyExposure if exposure is `None`.
153 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0,0), lsst.geom.Point2I(100,
155 dataset = TestDataset(bbox)
156 dataset.addSource(flux=1E5, centroid=lsst.geom.Point2D(25, 26))
157 dataset.addSource(flux=2E5, centroid=lsst.geom.Point2D(75, 24),
158 shape=lsst.afw.geom.Quadrupole(8, 7, 2))
159 with dataset.addBlend() as family:
160 family.addChild(flux=2E5, centroid=lsst.geom.Point2D(50, 72))
161 family.addChild(flux=1.5E5, centroid=lsst.geom.Point2D(51, 74))
162 exposure, catalog = dataset.realize(noise=100.0,
163 schema=TestDataset.makeMinimalSchema())
168 """Return the minimal schema needed to hold truth catalog fields.
172 When `TestDataset.realize` is called, the schema must include at least
173 these fields. Usually it will include additional fields for
174 measurement algorithm outputs, allowing the same catalog to be used
175 for both truth values (the fields from the minimal schema) and the
178 if not hasattr(cls,
"_schema"):
181 cls.
keys[
"parent"] = schema.find(
"parent").key
182 cls.
keys[
"nChild"] = schema.addField(
"deblend_nChild", type=np.int32)
183 cls.
keys[
"instFlux"] = schema.addField(
"truth_instFlux", type=np.float64,
184 doc=
"true instFlux", units=
"count")
185 cls.
keys[
"centroid"] = lsst.afw.table.Point2DKey.addFields(
186 schema,
"truth",
"true simulated centroid",
"pixel"
188 cls.
keys[
"centroid_sigma"] = lsst.afw.table.CovarianceMatrix2fKey.addFields(
189 schema,
"truth", [
'x',
'y'],
"pixel"
191 cls.
keys[
"centroid_flag"] = schema.addField(
"truth_flag", type=
"Flag",
192 doc=
"set if the object is a star")
194 schema,
"truth",
"true shape after PSF convolution", lsst.afw.table.CoordinateType.PIXEL
196 cls.
keys[
"isStar"] = schema.addField(
"truth_isStar", type=
"Flag",
197 doc=
"set if the object is a star")
198 schema.getAliasMap().
set(
"slot_Shape",
"truth")
199 schema.getAliasMap().
set(
"slot_Centroid",
"truth")
200 schema.getAliasMap().
set(
"slot_ModelFlux",
"truth")
203 schema.disconnectAliases()
208 minRotation=None, maxRotation=None,
209 minRefShift=None, maxRefShift=None,
210 minPixShift=2.0, maxPixShift=4.0, randomSeed=1):
211 """Return a perturbed version of the input WCS.
213 Create a new undistorted TAN WCS that is similar but not identical to
214 another, with random scaling, rotation, and offset (in both pixel
215 position and reference position).
219 oldWcs : `lsst.afw.geom.SkyWcs`
221 minScaleFactor : `float`
222 Minimum scale factor to apply to the input WCS.
223 maxScaleFactor : `float`
224 Maximum scale factor to apply to the input WCS.
225 minRotation : `lsst.geom.Angle` or `None`
226 Minimum rotation to apply to the input WCS. If `None`, defaults to
228 maxRotation : `lsst.geom.Angle` or `None`
229 Minimum rotation to apply to the input WCS. If `None`, defaults to
231 minRefShift : `lsst.geom.Angle` or `None`
232 Miniumum shift to apply to the input WCS reference value. If
233 `None`, defaults to 0.5 arcsec.
234 maxRefShift : `lsst.geom.Angle` or `None`
235 Miniumum shift to apply to the input WCS reference value. If
236 `None`, defaults to 1.0 arcsec.
237 minPixShift : `float`
238 Minimum shift to apply to the input WCS reference pixel.
239 maxPixShift : `float`
240 Maximum shift to apply to the input WCS reference pixel.
246 newWcs : `lsst.afw.geom.SkyWcs`
247 A perturbed version of the input WCS.
251 The maximum and minimum arguments are interpreted as absolute values
252 for a split range that covers both positive and negative values (as
253 this method is used in testing, it is typically most important to
254 avoid perturbations near zero). Scale factors are treated somewhat
255 differently: the actual scale factor is chosen between
256 ``minScaleFactor`` and ``maxScaleFactor`` OR (``1/maxScaleFactor``)
257 and (``1/minScaleFactor``).
259 The default range for rotation is 30-60 degrees, and the default range
260 for reference shift is 0.5-1.0 arcseconds (these cannot be safely
261 included directly as default values because Angle objects are
264 The random number generator is primed with the seed given. If
265 `None`, a seed is automatically chosen.
267 random_state = np.random.RandomState(randomSeed)
268 if minRotation
is None:
269 minRotation = 30.0*lsst.geom.degrees
270 if maxRotation
is None:
271 maxRotation = 60.0*lsst.geom.degrees
272 if minRefShift
is None:
273 minRefShift = 0.5*lsst.geom.arcseconds
274 if maxRefShift
is None:
275 maxRefShift = 1.0*lsst.geom.arcseconds
277 def splitRandom(min1, max1, min2=None, max2=None):
282 if random_state.uniform() > 0.5:
283 return float(random_state.uniform(min1, max1))
285 return float(random_state.uniform(min2, max2))
287 scaleFactor = splitRandom(minScaleFactor, maxScaleFactor, 1.0/maxScaleFactor, 1.0/minScaleFactor)
288 rotation = splitRandom(minRotation.asRadians(), maxRotation.asRadians())*lsst.geom.radians
289 refShiftRa = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.geom.radians
290 refShiftDec = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.geom.radians
291 pixShiftX = splitRandom(minPixShift, maxPixShift)
292 pixShiftY = splitRandom(minPixShift, maxPixShift)
297 newTransform = oldTransform*rTransform*sTransform
298 matrix = newTransform.getMatrix()
300 oldSkyOrigin = oldWcs.getSkyOrigin()
302 oldSkyOrigin.getDec() + refShiftDec)
304 oldPixOrigin = oldWcs.getPixelOrigin()
306 oldPixOrigin.getY() + pixShiftY)
310 def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, calibration=4):
311 """Create an Exposure, with a PhotoCalib, Wcs, and Psf, but no pixel values.
315 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
316 Bounding box of the image in image coordinates.
317 wcs : `lsst.afw.geom.SkyWcs`, optional
318 New WCS for the exposure (created from CRVAL and CDELT if `None`).
319 crval : `lsst.afw.geom.SpherePoint`, optional
320 ICRS center of the TAN WCS attached to the image. If `None`, (45
321 degrees, 45 degrees) is assumed.
322 cdelt : `lsst.geom.Angle`, optional
323 Pixel scale of the image. If `None`, 0.2 arcsec is assumed.
324 psfSigma : `float`, optional
325 Radius (sigma) of the Gaussian PSF attached to the image
326 psfDim : `int`, optional
327 Width and height of the image's Gaussian PSF attached to the image
328 calibration : `float`, optional
329 The spatially-constant calibration (in nJy/count) to set the
330 PhotoCalib of the exposure.
334 exposure : `lsst.age.image.ExposureF`
341 cdelt = 0.2*lsst.geom.arcseconds
345 exposure = lsst.afw.image.ExposureF(bbox)
350 exposure.setPhotoCalib(photoCalib)
355 """Create an image of an elliptical Gaussian.
359 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
360 Bounding box of image to create.
362 Total instrumental flux of the Gaussian (normalized analytically,
363 not using pixel values).
364 ellipse : `lsst.afw.geom.Ellipse`
365 Defines the centroid and shape.
369 image : `lsst.afw.image.ImageF`
370 An image of the Gaussian.
372 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX()),
373 np.arange(bbox.getBeginY(), bbox.getEndY()))
374 t = ellipse.getGridTransform()
375 xt = t[t.XX] * x + t[t.XY] * y + t[t.X]
376 yt = t[t.YX] * x + t[t.YY] * y + t[t.Y]
377 image = lsst.afw.image.ImageF(bbox)
378 image.getArray()[:, :] = np.exp(-0.5*(xt**2 + yt**2))*instFlux/(2.0*ellipse.getCore().getArea())
381 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
390 def _installFootprint(self, record, image):
391 """Create simulated Footprint and add it to a truth catalog record.
398 fpSet.setMask(self.
exposure.getMaskedImage().getMask(),
"DETECTED")
400 if len(fpSet.getFootprints()) > 1:
401 raise RuntimeError(
"Threshold value results in multiple Footprints for a single object")
402 if len(fpSet.getFootprints()) == 0:
403 raise RuntimeError(
"Threshold value results in zero Footprints for object")
404 record.setFootprint(fpSet.getFootprints()[0])
407 """Add a source to the simulation.
412 Total instFlux of the source to be added.
413 centroid : `lsst.geom.Point2D`
414 Position of the source to be added.
415 shape : `lsst.afw.geom.Quadrupole`
416 Second moments of the source before PSF convolution. Note that the
417 truth catalog records post-convolution moments. If `None`, a point
418 source will be added.
422 record : `lsst.afw.table.SourceRecord`
423 A truth catalog record.
424 image : `lsst.afw.image.ImageF`
425 Single-source image corresponding to the new source.
429 record.set(self.
keys[
"instFlux"], instFlux)
430 record.set(self.
keys[
"centroid"], centroid)
431 covariance = np.random.normal(0, 0.1, 4).reshape(2, 2)
432 covariance[0, 1] = covariance[1, 0]
433 record.set(self.
keys[
"centroid_sigma"], covariance.astype(np.float32))
435 record.set(self.
keys[
"isStar"],
True)
438 record.set(self.
keys[
"isStar"],
False)
439 fullShape = shape.convolve(self.
psfShape)
440 record.set(self.
keys[
"shape"], fullShape)
447 self.
exposure.getMaskedImage().getImage().getArray()[:, :] += image.getArray()
451 """Return a context manager which can add a blend of multiple sources.
455 Note that nothing stops you from creating overlapping sources just using the addSource() method,
456 but addBlend() is necesssary to create a parent object and deblended HeavyFootprints of the type
457 produced by the detection and deblending pipelines.
463 with d.addBlend() as b:
464 b.addChild(flux1, centroid1)
465 b.addChild(flux2, centroid2, shape2)
470 """Copy this dataset transformed to a new WCS, with new Psf and PhotoCalib.
474 wcs : `lsst.afw.geom.SkyWcs`
475 WCS for the new dataset.
477 Additional keyword arguments passed on to
478 `TestDataset.makeEmptyExposure`. If not specified, these revert
479 to the defaults for `~TestDataset.makeEmptyExposure`, not the
480 values in the current dataset.
484 newDataset : `TestDataset`
485 Transformed copy of this dataset.
493 oldPhotoCalib = self.
exposure.getPhotoCalib()
494 newPhotoCalib = result.exposure.getPhotoCalib()
495 oldPsfShape = self.
exposure.getPsf().computeShape()
497 if record.get(self.
keys[
"nChild"]):
498 raise NotImplementedError(
"Transforming blended sources in TestDatasets is not supported")
499 magnitude = oldPhotoCalib.instFluxToMagnitude(record.get(self.
keys[
"instFlux"]))
500 newFlux = newPhotoCalib.magnitudeToInstFlux(magnitude)
501 oldCentroid = record.get(self.
keys[
"centroid"])
502 newCentroid = xyt.applyForward(oldCentroid)
503 if record.get(self.
keys[
"isStar"]):
504 newDeconvolvedShape =
None
507 oldFullShape = record.get(self.
keys[
"shape"])
509 oldFullShape.getIxx() - oldPsfShape.getIxx(),
510 oldFullShape.getIyy() - oldPsfShape.getIyy(),
511 oldFullShape.getIxy() - oldPsfShape.getIxy(),
514 newDeconvolvedShape = oldDeconvolvedShape.transform(affine.getLinear())
515 result.addSource(newFlux, newCentroid, newDeconvolvedShape)
518 def realize(self, noise, schema, randomSeed=1):
519 r"""Simulate an exposure and detection catalog for this dataset.
521 The simulation includes noise, and the detection catalog includes
522 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s.
527 Standard deviation of noise to be added to the exposure. The
528 noise will be Gaussian and constant, appropriate for the
530 schema : `lsst.afw.table.Schema`
531 Schema of the new catalog to be created. Must start with
532 ``self.schema`` (i.e. ``schema.contains(self.schema)`` must be
533 `True`), but typically contains fields for already-configured
534 measurement algorithms as well.
535 randomSeed : `int`, optional
536 Seed for the random number generator.
537 If `None`, a seed is chosen automatically.
541 `exposure` : `lsst.afw.image.ExposureF`
543 `catalog` : `lsst.afw.table.SourceCatalog`
544 Simulated detection catalog.
546 random_state = np.random.RandomState(randomSeed)
547 assert schema.contains(self.
schema)
549 mapper.addMinimalSchema(self.
schema,
True)
551 exposure.getMaskedImage().getVariance().getArray()[:, :] = noise**2
552 exposure.getMaskedImage().getImage().getArray()[:, :] \
553 += random_state.randn(exposure.getHeight(), exposure.getWidth())*noise
555 catalog.extend(self.
catalog, mapper=mapper)
558 for record
in catalog:
561 if record.getParent() == 0:
565 parent = catalog.find(record.getParent())
566 footprint = parent.getFootprint()
567 parentFluxArrayNoNoise = np.zeros(footprint.getArea(), dtype=np.float32)
568 footprint.spans.flatten(parentFluxArrayNoNoise,
569 self.
exposure.getMaskedImage().getImage().getArray(),
571 parentFluxArrayNoisy = np.zeros(footprint.getArea(), dtype=np.float32)
572 footprint.spans.flatten(parentFluxArrayNoisy,
573 exposure.getMaskedImage().getImage().getArray(),
575 oldHeavy = record.getFootprint()
576 fraction = (oldHeavy.getImageArray() / parentFluxArrayNoNoise)
580 newHeavy = lsst.afw.detection.HeavyFootprintF(oldHeavy)
581 newHeavy.getImageArray()[:] = parentFluxArrayNoisy*fraction
582 newHeavy.getMaskArray()[:] = oldHeavy.getMaskArray()
583 newHeavy.getVarianceArray()[:] = oldHeavy.getVarianceArray()
584 record.setFootprint(newHeavy)
585 return exposure, catalog
591 """Create an instance of `SingleFrameMeasurementTask.ConfigClass`.
593 Only the specified plugin and its dependencies will be run; the
594 Centroid, Shape, and ModelFlux slots will be set to the truth fields
595 generated by the `TestDataset` class.
600 Name of measurement plugin to enable.
601 dependencies : iterable of `str`, optional
602 Names of dependencies of the measurement plugin.
606 config : `SingleFrameMeasurementTask.ConfigClass`
607 The resulting task configuration.
609 config = SingleFrameMeasurementTask.ConfigClass()
610 config.slots.centroid =
"truth"
611 config.slots.shape =
"truth"
612 config.slots.modelFlux =
None
613 config.slots.apFlux =
None
614 config.slots.psfFlux =
None
615 config.slots.gaussianFlux =
None
616 config.slots.calibFlux =
None
617 config.plugins.names = (plugin,) + tuple(dependencies)
622 """Create a configured instance of `SingleFrameMeasurementTask`.
626 plugin : `str`, optional
627 Name of measurement plugin to enable. If `None`, a configuration
628 must be supplied as the ``config`` parameter. If both are
629 specified, ``config`` takes precedence.
630 dependencies : iterable of `str`, optional
631 Names of dependencies of the specified measurement plugin.
632 config : `SingleFrameMeasurementTask.ConfigClass`, optional
633 Configuration for the task. If `None`, a measurement plugin must
634 be supplied as the ``plugin`` paramter. If both are specified,
635 ``config`` takes precedence.
636 schema : `lsst.afw.table.Schema`, optional
637 Measurement table schema. If `None`, a default schema is
639 algMetadata : `lsst.daf.base.PropertyList`, optional
640 Measurement algorithm metadata. If `None`, a default container
645 task : `SingleFrameMeasurementTask`
646 A configured instance of the measurement task.
650 raise ValueError(
"Either plugin or config argument must not be None")
653 schema = TestDataset.makeMinimalSchema()
655 schema.setAliasMap(
None)
656 if algMetadata
is None:
661 """Create an instance of `ForcedMeasurementTask.ConfigClass`.
663 In addition to the plugins specified in the plugin and dependencies
664 arguments, the `TransformedCentroid` and `TransformedShape` plugins
665 will be run and used as the centroid and shape slots; these simply
666 transform the reference catalog centroid and shape to the measurement
672 Name of measurement plugin to enable.
673 dependencies : iterable of `str`, optional
674 Names of dependencies of the measurement plugin.
678 config : `ForcedMeasurementTask.ConfigClass`
679 The resulting task configuration.
682 config = ForcedMeasurementTask.ConfigClass()
683 config.slots.centroid =
"base_TransformedCentroid"
684 config.slots.shape =
"base_TransformedShape"
685 config.slots.modelFlux =
None
686 config.slots.apFlux =
None
687 config.slots.psfFlux =
None
688 config.slots.gaussianFlux =
None
689 config.plugins.names = (plugin,) + tuple(dependencies) + (
"base_TransformedCentroid",
690 "base_TransformedShape")
695 """Create a configured instance of `ForcedMeasurementTask`.
699 plugin : `str`, optional
700 Name of measurement plugin to enable. If `None`, a configuration
701 must be supplied as the ``config`` parameter. If both are
702 specified, ``config`` takes precedence.
703 dependencies : iterable of `str`, optional
704 Names of dependencies of the specified measurement plugin.
705 config : `SingleFrameMeasurementTask.ConfigClass`, optional
706 Configuration for the task. If `None`, a measurement plugin must
707 be supplied as the ``plugin`` paramter. If both are specified,
708 ``config`` takes precedence.
709 refSchema : `lsst.afw.table.Schema`, optional
710 Reference table schema. If `None`, a default schema is
712 algMetadata : `lsst.daf.base.PropertyList`, optional
713 Measurement algorithm metadata. If `None`, a default container
718 task : `ForcedMeasurementTask`
719 A configured instance of the measurement task.
723 raise ValueError(
"Either plugin or config argument must not be None")
725 if refSchema
is None:
726 refSchema = TestDataset.makeMinimalSchema()
727 if algMetadata
is None:
733 """Base class for testing measurement transformations.
737 We test both that the transform itself operates successfully (fluxes are
738 converted to magnitudes, flags are propagated properly) and that the
739 transform is registered as the default for the appropriate measurement
742 In the simple case of one-measurement-per-transformation, the developer
743 need not directly write any tests themselves: simply customizing the class
744 variables is all that is required. More complex measurements (e.g.
745 multiple aperture fluxes) require extra effort.
747 name =
"MeasurementTransformTest"
748 """The name used for the measurement algorithm (str).
752 This determines the names of the fields in the resulting catalog. This
753 default should generally be fine, but subclasses can override if
759 algorithmClass =
None
760 transformClass =
None
762 flagNames = (
"flag",)
763 """Flags which may be set by the algorithm being tested (iterable of `str`).
769 singleFramePlugins = ()
774 self.
calexp = TestDataset.makeEmptyExposure(bbox)
775 self._setupTransform()
784 def _populateCatalog(self, baseNames):
786 for flagValue
in (
True,
False):
787 records.append(self.inputCat.addNew())
788 for baseName
in baseNames:
790 if records[-1].schema.join(baseName, flagName)
in records[-1].schema:
791 records[-1].
set(records[-1].schema.join(baseName, flagName), flagValue)
792 self._setFieldsInRecords(records, baseName)
794 def _checkOutput(self, baseNames):
795 for inSrc, outSrc
in zip(self.inputCat, self.outputCat):
796 for baseName
in baseNames:
797 self._compareFieldsInRecords(inSrc, outSrc, baseName)
799 keyName = outSrc.schema.join(baseName, flagName)
800 if keyName
in inSrc.schema:
801 self.assertEqual(outSrc.get(keyName), inSrc.get(keyName))
803 self.assertFalse(keyName
in outSrc.schema)
805 def _runTransform(self, doExtend=True):
807 self.outputCat.extend(self.inputCat, mapper=self.mapper)
808 self.transform(self.inputCat, self.outputCat, self.
calexp.getWcs(), self.
calexp.getPhotoCalib())
811 """Test the transformation on a catalog containing random data.
815 baseNames : iterable of `str`
816 Iterable of the initial parts of measurement field names.
822 - An appropriate exception is raised on an attempt to transform
823 between catalogs with different numbers of rows;
824 - Otherwise, all appropriate conversions are properly appled and that
825 flags have been propagated.
827 The ``baseNames`` argument requires some explanation. This should be
828 an iterable of the leading parts of the field names for each
829 measurement; that is, everything that appears before ``_instFlux``,
830 ``_flag``, etc. In the simple case of a single measurement per plugin,
831 this is simply equal to ``self.name`` (thus measurements are stored as
832 ``self.name + "_instFlux"``, etc). More generally, the developer may
833 specify whatever iterable they require. For example, to handle
834 multiple apertures, we could have ``(self.name + "_0", self.name +
837 baseNames = baseNames
or [self.
name]
843 def _checkRegisteredTransform(self, registry, name):
846 self.assertEqual(registry[name].PluginClass.getTransformClass(), self.
transformClass)
849 """Test that the transformation is appropriately registered.
859 def _setupTransform(self):
865 inputSchema.getAliasMap().
set(
"slot_Centroid",
"dummy")
866 inputSchema.getAliasMap().
set(
"slot_Shape",
"dummy")
867 self.algorithmClass(self.
control, self.name, inputSchema)
868 inputSchema.getAliasMap().
erase(
"slot_Centroid")
869 inputSchema.getAliasMap().
erase(
"slot_Shape")
878 def _setupTransform(self):
885 inputMapper.editOutputSchema().getAliasMap().
set(
"slot_Centroid",
"dummy")
886 inputMapper.editOutputSchema().getAliasMap().
set(
"slot_Shape",
"dummy")
888 inputMapper.editOutputSchema().getAliasMap().
erase(
"slot_Centroid")
889 inputMapper.editOutputSchema().getAliasMap().
erase(
"slot_Shape")
898 def _setFieldsInRecords(self, records, name):
899 for record
in records:
900 record[record.schema.join(name,
'instFlux')] = np.random.random()
901 record[record.schema.join(name,
'instFluxErr')] = np.random.random()
904 assert len(records) > 1
905 records[0][record.schema.join(name,
'instFlux')] = -1
907 def _compareFieldsInRecords(self, inSrc, outSrc, name):
908 instFluxName = inSrc.schema.join(name,
'instFlux')
909 instFluxErrName = inSrc.schema.join(name,
'instFluxErr')
910 if inSrc[instFluxName] > 0:
911 mag = self.
calexp.getPhotoCalib().instFluxToMagnitude(inSrc[instFluxName],
912 inSrc[instFluxErrName])
913 self.assertEqual(outSrc[outSrc.schema.join(name,
'mag')], mag.value)
914 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], mag.error)
917 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'mag')]))
918 if np.isnan(inSrc[instFluxErrName]):
919 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'magErr')]))
921 mag = self.
calexp.getPhotoCalib().instFluxToMagnitude(inSrc[instFluxName],
922 inSrc[instFluxErrName])
923 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], mag.error)
928 def _setFieldsInRecords(self, records, name):
929 for record
in records:
930 record[record.schema.join(name,
'x')] = np.random.random()
931 record[record.schema.join(name,
'y')] = np.random.random()
934 for fieldSuffix
in (
'xErr',
'yErr',
'x_y_Cov'):
935 fieldName = record.schema.join(name, fieldSuffix)
936 if fieldName
in record.schema:
937 record[fieldName] = np.random.random()
939 def _compareFieldsInRecords(self, inSrc, outSrc, name):
940 centroidResultKey = CentroidResultKey(inSrc.schema[self.
name])
941 centroidResult = centroidResultKey.get(inSrc)
944 coordTruth = self.
calexp.getWcs().pixelToSky(centroidResult.getCentroid())
945 self.assertEqual(coordTruth, coord)
950 coordErr = lsst.afw.table.CovarianceMatrix2fKey(outSrc.schema[self.
name],
951 [
"ra",
"dec"]).get(outSrc)
953 self.assertFalse(centroidResultKey.getCentroidErr().
isValid())
955 transform = self.
calexp.getWcs().linearizePixelToSky(coordTruth, lsst.geom.radians)
956 coordErrTruth = np.dot(np.dot(transform.getLinear().getMatrix(),
957 centroidResult.getCentroidErr()),
958 transform.getLinear().getMatrix().transpose())
959 np.testing.assert_array_almost_equal(np.array(coordErrTruth), coordErr)