22 """Brighter Fatter Kernel calibration definition."""
25 __all__ = [
'BrighterFatterKernel']
29 from astropy.table
import Table
31 from .
import IsrCalib
35 """Calibration of brighter-fatter kernels for an instrument.
37 ampKernels are the kernels for each amplifier in a detector, as
38 generated by having level == 'AMP'
40 detectorKernel is the kernel generated for a detector as a
41 whole, as generated by having level == 'DETECTOR'
43 makeDetectorKernelFromAmpwiseKernels is a method to generate the
44 kernel for a detector, constructed by averaging together the
45 ampwise kernels in the detector. The existing application code is
46 only defined for kernels with level == 'DETECTOR', so this method
47 is used if the supplied kernel was built with level == 'AMP'.
52 Level the kernels will be generated for.
53 log : `lsst.log.Log`, optional
54 Log to write messages to.
56 Parameters to pass to parent constructor.
60 _SCHEMA =
'Brighter-fatter kernel'
63 def __init__(self, camera=None, level=None, **kwargs):
84 self.
initFromCamerainitFromCamera(camera, detectorId=kwargs.get(
'detectorId',
None))
87 self.requiredAttributes.update([
'level',
'means',
'variances',
'rawXcorrs',
88 'badAmps',
'gain',
'noise',
'meanXcorrs',
'valid',
89 'ampKernels',
'detKernels'])
92 """Calibration equivalence
94 if not isinstance(other, self.__class__):
97 for attr
in self._requiredAttributes:
98 attrSelf = getattr(self, attr)
99 attrOther = getattr(other, attr)
100 if isinstance(attrSelf, dict)
and isinstance(attrOther, dict):
101 for ampName
in attrSelf:
102 if not np.allclose(attrSelf[ampName], attrOther[ampName], equal_nan=
True):
105 if attrSelf != attrOther:
110 """Update calibration metadata.
112 This calls the base class's method after ensuring the required
113 calibration keywords will be saved.
117 setDate : `bool`, optional
118 Update the CALIBDATE fields in the metadata to the current
119 time. Defaults to False.
121 Other keyword parameters to set in the metadata.
123 kwargs[
'LEVEL'] = self.
levellevel
124 kwargs[
'KERNEL_DX'] = self.
shapeshape[0]
125 kwargs[
'KERNEL_DY'] = self.
shapeshape[1]
130 """Initialize kernel structure from camera.
134 camera : `lsst.afw.cameraGeom.Camera`
135 Camera to use to define geometry.
136 detectorId : `int`, optional
137 Index of the detector to generate.
141 calib : `lsst.ip.isr.BrighterFatterKernel`
142 The initialized calibration.
147 Raised if no detectorId is supplied for a calibration with
152 if self.
levellevel ==
'AMP':
153 if detectorId
is None:
154 raise RuntimeError(
"A detectorId must be supplied if level='AMP'.")
156 detector = camera[detectorId]
163 ampName = amp.getName()
164 self.
meansmeans[ampName] = []
167 self.
gaingain[ampName] = amp.getGain()
168 self.
noisenoise[ampName] = amp.getReadNoise()
171 self.
validvalid[ampName] = []
172 elif self.
levellevel ==
'DETECTOR':
174 detName = det.getName()
180 """Return the set of lengths needed for reshaping components.
185 Product of the elements of self.shape.
187 Size of an untiled covariance.
189 Number of observation pairs used in the kernel.
191 kernelLength = self.
shapeshape[0] * self.
shapeshape[1]
192 smallLength = int((self.
shapeshape[0] - 1)*(self.
shapeshape[1] - 1)/4)
193 nObservations = set([len(self.
meansmeans[amp])
for amp
in self.
meansmeans])
194 if len(nObservations) != 1:
195 raise RuntimeError(
"Inconsistent number of observations found.")
196 nObs = nObservations.pop()
198 return (kernelLength, smallLength, nObs)
202 """Construct a calibration from a dictionary of properties.
207 Dictionary of properties.
211 calib : `lsst.ip.isr.BrighterFatterKernel
212 Constructed calibration.
217 Raised if the supplied dictionary is for a different
222 if calib._OBSTYPE != (found := dictionary[
'metadata'][
'OBSTYPE']):
223 raise RuntimeError(f
"Incorrect brighter-fatter kernel supplied. Expected {calib._OBSTYPE}, "
226 calib.setMetadata(dictionary[
'metadata'])
227 calib.calibInfoFromDict(dictionary)
229 calib.level = dictionary[
'metadata'].get(
'LEVEL',
'AMP')
230 calib.shape = (dictionary[
'metadata'].get(
'KERNEL_DX', 0),
231 dictionary[
'metadata'].get(
'KERNEL_DY', 0))
233 calib.means = {amp: np.array(dictionary[
'means'][amp])
for amp
in dictionary[
'means']}
234 calib.variances = {amp: np.array(dictionary[
'variances'][amp])
for amp
in dictionary[
'variances']}
237 _, smallLength, nObs = calib.getLengths()
238 smallShapeSide = int(np.sqrt(smallLength))
240 calib.rawXcorrs = {amp: np.array(dictionary[
'rawXcorrs'][amp]).reshape((nObs,
243 for amp
in dictionary[
'rawXcorrs']}
245 calib.gain = dictionary[
'gain']
246 calib.noise = dictionary[
'noise']
248 calib.meanXcorrs = {amp: np.array(dictionary[
'meanXcorrs'][amp]).reshape(calib.shape)
249 for amp
in dictionary[
'rawXcorrs']}
250 calib.ampKernels = {amp: np.array(dictionary[
'ampKernels'][amp]).reshape(calib.shape)
251 for amp
in dictionary[
'ampKernels']}
252 calib.valid = {amp: bool(value)
for amp, value
in dictionary[
'valid'].items()}
253 calib.badAmps = [amp
for amp, valid
in dictionary[
'valid'].items()
if valid
is False]
255 calib.detKernels = {det: np.array(dictionary[
'detKernels'][det]).reshape(calib.shape)
256 for det
in dictionary[
'detKernels']}
258 calib.updateMetadata()
262 """Return a dictionary containing the calibration properties.
264 The dictionary should be able to be round-tripped through
270 Dictionary of properties.
275 metadata = self.getMetadata()
276 outDict[
'metadata'] = metadata
279 kernelLength, smallLength, nObs = self.
getLengthsgetLengths()
281 outDict[
'means'] = {amp: np.array(self.
meansmeans[amp]).tolist()
for amp
in self.
meansmeans}
282 outDict[
'variances'] = {amp: np.array(self.
variancesvariances[amp]).tolist()
for amp
in self.
variancesvariances}
283 outDict[
'rawXcorrs'] = {amp: np.array(self.
rawXcorrsrawXcorrs[amp]).reshape(nObs*smallLength).tolist()
285 outDict[
'badAmps'] = self.
badAmpsbadAmps
286 outDict[
'gain'] = self.
gaingain
287 outDict[
'noise'] = self.
noisenoise
289 outDict[
'meanXcorrs'] = {amp: self.
meanXcorrsmeanXcorrs[amp].reshape(kernelLength).tolist()
291 outDict[
'ampKernels'] = {amp: self.
ampKernelsampKernels[amp].reshape(kernelLength).tolist()
293 outDict[
'valid'] = self.
validvalid
294 outDict[
'detKernels'] = {det: self.
detKernelsdetKernels[det].reshape(kernelLength).tolist()
300 """Construct calibration from a list of tables.
302 This method uses the `fromDict` method to create the
303 calibration, after constructing an appropriate dictionary from
308 tableList : `list` [`astropy.table.Table`]
309 List of tables to use to construct the brighter-fatter
314 calib : `lsst.ip.isr.BrighterFatterKernel`
315 The calibration defined in the tables.
317 ampTable = tableList[0]
319 metadata = ampTable.meta
321 inDict[
'metadata'] = metadata
323 amps = ampTable[
'AMPLIFIER']
325 meanList = ampTable[
'MEANS']
326 varianceList = ampTable[
'VARIANCES']
328 rawXcorrs = ampTable[
'RAW_XCORRS']
329 gainList = ampTable[
'GAIN']
330 noiseList = ampTable[
'NOISE']
332 meanXcorrs = ampTable[
'MEAN_XCORRS']
333 ampKernels = ampTable[
'KERNEL']
334 validList = ampTable[
'VALID']
336 inDict[
'means'] = {amp: mean
for amp, mean
in zip(amps, meanList)}
337 inDict[
'variances'] = {amp: var
for amp, var
in zip(amps, varianceList)}
338 inDict[
'rawXcorrs'] = {amp: kernel
for amp, kernel
in zip(amps, rawXcorrs)}
339 inDict[
'gain'] = {amp: gain
for amp, gain
in zip(amps, gainList)}
340 inDict[
'noise'] = {amp: noise
for amp, noise
in zip(amps, noiseList)}
341 inDict[
'meanXcorrs'] = {amp: kernel
for amp, kernel
in zip(amps, meanXcorrs)}
342 inDict[
'ampKernels'] = {amp: kernel
for amp, kernel
in zip(amps, ampKernels)}
343 inDict[
'valid'] = {amp: bool(valid)
for amp, valid
in zip(amps, validList)}
345 inDict[
'badAmps'] = [amp
for amp, valid
in inDict[
'valid'].items()
if valid
is False]
347 if len(tableList) > 1:
348 detTable = tableList[1]
349 inDict[
'detKernels'] = {det: kernel
for det, kernel
350 in zip(detTable[
'DETECTOR'], detTable[
'KERNEL'])}
352 inDict[
'detKernels'] = {}
357 """Construct a list of tables containing the information in this calibration.
359 The list of tables should create an identical calibration
360 after being passed to this class's fromTable method.
364 tableList : `list` [`lsst.afw.table.Table`]
365 List of tables containing the crosstalk calibration
373 kernelLength, smallLength, nObs = self.
getLengthsgetLengths()
386 for amp
in self.
meansmeans.keys():
388 meanList.append(self.
meansmeans[amp])
389 varianceList.append(self.
variancesvariances[amp])
390 rawXcorrs.append(np.array(self.
rawXcorrsrawXcorrs[amp]).reshape(nObs*smallLength).tolist())
391 gainList.append(self.
gaingain[amp])
392 noiseList.append(self.
noisenoise[amp])
394 meanXcorrsList.append(self.
meanXcorrsmeanXcorrs[amp].reshape(kernelLength).tolist())
395 kernelList.append(self.
ampKernelsampKernels[amp].reshape(kernelLength).tolist())
396 validList.append(int(self.
validvalid[amp]
and not (amp
in self.
badAmpsbadAmps)))
398 ampTable = Table({
'AMPLIFIER': ampList,
400 'VARIANCES': varianceList,
401 'RAW_XCORRS': rawXcorrs,
404 'MEAN_XCORRS': meanXcorrsList,
405 'KERNEL': kernelList,
409 ampTable.meta = self.getMetadata().
toDict()
410 tableList.append(ampTable)
417 kernelList.append(self.
detKernelsdetKernels[det].reshape(kernelLength).tolist())
419 detTable = Table({
'DETECTOR': detList,
420 'KERNEL': kernelList})
421 detTable.meta = self.getMetadata().
toDict()
422 tableList.append(detTable)
428 """Average the amplifier level kernels to create a detector level kernel.
430 inKernels = np.array([self.
ampKernelsampKernels[amp]
for amp
in
431 self.
ampKernelsampKernels
if amp
not in ampsToExclude])
432 averagingList = np.transpose(inKernels)
433 avgKernel = np.zeros_like(inKernels[0])
434 sctrl = afwMath.StatisticsControl()
435 sctrl.setNumSigmaClip(5.0)
436 for i
in range(np.shape(avgKernel)[0]):
437 for j
in range(np.shape(avgKernel)[1]):
438 avgKernel[i, j] = afwMath.makeStatistics(averagingList[i, j],
439 afwMath.MEANCLIP, sctrl).getValue()
441 self.
detKernelsdetKernels[detectorName] = avgKernel
444 self.detKernel[detectorName] = self.ampKernel[ampName]
def __init__(self, camera=None, level=None, **kwargs)
def fromTable(cls, tableList)
def initFromCamera(self, camera, detectorId=None)
def updateMetadata(self, setDate=False, **kwargs)
def replaceDetectorKernelWithAmpKernel(self, ampName, detectorName)
def fromDict(cls, dictionary)
def makeDetectorKernelFromAmpwiseKernels(self, detectorName, ampsToExclude=[])