35from .sfm
import SingleFrameMeasurementTask
36from .forcedMeasurement
import ForcedMeasurementTask
37from ._measBaseLib
import CentroidResultKey
39__all__ = (
"BlendContext",
"TestDataset",
"AlgorithmTestCase",
"TransformTestCase",
40 "SingleFramePluginTransformSetupHelper",
"ForcedPluginTransformSetupHelper",
41 "FluxTransformTestCase",
"CentroidTransformTestCase")
45 """Context manager which adds multiple overlapping sources and a parent.
49 This is used
as the
return value
for `TestDataset.addBlend`,
and this
is
50 the only way it should be used.
63 def addChild(self, instFlux, centroid, shape=None):
64 """Add a child to the blend; return corresponding truth catalog record.
67 Total instFlux of the source to be added.
69 Position of the source to be added.
70 shape : `lsst.afw.geom.Quadrupole`
71 Second moments of the source before PSF convolution. Note that
72 the truth catalog records post-convolution moments)
74 record, image = self.owner.addSource(instFlux, centroid, shape)
77 self.
children.append((record, image))
93 instFlux += record.get(self.
owner.keys[
"instFlux"])
99 w = record.get(self.
owner.keys[
"instFlux"])/instFlux
100 x += record.get(self.
owner.keys[
"centroid"].getX())*w
101 y += record.get(self.
owner.keys[
"centroid"].getY())*w
108 w = record.get(self.
owner.keys[
"instFlux"])/instFlux
109 dx = record.get(self.
owner.keys[
"centroid"].getX()) - x
110 dy = record.get(self.
owner.keys[
"centroid"].getY()) - y
111 xx += (record.get(self.
owner.keys[
"shape"].getIxx()) + dx**2)*w
112 yy += (record.get(self.
owner.keys[
"shape"].getIyy()) + dy**2)*w
113 xy += (record.get(self.
owner.keys[
"shape"].getIxy()) + dx*dy)*w
114 self.
parentRecord.set(self.
owner.keys[
"shape"], lsst.afw.geom.Quadrupole(xx, yy, xy))
119 deblend = lsst.afw.image.MaskedImageF(self.
owner.exposure.maskedImage,
True)
121 deblend.image.array[:, :] = image.array
122 heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.
parentRecord.getFootprint(), deblend)
123 record.setFootprint(heavyFootprint)
127 """A simulated dataset consisuting of test image and truth catalog.
129 TestDataset creates an idealized image made of pure Gaussians (including a
130 Gaussian PSF), with simple noise
and idealized Footprints/HeavyFootprints
131 that simulated the outputs of detection
and deblending. Multiple noise
132 realizations can be created
from the same underlying sources, allowing
133 uncertainty estimates to be verified via Monte Carlo.
138 Bounding box of the test image.
140 Threshold absolute value used to determine footprints
for
141 simulated sources. This thresholding will be applied before noise
is
142 actually added to images (
or before the noise level
is even known), so
143 this will necessarily produce somewhat artificial footprints.
144 exposure : `lsst.afw.image.ExposureF`
145 The image to which test sources should be added. Ownership should
146 be considered transferred
from the caller to the TestDataset.
147 Must have a Gaussian PSF
for truth catalog shapes to be exact.
149 Keyword arguments forwarded to makeEmptyExposure
if exposure
is `
None`.
162 shape=lsst.afw.geom.Quadrupole(8, 7, 2))
163 with dataset.addBlend()
as family:
166 exposure, catalog = dataset.realize(noise=100.0,
167 schema=TestDataset.makeMinimalSchema())
170 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
181 """Return the minimal schema needed to hold truth catalog fields.
185 When `TestDataset.realize` is called, the schema must include at least
186 these fields. Usually it will include additional fields
for
187 measurement algorithm outputs, allowing the same catalog to be used
188 for both truth values (the fields
from the minimal schema)
and the
191 if not hasattr(cls,
"_schema"):
194 cls.
keys[
"parent"] = schema.find(
"parent").key
195 cls.
keys[
"nChild"] = schema.addField(
"deblend_nChild", type=np.int32)
196 cls.
keys[
"instFlux"] = schema.addField(
"truth_instFlux", type=np.float64,
197 doc=
"true instFlux", units=
"count")
198 cls.
keys[
"instFluxErr"] = schema.addField(
"truth_instFluxErr", type=np.float64,
199 doc=
"true instFluxErr", units=
"count")
200 cls.
keys[
"centroid"] = lsst.afw.table.Point2DKey.addFields(
201 schema,
"truth",
"true simulated centroid",
"pixel"
203 cls.
keys[
"centroid_sigma"] = lsst.afw.table.CovarianceMatrix2fKey.addFields(
204 schema,
"truth", [
'x',
'y'],
"pixel"
206 cls.
keys[
"centroid_flag"] = schema.addField(
"truth_flag", type=
"Flag",
207 doc=
"set if the object is a star")
209 schema,
"truth",
"true shape after PSF convolution", lsst.afw.table.CoordinateType.PIXEL
211 cls.
keys[
"isStar"] = schema.addField(
"truth_isStar", type=
"Flag",
212 doc=
"set if the object is a star")
213 schema.getAliasMap().set(
"slot_Shape",
"truth")
214 schema.getAliasMap().set(
"slot_Centroid",
"truth")
215 schema.getAliasMap().set(
"slot_ModelFlux",
"truth")
218 schema.disconnectAliases()
223 minRotation=None, maxRotation=None,
224 minRefShift=None, maxRefShift=None,
225 minPixShift=2.0, maxPixShift=4.0, randomSeed=1):
226 """Return a perturbed version of the input WCS.
228 Create a new undistorted TAN WCS that is similar but
not identical to
229 another,
with random scaling, rotation,
and offset (
in both pixel
230 position
and reference position).
236 minScaleFactor : `float`
237 Minimum scale factor to apply to the input WCS.
238 maxScaleFactor : `float`
239 Maximum scale factor to apply to the input WCS.
241 Minimum rotation to apply to the input WCS. If `
None`, defaults to
244 Minimum rotation to apply to the input WCS. If `
None`, defaults to
247 Miniumum shift to apply to the input WCS reference value. If
248 `
None`, defaults to 0.5 arcsec.
250 Miniumum shift to apply to the input WCS reference value. If
251 `
None`, defaults to 1.0 arcsec.
252 minPixShift : `float`
253 Minimum shift to apply to the input WCS reference pixel.
254 maxPixShift : `float`
255 Maximum shift to apply to the input WCS reference pixel.
262 A perturbed version of the input WCS.
266 The maximum
and minimum arguments are interpreted
as absolute values
267 for a split range that covers both positive
and negative values (
as
268 this method
is used
in testing, it
is typically most important to
269 avoid perturbations near zero). Scale factors are treated somewhat
270 differently: the actual scale factor
is chosen between
271 ``minScaleFactor``
and ``maxScaleFactor`` OR (``1/maxScaleFactor``)
272 and (``1/minScaleFactor``).
274 The default range
for rotation
is 30-60 degrees,
and the default range
275 for reference shift
is 0.5-1.0 arcseconds (these cannot be safely
276 included directly
as default values because Angle objects are
279 The random number generator
is primed
with the seed given. If
280 `
None`, a seed
is automatically chosen.
282 random_state = np.random.RandomState(randomSeed)
283 if minRotation
is None:
284 minRotation = 30.0*lsst.geom.degrees
285 if maxRotation
is None:
286 maxRotation = 60.0*lsst.geom.degrees
287 if minRefShift
is None:
288 minRefShift = 0.5*lsst.geom.arcseconds
289 if maxRefShift
is None:
290 maxRefShift = 1.0*lsst.geom.arcseconds
292 def splitRandom(min1, max1, min2=None, max2=None):
297 if random_state.uniform() > 0.5:
298 return float(random_state.uniform(min1, max1))
300 return float(random_state.uniform(min2, max2))
302 scaleFactor = splitRandom(minScaleFactor, maxScaleFactor, 1.0/maxScaleFactor, 1.0/minScaleFactor)
303 rotation = splitRandom(minRotation.asRadians(), maxRotation.asRadians())*lsst.geom.radians
304 refShiftRa = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.geom.radians
305 refShiftDec = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.geom.radians
306 pixShiftX = splitRandom(minPixShift, maxPixShift)
307 pixShiftY = splitRandom(minPixShift, maxPixShift)
312 newTransform = oldTransform*rTransform*sTransform
313 matrix = newTransform.getMatrix()
315 oldSkyOrigin = oldWcs.getSkyOrigin()
317 oldSkyOrigin.getDec() + refShiftDec)
319 oldPixOrigin = oldWcs.getPixelOrigin()
321 oldPixOrigin.getY() + pixShiftY)
325 def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, calibration=4):
326 """Create an Exposure, with a PhotoCalib, Wcs, and Psf, but no pixel values.
331 Bounding box of the image
in image coordinates.
333 New WCS
for the exposure (created
from CRVAL
and CDELT
if `
None`).
334 crval : `lsst.afw.geom.SpherePoint`, optional
335 ICRS center of the TAN WCS attached to the image. If `
None`, (45
336 degrees, 45 degrees)
is assumed.
338 Pixel scale of the image. If `
None`, 0.2 arcsec
is assumed.
339 psfSigma : `float`, optional
340 Radius (sigma) of the Gaussian PSF attached to the image
341 psfDim : `int`, optional
342 Width
and height of the image
's Gaussian PSF attached to the image
343 calibration : `float`, optional
344 The spatially-constant calibration (in nJy/count) to set the
345 PhotoCalib of the exposure.
349 exposure : `lsst.age.image.ExposureF`
356 cdelt = 0.2*lsst.geom.arcseconds
360 exposure = lsst.afw.image.ExposureF(bbox)
367 22.2*lsst.geom.degrees,
371 exposure.setPhotoCalib(photoCalib)
372 exposure.info.setVisitInfo(visitInfo)
377 """Create an image of an elliptical Gaussian.
382 Bounding box of image to create.
384 Total instrumental flux of the Gaussian (normalized analytically,
385 not using pixel values).
386 ellipse : `lsst.afw.geom.Ellipse`
387 Defines the centroid
and shape.
391 image : `lsst.afw.image.ImageF`
392 An image of the Gaussian.
394 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX()),
395 np.arange(bbox.getBeginY(), bbox.getEndY()))
396 t = ellipse.getGridTransform()
397 xt = t[t.XX] * x + t[t.XY] * y + t[t.X]
398 yt = t[t.YX] * x + t[t.YY] * y + t[t.Y]
399 image = lsst.afw.image.ImageF(bbox)
400 image.array[:, :] = np.exp(-0.5*(xt**2 + yt**2))*instFlux/(2.0*ellipse.getCore().getArea())
404 """Create simulated Footprint and add it to a truth catalog record.
407 if setPeakSignificance:
408 schema.addField(
"significance", type=float,
409 doc=
"Ratio of peak value to configured standard deviation.")
414 if setPeakSignificance:
417 for footprint
in fpSet.getFootprints():
418 footprint.updatePeakSignificance(self.
threshold.getValue())
420 fpSet.setMask(self.
exposure.mask,
"DETECTED")
422 if len(fpSet.getFootprints()) > 1:
423 raise RuntimeError(
"Threshold value results in multiple Footprints for a single object")
424 if len(fpSet.getFootprints()) == 0:
425 raise RuntimeError(
"Threshold value results in zero Footprints for object")
426 record.setFootprint(fpSet.getFootprints()[0])
428 def addSource(self, instFlux, centroid, shape=None, setPeakSignificance=True):
429 """Add a source to the simulation.
431 To insert a point source with a given signal-to-noise (sn), the total
432 ``instFlux`` should be: ``sn*noise*psf_scale``, where ``noise``
is the
433 noise you will
pass to ``
realize()``,
and
434 ``psf_scale=sqrt(4*pi*r^2)``, where ``r``
is the width of the PSF.
439 Total instFlux of the source to be added.
441 Position of the source to be added.
442 shape : `lsst.afw.geom.Quadrupole`
443 Second moments of the source before PSF convolution. Note that the
444 truth catalog records post-convolution moments. If `
None`, a point
445 source will be added.
446 setPeakSignificance : `bool`
447 Set the ``significance`` field
for peaks
in the footprints?
448 See ``lsst.meas.algorithms.SourceDetectionTask.setPeakSignificance``
449 for how this field
is computed
for real datasets.
454 A truth catalog record.
455 image : `lsst.afw.image.ImageF`
456 Single-source image corresponding to the new source.
460 record.set(self.
keys[
"instFlux"], instFlux)
461 record.set(self.
keys[
"instFluxErr"], 0)
462 record.set(self.
keys[
"centroid"], centroid)
463 covariance = np.random.normal(0, 0.1, 4).reshape(2, 2)
464 covariance[0, 1] = covariance[1, 0]
465 record.set(self.
keys[
"centroid_sigma"], covariance.astype(np.float32))
467 record.set(self.
keys[
"isStar"],
True)
470 record.set(self.
keys[
"isStar"],
False)
471 fullShape = shape.convolve(self.
psfShape)
472 record.set(self.
keys[
"shape"], fullShape)
475 lsst.afw.geom.Ellipse(fullShape, centroid))
479 self.
exposure.image.array[:, :] += image.array
483 """Return a context manager which can add a blend of multiple sources.
487 Note that nothing stops you from creating overlapping sources just using the
addSource() method,
488 but
addBlend()
is necesssary to create a parent object
and deblended HeavyFootprints of the type
489 produced by the detection
and deblending pipelines.
495 with d.addBlend()
as b:
496 b.addChild(flux1, centroid1)
497 b.addChild(flux2, centroid2, shape2)
501 def transform(self, wcs, **kwds):
502 """Copy this dataset transformed to a new WCS, with new Psf and PhotoCalib.
507 WCS for the new dataset.
509 Additional keyword arguments passed on to
510 `TestDataset.makeEmptyExposure`. If
not specified, these revert
511 to the defaults
for `~TestDataset.makeEmptyExposure`,
not the
512 values
in the current dataset.
516 newDataset : `TestDataset`
517 Transformed copy of this dataset.
525 oldPhotoCalib = self.
exposure.getPhotoCalib()
526 newPhotoCalib = result.exposure.getPhotoCalib()
527 oldPsfShape = self.
exposure.getPsf().computeShape(bboxD.getCenter())
529 if record.get(self.
keys[
"nChild"]):
530 raise NotImplementedError(
"Transforming blended sources in TestDatasets is not supported")
531 magnitude = oldPhotoCalib.instFluxToMagnitude(record.get(self.
keys[
"instFlux"]))
532 newFlux = newPhotoCalib.magnitudeToInstFlux(magnitude)
533 oldCentroid = record.get(self.
keys[
"centroid"])
534 newCentroid = xyt.applyForward(oldCentroid)
535 if record.get(self.
keys[
"isStar"]):
536 newDeconvolvedShape =
None
539 oldFullShape = record.get(self.
keys[
"shape"])
540 oldDeconvolvedShape = lsst.afw.geom.Quadrupole(
541 oldFullShape.getIxx() - oldPsfShape.getIxx(),
542 oldFullShape.getIyy() - oldPsfShape.getIyy(),
543 oldFullShape.getIxy() - oldPsfShape.getIxy(),
546 newDeconvolvedShape = oldDeconvolvedShape.transform(affine.getLinear())
547 result.addSource(newFlux, newCentroid, newDeconvolvedShape)
550 def realize(self, noise, schema, randomSeed=1):
551 r"""Simulate an exposure and detection catalog for this dataset.
553 The simulation includes noise, and the detection catalog includes
554 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s.
559 Standard deviation of noise to be added to the exposure. The
560 noise will be Gaussian
and constant, appropriate
for the
563 Schema of the new catalog to be created. Must start
with
564 ``self.
schema`` (i.e. ``schema.contains(self.
schema)`` must be
565 `
True`), but typically contains fields
for already-configured
566 measurement algorithms
as well.
567 randomSeed : `int`, optional
568 Seed
for the random number generator.
569 If `
None`, a seed
is chosen automatically.
573 `exposure` : `lsst.afw.image.ExposureF`
576 Simulated detection catalog.
578 random_state = np.random.RandomState(randomSeed)
579 assert schema.contains(self.
schema)
581 mapper.addMinimalSchema(self.
schema,
True)
583 exposure.variance.array[:, :] = noise**2
584 exposure.image.array[:, :] += random_state.randn(exposure.height, exposure.width)*noise
586 catalog.extend(self.
catalog, mapper=mapper)
589 for record
in catalog:
592 if record.getParent() == 0:
596 parent = catalog.find(record.getParent())
597 footprint = parent.getFootprint()
598 parentFluxArrayNoNoise = np.zeros(footprint.getArea(), dtype=np.float32)
599 footprint.spans.flatten(parentFluxArrayNoNoise, self.
exposure.image.array, self.
exposure.getXY0())
600 parentFluxArrayNoisy = np.zeros(footprint.getArea(), dtype=np.float32)
601 footprint.spans.flatten(parentFluxArrayNoisy, exposure.image.array, exposure.getXY0())
602 oldHeavy = record.getFootprint()
603 fraction = (oldHeavy.getImageArray() / parentFluxArrayNoNoise)
607 newHeavy = lsst.afw.detection.HeavyFootprintF(oldHeavy)
608 newHeavy.getImageArray()[:] = parentFluxArrayNoisy*fraction
609 newHeavy.getMaskArray()[:] = oldHeavy.getMaskArray()
610 newHeavy.getVarianceArray()[:] = oldHeavy.getVarianceArray()
611 record.setFootprint(newHeavy)
612 return exposure, catalog
616 """Base class for tests of measurement tasks.
619 """Create an instance of `SingleFrameMeasurementTask.ConfigClass`.
621 Only the specified plugin and its dependencies will be run; the
622 Centroid, Shape,
and ModelFlux slots will be set to the truth fields
623 generated by the `TestDataset`
class.
628 Name of measurement plugin to enable.
629 dependencies : iterable of `str`, optional
630 Names of dependencies of the measurement plugin.
634 config : `SingleFrameMeasurementTask.ConfigClass`
635 The resulting task configuration.
637 config = SingleFrameMeasurementTask.ConfigClass()
638 with warnings.catch_warnings():
639 warnings.filterwarnings(
"ignore", message=
"ignoreSlotPluginChecks", category=FutureWarning)
640 config = SingleFrameMeasurementTask.ConfigClass(ignoreSlotPluginChecks=
True)
641 config.slots.centroid =
"truth"
642 config.slots.shape =
"truth"
643 config.slots.modelFlux =
None
644 config.slots.apFlux =
None
645 config.slots.psfFlux =
None
646 config.slots.gaussianFlux =
None
647 config.slots.calibFlux =
None
648 config.plugins.names = (plugin,) + tuple(dependencies)
653 """Create a configured instance of `SingleFrameMeasurementTask`.
657 plugin : `str`, optional
658 Name of measurement plugin to enable. If `None`, a configuration
659 must be supplied
as the ``config`` parameter. If both are
660 specified, ``config`` takes precedence.
661 dependencies : iterable of `str`, optional
662 Names of dependencies of the specified measurement plugin.
663 config : `SingleFrameMeasurementTask.ConfigClass`, optional
664 Configuration
for the task. If `
None`, a measurement plugin must
665 be supplied
as the ``plugin`` paramter. If both are specified,
666 ``config`` takes precedence.
668 Measurement table schema. If `
None`, a default schema
is
671 Measurement algorithm metadata. If `
None`, a default container
676 task : `SingleFrameMeasurementTask`
677 A configured instance of the measurement task.
681 raise ValueError(
"Either plugin or config argument must not be None")
684 schema = TestDataset.makeMinimalSchema()
686 schema.setAliasMap(
None)
687 if algMetadata
is None:
692 """Create an instance of `ForcedMeasurementTask.ConfigClass`.
694 In addition to the plugins specified in the plugin
and dependencies
695 arguments, the `TransformedCentroid`
and `TransformedShape` plugins
696 will be run
and used
as the centroid
and shape slots; these simply
697 transform the reference catalog centroid
and shape to the measurement
703 Name of measurement plugin to enable.
704 dependencies : iterable of `str`, optional
705 Names of dependencies of the measurement plugin.
709 config : `ForcedMeasurementTask.ConfigClass`
710 The resulting task configuration.
713 config = ForcedMeasurementTask.ConfigClass()
714 config.slots.centroid = "base_TransformedCentroid"
715 config.slots.shape =
"base_TransformedShape"
716 config.slots.modelFlux =
None
717 config.slots.apFlux =
None
718 config.slots.psfFlux =
None
719 config.slots.gaussianFlux =
None
720 config.plugins.names = (plugin,) + tuple(dependencies) + (
"base_TransformedCentroid",
721 "base_TransformedShape")
726 """Create a configured instance of `ForcedMeasurementTask`.
730 plugin : `str`, optional
731 Name of measurement plugin to enable. If `None`, a configuration
732 must be supplied
as the ``config`` parameter. If both are
733 specified, ``config`` takes precedence.
734 dependencies : iterable of `str`, optional
735 Names of dependencies of the specified measurement plugin.
736 config : `SingleFrameMeasurementTask.ConfigClass`, optional
737 Configuration
for the task. If `
None`, a measurement plugin must
738 be supplied
as the ``plugin`` paramter. If both are specified,
739 ``config`` takes precedence.
741 Reference table schema. If `
None`, a default schema
is
744 Measurement algorithm metadata. If `
None`, a default container
749 task : `ForcedMeasurementTask`
750 A configured instance of the measurement task.
754 raise ValueError(
"Either plugin or config argument must not be None")
756 if refSchema
is None:
757 refSchema = TestDataset.makeMinimalSchema()
758 if algMetadata
is None:
764 """Base class for testing measurement transformations.
768 We test both that the transform itself operates successfully (fluxes are
769 converted to magnitudes, flags are propagated properly) and that the
770 transform
is registered
as the default
for the appropriate measurement
773 In the simple case of one-measurement-per-transformation, the developer
774 need
not directly write any tests themselves: simply customizing the
class
775 variables
is all that
is required. More complex measurements (e.g.
776 multiple aperture fluxes) require extra effort.
778 name = "MeasurementTransformTest"
779 """The name used for the measurement algorithm (str).
783 This determines the names of the fields in the resulting catalog. This
784 default should generally be fine, but subclasses can override
if
790 algorithmClass =
None
791 transformClass =
None
793 flagNames = (
"flag",)
794 """Flags which may be set by the algorithm being tested (iterable of `str`).
800 singleFramePlugins = ()
805 self.
calexp = TestDataset.makeEmptyExposure(bbox)
806 self._setupTransform()
817 for flagValue
in (
True,
False):
818 records.append(self.
inputCat.addNew())
819 for baseName
in baseNames:
821 if records[-1].schema.join(baseName, flagName)
in records[-1].schema:
822 records[-1].set(records[-1].schema.join(baseName, flagName), flagValue)
823 self._setFieldsInRecords(records, baseName)
827 for baseName
in baseNames:
828 self._compareFieldsInRecords(inSrc, outSrc, baseName)
830 keyName = outSrc.schema.join(baseName, flagName)
831 if keyName
in inSrc.schema:
832 self.assertEqual(outSrc.get(keyName), inSrc.get(keyName))
834 self.assertFalse(keyName
in outSrc.schema)
842 """Test the transformation on a catalog containing random data.
846 baseNames : iterable of `str`
847 Iterable of the initial parts of measurement field names.
853 - An appropriate exception is raised on an attempt to transform
854 between catalogs
with different numbers of rows;
855 - Otherwise, all appropriate conversions are properly appled
and that
856 flags have been propagated.
858 The ``baseNames`` argument requires some explanation. This should be
859 an iterable of the leading parts of the field names
for each
860 measurement; that
is, everything that appears before ``_instFlux``,
861 ``_flag``, etc. In the simple case of a single measurement per plugin,
862 this
is simply equal to ``self.
name`` (thus measurements are stored
as
863 ``self.
name +
"_instFlux"``, etc). More generally, the developer may
864 specify whatever iterable they require. For example, to handle
865 multiple apertures, we could have ``(self.
name +
"_0", self.
name +
868 baseNames = baseNames or [self.
name]
877 self.assertEqual(registry[name].PluginClass.getTransformClass(), self.
transformClass)
880 """Test that the transformation is appropriately registered.
896 inputSchema.getAliasMap().set(
"slot_Centroid",
"dummy")
897 inputSchema.getAliasMap().set(
"slot_Shape",
"dummy")
899 inputSchema.getAliasMap().erase(
"slot_Centroid")
900 inputSchema.getAliasMap().erase(
"slot_Shape")
916 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"dummy")
917 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Shape",
"dummy")
919 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Centroid")
920 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Shape")
930 for record
in records:
931 record[record.schema.join(name,
'instFlux')] = np.random.random()
932 record[record.schema.join(name,
'instFluxErr')] = np.random.random()
935 assert len(records) > 1
936 records[0][record.schema.join(name,
'instFlux')] = -1
939 instFluxName = inSrc.schema.join(name,
'instFlux')
940 instFluxErrName = inSrc.schema.join(name,
'instFluxErr')
941 if inSrc[instFluxName] > 0:
942 mag = self.
calexp.getPhotoCalib().instFluxToMagnitude(inSrc[instFluxName],
943 inSrc[instFluxErrName])
944 self.assertEqual(outSrc[outSrc.schema.join(name,
'mag')], mag.value)
945 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], mag.error)
948 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'mag')]))
949 if np.isnan(inSrc[instFluxErrName]):
950 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'magErr')]))
952 mag = self.
calexp.getPhotoCalib().instFluxToMagnitude(inSrc[instFluxName],
953 inSrc[instFluxErrName])
954 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], mag.error)
960 for record
in records:
961 record[record.schema.join(name,
'x')] = np.random.random()
962 record[record.schema.join(name,
'y')] = np.random.random()
965 for fieldSuffix
in (
'xErr',
'yErr',
'x_y_Cov'):
966 fieldName = record.schema.join(name, fieldSuffix)
967 if fieldName
in record.schema:
968 record[fieldName] = np.random.random()
971 centroidResultKey = CentroidResultKey(inSrc.schema[self.
name])
972 centroidResult = centroidResultKey.get(inSrc)
975 coordTruth = self.
calexp.getWcs().pixelToSky(centroidResult.getCentroid())
976 self.assertEqual(coordTruth, coord)
981 coordErr = lsst.afw.table.CovarianceMatrix2fKey(outSrc.schema[self.
name],
982 [
"ra",
"dec"]).get(outSrc)
984 self.assertFalse(centroidResultKey.getCentroidErr().isValid())
986 transform = self.
calexp.getWcs().linearizePixelToSky(coordTruth, lsst.geom.radians)
987 coordErrTruth = np.dot(np.dot(transform.getLinear().getMatrix(),
988 centroidResult.getCentroidErr()),
989 transform.getLinear().getMatrix().transpose())
990 np.testing.assert_array_almost_equal(np.array(coordErrTruth), coordErr)
static afw::table::Schema makeMinimalSchema()
static QuadrupoleKey addFields(Schema &schema, std::string const &name, std::string const &doc, CoordinateType coordType=CoordinateType::PIXEL)
static Schema makeMinimalSchema()
makeSingleFrameMeasurementConfig(self, plugin=None, dependencies=())
makeForcedMeasurementConfig(self, plugin=None, dependencies=())
makeForcedMeasurementTask(self, plugin=None, dependencies=(), config=None, refSchema=None, algMetadata=None)
makeSingleFrameMeasurementTask(self, plugin=None, dependencies=(), config=None, schema=None, algMetadata=None)
addChild(self, instFlux, centroid, shape=None)
__exit__(self, type_, value, tb)
makePerturbedWcs(oldWcs, minScaleFactor=1.2, maxScaleFactor=1.5, minRotation=None, maxRotation=None, minRefShift=None, maxRefShift=None, minPixShift=2.0, maxPixShift=4.0, randomSeed=1)
realize(self, noise, schema, randomSeed=1)
addSource(self, instFlux, centroid, shape=None, setPeakSignificance=True)
_installFootprint(self, record, image, setPeakSignificance=True)
drawGaussian(bbox, instFlux, ellipse)
makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, calibration=4)
__init__(self, bbox, threshold=10.0, exposure=None, **kwds)
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Eigen::Matrix2d makeCdMatrix(lsst::geom::Angle const &scale, lsst::geom::Angle const &orientation=0 *lsst::geom::degrees, bool flipX=false)
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
lsst::geom::AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, lsst::geom::Point2D const &inPoint)