23 """Quality Assessment class for Kernel Candidates"""
30 from .
import diffimLib
31 from .utils
import calcCentroid, calcWidth
36 """Class to undertake QA of KernelCandidates after modeling of
37 the Psf-matching kernel. Both directly--fitted diffim (LOCAL)
38 and spatially--interpolated kernel diffim (SPATIAL) metrics
39 are calculated, based on the distribution of residuals in the
40 KernelCandidates stamp.
42 @param nKernelSpatial : Number of terms in the spatial model; needed to initialize per-basis QA arrays
46 "Position of reference object for registration (radians)."))
48 self.fields.append(
afwTable.Field[
"Angle"](
"RegisterResidualBearing",
49 "Angle of residual wrt declination parallel in radians"))
51 self.fields.append(
afwTable.Field[
"Angle"](
"RegisterResidualDistance",
52 "Offset of residual in radians"))
55 for kType
in (
"LOCAL",
"SPATIAL"):
57 commentAndUnit = metricMap[k][
'comment']
58 self.fields.append(
afwTable.Field[metricMap[k][
'type']](k%(kType), *commentAndUnit))
61 "Status of the KernelCandidate"))
63 self.fields.append(
afwTable.Field[
"ArrayD"](
"KernelCoeffValues_LOCAL",
64 "Original basis coefficients",
68 "Evaluation of background model at this point"))
70 self.fields.append(
afwTable.Field[
"F"](
"KCDiffimMseKernel_SPATIAL",
71 "Mean squared error of spatial kernel estimate"))
74 nameList = [
'KCDiffimMean_%s',
'KCDiffimMedian_%s',
'KCDiffimIQR_%s',
'KCDiffimStDev_%s',
75 'KCDiffimKSD_%s',
'KCDiffimKSProb_%s',
'KCDiffimADA2_%s',
'KCDiffimADCrit_%s',
76 'KCDiffimADSig_%s',
'KCDiffimChiSq_%s',
'KCDiffimMseResids_%s',
'KCKernelCentX_%s',
77 'KCKernelCentY_%s',
'KCKernelStdX_%s',
'KCKernelStdY_%s',
'KernelCandidateId_%s']
78 typeList = [
'F',
'F',
'F',
'F',
'F',
'F',
'F',
'ArrayD',
'ArrayD',
'F',
'F',
'F',
80 commentList = [(
"Mean of KernelCandidate diffim",
"sigma"),
81 (
"Median of KernelCandidate diffim",
"sigma"),
82 (
"Inner quartile range of KernelCandidate diffim",
"sigma"),
83 (
"Standard deviation of KernelCandidate diffim",
"sigma"),
84 (
"D from K-S test of diffim pixels relative to Normal", ),
85 (
"Prob from K-S test of diffim pixels relative to Normal",
"likelihood"),
86 (
"Anderson-Darling test statistic of diffim pixels relative to Normal", ),
87 (
"Critical values for the significance levels in KCDiffimADSig. If A2 is greater "+\
88 "than this number, hypothesis that the distributions are similar can be rejected.", 5),
89 (
"Anderson-Darling significance levels for the Normal distribution", 5),
90 (
"Reduced chi^2 of the residual.",
"likelihood"),
91 (
"Mean squared error in diffim : Variance + Bias**2",),
92 (
"Centroid in X for this Kernel",
"pixels"),
93 (
"Centroid in Y for this Kernel",
"pixels"),
94 (
"Standard deviation in X for this Kernel",
"pixels"),
95 (
"Standard deviation in Y for this Kernel",
"pixels"),
96 (
"Id for this KernelCandidate",)]
98 for name, mtype, comment
in zip(nameList, typeList, commentList):
99 metricMap[name] = {
'type':mtype,
'comment':comment}
105 """Add the to-be-generated QA keys to the Source schema"""
106 schema = inSourceCatalog.getSchema()
108 psfDef = inSourceCatalog.getPsfFluxDefinition()
109 centroidDef = inSourceCatalog.getCentroidDefinition()
110 shapeDef = inSourceCatalog.getShapeDefinition()
111 for n
in schema.getNames():
112 inKeys.append(schema[n].asKey())
115 schema.addField(field)
117 for source
in inSourceCatalog:
118 rec = outSourceCatalog.addNew()
120 if k.getTypeString() ==
'Coord':
121 rec.setCoord(source.getCoord())
123 setter = getattr(rec,
"set"+k.getTypeString())
124 getter = getattr(source,
"get"+k.getTypeString())
126 outSourceCatalog.definePsfFlux(psfDef)
127 outSourceCatalog.defineCentroid(centroidDef)
128 outSourceCatalog.defineShape(shapeDef)
129 return outSourceCatalog
132 """Calculate the core QA statistics on a difference image"""
134 maskArr = di.getMask().getArray()
137 maskArr &= mask.getPlaneBitMask([
"BAD",
"SAT",
"NO_DATA",
"EDGE"])
140 diArr = ma.array(di.getImage().getArray(), mask=maskArr)
141 varArr = ma.array(di.getVariance().getArray(), mask=maskArr)
144 diArr /= np.sqrt(varArr)
149 median = ma.extras.median(diArr)
152 data = ma.getdata(diArr[~diArr.mask])
153 iqr = np.percentile(data, 75.) - np.percentile(data, 25.)
156 chisq=np.sum(np.power(data, 2.))
162 variance = np.power(data, 2.).mean()
163 mseResids = bias**2 + variance
168 rchisq=chisq/(len(data)-1-dof)
171 D, prob = scipy.stats.kstest(data,
'norm')
173 A2, crit, sig = scipy.stats.anderson(data,
'norm')
175 if np.isinf(A2)
or np.isnan(A2):
177 except ZeroDivisionError:
185 return {
"mean": mean,
"stdev": stdev,
"median": median,
"iqr": iqr,
186 "D": D,
"prob": prob,
"A2": A2,
"crit": crit,
"sig": sig,
187 "rchisq": rchisq,
"mseResids": mseResids}
190 def apply(self, candidateList, spatialKernel, spatialBackground, dof=0):
191 """Evaluate the QA metrics for all KernelCandidates in the
192 candidateList; set the values of the metrics in their
193 associated Sources"""
194 for kernelCandidate
in candidateList:
195 source = kernelCandidate.getSource()
196 schema = source.schema
199 if kernelCandidate.getStatus() != afwMath.SpatialCellCandidate.UNKNOWN:
200 kType = getattr(diffimLib.KernelCandidateF,
"ORIG")
201 di = kernelCandidate.getDifferenceImage(kType)
202 kernelValues = kernelCandidate.getKernel(kType).getKernelParameters()
203 kernelValues = np.asarray(kernelValues)
205 lkim = kernelCandidate.getKernelImage(kType)
207 stdx, stdy =
calcWidth(lkim.getArray(), centx, centy)
213 metrics = {
"KCDiffimMean_LOCAL":localResults[
"mean"],
214 "KCDiffimMedian_LOCAL":localResults[
"median"],
215 "KCDiffimIQR_LOCAL":localResults[
"iqr"],
216 "KCDiffimStDev_LOCAL":localResults[
"stdev"],
217 "KCDiffimKSD_LOCAL":localResults[
"D"],
218 "KCDiffimKSProb_LOCAL":localResults[
"prob"],
219 "KCDiffimADA2_LOCAL":localResults[
"A2"],
220 "KCDiffimADCrit_LOCAL":localResults[
"crit"],
221 "KCDiffimADSig_LOCAL":localResults[
"sig"],
222 "KCDiffimChiSq_LOCAL":localResults[
"rchisq"],
223 "KCDiffimMseResids_LOCAL":localResults[
"mseResids"],
224 "KCKernelCentX_LOCAL":centx,
225 "KCKernelCentY_LOCAL":centy,
226 "KCKernelStdX_LOCAL":stdx,
227 "KCKernelStdY_LOCAL":stdy,
228 "KernelCandidateId_LOCAL":kernelCandidate.getId(),
229 "KernelCoeffValues_LOCAL":kernelValues}
230 for k
in metrics.keys():
231 key = schema[k].asKey()
232 setter = getattr(source,
"set"+key.getTypeString())
233 setter(key, metrics[k])
236 kType = getattr(diffimLib.KernelCandidateF,
"ORIG")
237 lkim = kernelCandidate.getKernelImage(kType)
243 skim = afwImage.ImageD(spatialKernel.getDimensions())
244 spatialKernel.computeImage(skim,
False, kernelCandidate.getXCenter(),
245 kernelCandidate.getYCenter())
247 stdx, stdy =
calcWidth(skim.getArray(), centx, centy)
250 sbg = spatialBackground(kernelCandidate.getXCenter(), kernelCandidate.getYCenter())
251 di = kernelCandidate.getDifferenceImage(sk, sbg)
257 bias = np.mean(skim.getArray())
258 variance = np.mean(np.power(skim.getArray(), 2.))
259 mseKernel = bias**2 + variance
263 metrics = {
"KCDiffimMean_SPATIAL":spatialResults[
"mean"],
264 "KCDiffimMedian_SPATIAL":spatialResults[
"median"],
265 "KCDiffimIQR_SPATIAL":spatialResults[
"iqr"],
266 "KCDiffimStDev_SPATIAL":spatialResults[
"stdev"],
267 "KCDiffimKSD_SPATIAL":spatialResults[
"D"],
268 "KCDiffimKSProb_SPATIAL":spatialResults[
"prob"],
269 "KCDiffimADA2_SPATIAL":spatialResults[
"A2"],
270 "KCDiffimADCrit_SPATIAL":spatialResults[
"crit"],
271 "KCDiffimADSig_SPATIAL":spatialResults[
"sig"],
272 "KCDiffimChiSq_SPATIAL":spatialResults[
"rchisq"],
273 "KCDiffimMseResids_SPATIAL":spatialResults[
"mseResids"],
274 "KCDiffimMseKernel_SPATIAL":mseKernel,
275 "KCKernelCentX_SPATIAL":centx,
276 "KCKernelCentY_SPATIAL":centy,
277 "KCKernelStdX_SPATIAL":stdx,
278 "KCKernelStdY_SPATIAL":stdy,
279 "KernelCandidateId_SPATIAL":kernelCandidate.getId()}
280 for k
in metrics.keys():
281 key = schema[k].asKey()
282 setter = getattr(source,
"set"+key.getTypeString())
283 setter(key, metrics[k])
285 def aggregate(self, sourceCatalog, metadata, wcsresids, diaSources=None):
286 """Generate aggregate metrics (e.g. total numbers of false
287 positives) from all the Sources in the sourceCatalog"""
288 for source
in sourceCatalog:
289 sourceId = source.getId()
290 if sourceId
in wcsresids.keys():
293 coord, resids = wcsresids[sourceId]
294 key = source.schema[
"RegisterResidualBearing"].asKey()
295 setter = getattr(source,
"set"+key.getTypeString())
296 setter(key, resids[0])
297 key = source.schema[
"RegisterResidualDistance"].asKey()
298 setter = getattr(source,
"set"+key.getTypeString())
299 setter(key, resids[1])
300 key = source.schema[
"RegisterRefPosition"].asKey()
301 setter = getattr(source,
"set"+key.getTypeString())
303 coord.getDec().asRadians()))
305 metadata.add(
"NFalsePositivesTotal", len(diaSources))
309 for source
in diaSources:
310 refId = source.get(
"refMatchId")
311 srcId = source.get(
"srcMatchId")
316 if refId == 0
and srcId == 0:
318 metadata.add(
"NFalsePositivesRefAssociated", nRefMatch)
319 metadata.add(
"NFalsePositivesSrcAssociated", nSrcMatch)
320 metadata.add(
"NFalsePositivesUnassociated", nunmatched)
321 for kType
in (
"LOCAL",
"SPATIAL"):
322 for sName
in (
"KCDiffimMean",
"KCDiffimMedian",
"KCDiffimIQR",
"KCDiffimStDev",
323 "KCDiffimKSProb",
"KCDiffimADSig",
"KCDiffimChiSq",
324 "KCDiffimMseResids",
"KCDiffimMseKernel"):
325 if sName ==
"KCDiffimMseKernel" and kType ==
"LOCAL":
327 kName =
"%s_%s" % (sName, kType)
328 vals = np.array([s.get(kName)
for s
in sourceCatalog])
329 idx = np.isfinite(vals)
330 metadata.add(
"%s_MEAN" % (kName), np.mean(vals[idx]))
331 metadata.add(
"%s_MEDIAN" % (kName), np.median(vals[idx]))
332 metadata.add(
"%s_STDEV" % (kName), np.std(vals[idx]))
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
A description of a field in a table.
A kernel created from an Image.