23 __all__ = [
"KernelCandidateQa"]
32 from .
import diffimLib
33 from .utils
import calcCentroid, calcWidth
37 """Quality Assessment class for Kernel Candidates"""
40 """Class to undertake QA of KernelCandidates after modeling of
41 the Psf-matching kernel. Both directly--fitted diffim (LOCAL)
42 and spatially--interpolated kernel diffim (SPATIAL) metrics
43 are calculated, based on the distribution of residuals in the
44 KernelCandidates stamp.
48 nKernelSpatial : `int`
49 Number of terms in the spatial model; needed to initialize per-basis QA arrays
53 "RegisterRefPosition",
54 "Position of reference object for registration (radians)."))
57 "Angle of residual wrt declination parallel in radians"))
60 "Offset of residual in radians"))
63 for kType
in (
"LOCAL",
"SPATIAL"):
65 commentAndUnit = metricMap[k][
'comment']
69 "Status of the KernelCandidate"))
72 "Original basis coefficients",
76 "Evaluation of background model at this point"))
79 "Mean squared error of spatial kernel estimate"))
82 nameList = [
'KCDiffimMean_%s',
'KCDiffimMedian_%s',
'KCDiffimIQR_%s',
'KCDiffimStDev_%s',
83 'KCDiffimKSD_%s',
'KCDiffimKSProb_%s',
'KCDiffimADA2_%s',
'KCDiffimADCrit_%s',
84 'KCDiffimADSig_%s',
'KCDiffimChiSq_%s',
'KCDiffimMseResids_%s',
'KCKernelCentX_%s',
85 'KCKernelCentY_%s',
'KCKernelStdX_%s',
'KCKernelStdY_%s',
'KernelCandidateId_%s']
86 typeList = [
'F',
'F',
'F',
'F',
'F',
'F',
'F',
'ArrayD',
'ArrayD',
'F',
'F',
'F',
89 (
"Mean of KernelCandidate diffim",
"sigma"),
90 (
"Median of KernelCandidate diffim",
"sigma"),
91 (
"Inner quartile range of KernelCandidate diffim",
"sigma"),
92 (
"Standard deviation of KernelCandidate diffim",
"sigma"),
93 (
"D from K-S test of diffim pixels relative to Normal", ),
94 (
"Prob from K-S test of diffim pixels relative to Normal",
"likelihood"),
95 (
"Anderson-Darling test statistic of diffim pixels relative to Normal", ),
96 (
"Critical values for the significance levels in KCDiffimADSig. If A2 is greater "
97 "than this number, hypothesis that the distributions are similar can be rejected.", 5),
98 (
"Anderson-Darling significance levels for the Normal distribution", 5),
99 (
"Reduced chi^2 of the residual.",
"likelihood"),
100 (
"Mean squared error in diffim : Variance + Bias**2",),
101 (
"Centroid in X for this Kernel",
"pixel"),
102 (
"Centroid in Y for this Kernel",
"pixel"),
103 (
"Standard deviation in X for this Kernel",
"pixel"),
104 (
"Standard deviation in Y for this Kernel",
"pixel"),
105 (
"Id for this KernelCandidate",),
108 for name, mtype, comment
in zip(nameList, typeList, commentList):
109 metricMap[name] = {
'type': mtype,
'comment': comment}
114 """Add the to-be-generated QA keys to the Source schema"""
115 schema = inSourceCatalog.getSchema()
117 psfDef = inSourceCatalog.schema.getAliasMap().get(
"slot_PsfFlux")
118 centroidDef = inSourceCatalog.getCentroidDefinition()
119 shapeDef = inSourceCatalog.getShapeDefinition()
120 for n
in schema.getNames():
121 inKeys.append(schema[n].asKey())
123 for field
in self.
fieldsfields:
124 schema.addField(field)
126 for source
in inSourceCatalog:
127 rec = outSourceCatalog.addNew()
129 if k.getTypeString() ==
'Coord':
130 rec.setCoord(source.getCoord())
132 setter = getattr(rec,
"set"+k.getTypeString())
133 getter = getattr(source,
"get"+k.getTypeString())
135 outSourceCatalog.definePsfFlux(psfDef)
136 outSourceCatalog.defineCentroid(centroidDef)
137 outSourceCatalog.defineShape(shapeDef)
138 return outSourceCatalog
141 def _calculateStats(di, dof=0.):
142 """Calculate the core QA statistics on a difference image"""
144 maskArr = di.getMask().getArray()
147 maskArr &= mask.getPlaneBitMask([
"BAD",
"SAT",
"NO_DATA",
"EDGE"])
150 diArr = ma.array(di.getImage().getArray(), mask=maskArr)
151 varArr = ma.array(di.getVariance().getArray(), mask=maskArr)
154 diArr /= np.sqrt(varArr)
159 median = ma.extras.median(diArr)
162 data = ma.getdata(diArr[~diArr.mask])
163 iqr = np.percentile(data, 75.) - np.percentile(data, 25.)
166 chisq = np.sum(np.power(data, 2.))
172 variance = np.power(data, 2.).mean()
173 mseResids = bias**2 + variance
178 rchisq = chisq/(len(data) - 1 - dof)
181 D, prob = scipy.stats.kstest(data,
'norm')
183 A2, crit, sig = scipy.stats.anderson(data,
'norm')
185 if np.isinf(A2)
or np.isnan(A2):
187 except ZeroDivisionError:
195 return {
"mean": mean,
"stdev": stdev,
"median": median,
"iqr": iqr,
196 "D": D,
"prob": prob,
"A2": A2,
"crit": crit,
"sig": sig,
197 "rchisq": rchisq,
"mseResids": mseResids}
200 def apply(cls, candidateList, spatialKernel, spatialBackground, dof=0):
201 """Evaluate the QA metrics for all KernelCandidates in the
202 candidateList; set the values of the metrics in their
203 associated Sources"""
204 for kernelCandidate
in candidateList:
205 source = kernelCandidate.getSource()
206 schema = source.schema
209 if kernelCandidate.getStatus() != afwMath.SpatialCellCandidate.UNKNOWN:
210 kType = getattr(diffimLib.KernelCandidateF,
"ORIG")
211 di = kernelCandidate.getDifferenceImage(kType)
212 kernelValues = kernelCandidate.getKernel(kType).getKernelParameters()
213 kernelValues = np.asarray(kernelValues)
215 lkim = kernelCandidate.getKernelImage(kType)
217 stdx, stdy =
calcWidth(lkim.getArray(), centx, centy)
223 metrics = {
"KCDiffimMean_LOCAL": localResults[
"mean"],
224 "KCDiffimMedian_LOCAL": localResults[
"median"],
225 "KCDiffimIQR_LOCAL": localResults[
"iqr"],
226 "KCDiffimStDev_LOCAL": localResults[
"stdev"],
227 "KCDiffimKSD_LOCAL": localResults[
"D"],
228 "KCDiffimKSProb_LOCAL": localResults[
"prob"],
229 "KCDiffimADA2_LOCAL": localResults[
"A2"],
230 "KCDiffimADCrit_LOCAL": localResults[
"crit"],
231 "KCDiffimADSig_LOCAL": localResults[
"sig"],
232 "KCDiffimChiSq_LOCAL": localResults[
"rchisq"],
233 "KCDiffimMseResids_LOCAL": localResults[
"mseResids"],
234 "KCKernelCentX_LOCAL": centx,
235 "KCKernelCentY_LOCAL": centy,
236 "KCKernelStdX_LOCAL": stdx,
237 "KCKernelStdY_LOCAL": stdy,
238 "KernelCandidateId_LOCAL": kernelCandidate.getId(),
239 "KernelCoeffValues_LOCAL": kernelValues}
241 key = schema[k].asKey()
242 setter = getattr(source,
"set"+key.getTypeString())
243 setter(key, metrics[k])
246 kType = getattr(diffimLib.KernelCandidateF,
"ORIG")
247 lkim = kernelCandidate.getKernelImage(kType)
253 skim = afwImage.ImageD(spatialKernel.getDimensions())
254 spatialKernel.computeImage(skim,
False, kernelCandidate.getXCenter(),
255 kernelCandidate.getYCenter())
257 stdx, stdy =
calcWidth(skim.getArray(), centx, centy)
260 sbg = spatialBackground(kernelCandidate.getXCenter(), kernelCandidate.getYCenter())
261 di = kernelCandidate.getDifferenceImage(sk, sbg)
267 bias = np.mean(skim.getArray())
268 variance = np.mean(np.power(skim.getArray(), 2.))
269 mseKernel = bias**2 + variance
273 metrics = {
"KCDiffimMean_SPATIAL": spatialResults[
"mean"],
274 "KCDiffimMedian_SPATIAL": spatialResults[
"median"],
275 "KCDiffimIQR_SPATIAL": spatialResults[
"iqr"],
276 "KCDiffimStDev_SPATIAL": spatialResults[
"stdev"],
277 "KCDiffimKSD_SPATIAL": spatialResults[
"D"],
278 "KCDiffimKSProb_SPATIAL": spatialResults[
"prob"],
279 "KCDiffimADA2_SPATIAL": spatialResults[
"A2"],
280 "KCDiffimADCrit_SPATIAL": spatialResults[
"crit"],
281 "KCDiffimADSig_SPATIAL": spatialResults[
"sig"],
282 "KCDiffimChiSq_SPATIAL": spatialResults[
"rchisq"],
283 "KCDiffimMseResids_SPATIAL": spatialResults[
"mseResids"],
284 "KCDiffimMseKernel_SPATIAL": mseKernel,
285 "KCKernelCentX_SPATIAL": centx,
286 "KCKernelCentY_SPATIAL": centy,
287 "KCKernelStdX_SPATIAL": stdx,
288 "KCKernelStdY_SPATIAL": stdy,
289 "KernelCandidateId_SPATIAL": kernelCandidate.getId()}
291 key = schema[k].asKey()
292 setter = getattr(source,
"set"+key.getTypeString())
293 setter(key, metrics[k])
296 def aggregate(sourceCatalog, metadata, wcsresids, diaSources=None):
297 """Generate aggregate metrics (e.g. total numbers of false
298 positives) from all the Sources in the sourceCatalog"""
299 for source
in sourceCatalog:
300 sourceId = source.getId()
301 if sourceId
in wcsresids:
304 coord, resids = wcsresids[sourceId]
305 key = source.schema[
"RegisterResidualBearing"].asKey()
306 setter = getattr(source,
"set"+key.getTypeString())
307 setter(key, resids[0])
308 key = source.schema[
"RegisterResidualDistance"].asKey()
309 setter = getattr(source,
"set"+key.getTypeString())
310 setter(key, resids[1])
311 key = source.schema[
"RegisterRefPosition"].asKey()
312 setter = getattr(source,
"set"+key.getTypeString())
314 coord.getDec().asRadians()))
316 metadata.add(
"NFalsePositivesTotal", len(diaSources))
320 for source
in diaSources:
321 refId = source.get(
"refMatchId")
322 srcId = source.get(
"srcMatchId")
327 if refId == 0
and srcId == 0:
329 metadata.add(
"NFalsePositivesRefAssociated", nRefMatch)
330 metadata.add(
"NFalsePositivesSrcAssociated", nSrcMatch)
331 metadata.add(
"NFalsePositivesUnassociated", nunmatched)
332 for kType
in (
"LOCAL",
"SPATIAL"):
333 for sName
in (
"KCDiffimMean",
"KCDiffimMedian",
"KCDiffimIQR",
"KCDiffimStDev",
334 "KCDiffimKSProb",
"KCDiffimADSig",
"KCDiffimChiSq",
335 "KCDiffimMseResids",
"KCDiffimMseKernel"):
336 if sName ==
"KCDiffimMseKernel" and kType ==
"LOCAL":
338 kName =
"%s_%s" % (sName, kType)
339 vals = np.array([s.get(kName)
for s
in sourceCatalog])
340 idx = np.isfinite(vals)
341 metadata.add(
"%s_MEAN" % (kName), np.mean(vals[idx]))
342 metadata.add(
"%s_MEDIAN" % (kName), np.median(vals[idx]))
343 metadata.add(
"%s_STDEV" % (kName), np.std(vals[idx]))
A kernel created from an Image.
def apply(cls, candidateList, spatialKernel, spatialBackground, dof=0)
def addToSchema(self, inSourceCatalog)
def __init__(self, nKernelSpatial)
def _calculateStats(di, dof=0.)
def aggregate(sourceCatalog, metadata, wcsresids, diaSources=None)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
def calcWidth(arr, centx, centy)
A description of a field in a table.