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 """Update calibration metadata.
94 This calls the base class's method after ensuring the required
95 calibration keywords will be saved.
99 setDate : `bool`, optional
100 Update the CALIBDATE fields in the metadata to the current
101 time. Defaults to False.
103 Other keyword parameters to set in the metadata.
105 kwargs[
'LEVEL'] = self.
levellevel
106 kwargs[
'KERNEL_DX'] = self.
shapeshape[0]
107 kwargs[
'KERNEL_DY'] = self.
shapeshape[1]
112 """Initialize kernel structure from camera.
116 camera : `lsst.afw.cameraGeom.Camera`
117 Camera to use to define geometry.
118 detectorId : `int`, optional
119 Index of the detector to generate.
123 calib : `lsst.ip.isr.BrighterFatterKernel`
124 The initialized calibration.
129 Raised if no detectorId is supplied for a calibration with
134 if self.
levellevel ==
'AMP':
135 if detectorId
is None:
136 raise RuntimeError(
"A detectorId must be supplied if level='AMP'.")
138 detector = camera[detectorId]
145 ampName = amp.getName()
146 self.
meansmeans[ampName] = []
149 self.
gaingain[ampName] = amp.getGain()
150 self.
noisenoise[ampName] = amp.getReadNoise()
153 self.
validvalid[ampName] = []
154 elif self.
levellevel ==
'DETECTOR':
156 detName = det.getName()
162 """Return the set of lengths needed for reshaping components.
167 Product of the elements of self.shape.
169 Size of an untiled covariance.
171 Number of observation pairs used in the kernel.
173 kernelLength = self.
shapeshape[0] * self.
shapeshape[1]
174 smallLength = int((self.
shapeshape[0] - 1)*(self.
shapeshape[1] - 1)/4)
175 nObservations = set([len(self.
meansmeans[amp])
for amp
in self.
meansmeans])
176 if len(nObservations) != 1:
177 raise RuntimeError(
"Inconsistent number of observations found.")
178 nObs = nObservations.pop()
180 return (kernelLength, smallLength, nObs)
184 """Construct a calibration from a dictionary of properties.
189 Dictionary of properties.
193 calib : `lsst.ip.isr.BrighterFatterKernel
194 Constructed calibration.
199 Raised if the supplied dictionary is for a different
204 if calib._OBSTYPE != (found := dictionary[
'metadata'][
'OBSTYPE']):
205 raise RuntimeError(f
"Incorrect brighter-fatter kernel supplied. Expected {calib._OBSTYPE}, "
208 calib.setMetadata(dictionary[
'metadata'])
209 calib.calibInfoFromDict(dictionary)
211 calib.level = dictionary[
'metadata'].get(
'LEVEL',
'AMP')
212 calib.shape = (dictionary[
'metadata'].get(
'KERNEL_DX', 0),
213 dictionary[
'metadata'].get(
'KERNEL_DY', 0))
215 calib.means = {amp: np.array(dictionary[
'means'][amp])
for amp
in dictionary[
'means']}
216 calib.variances = {amp: np.array(dictionary[
'variances'][amp])
for amp
in dictionary[
'variances']}
219 _, smallLength, nObs = calib.getLengths()
220 smallShapeSide = int(np.sqrt(smallLength))
222 calib.rawXcorrs = {amp: np.array(dictionary[
'rawXcorrs'][amp]).reshape((nObs,
225 for amp
in dictionary[
'rawXcorrs']}
227 calib.gain = dictionary[
'gain']
228 calib.noise = dictionary[
'noise']
230 calib.meanXcorrs = {amp: np.array(dictionary[
'meanXcorrs'][amp]).reshape(calib.shape)
231 for amp
in dictionary[
'rawXcorrs']}
232 calib.ampKernels = {amp: np.array(dictionary[
'ampKernels'][amp]).reshape(calib.shape)
233 for amp
in dictionary[
'ampKernels']}
234 calib.valid = {amp: bool(value)
for amp, value
in dictionary[
'valid'].items()}
235 calib.badAmps = [amp
for amp, valid
in dictionary[
'valid'].items()
if valid
is False]
237 calib.detKernels = {det: np.array(dictionary[
'detKernels'][det]).reshape(calib.shape)
238 for det
in dictionary[
'detKernels']}
240 calib.updateMetadata()
244 """Return a dictionary containing the calibration properties.
246 The dictionary should be able to be round-tripped through
252 Dictionary of properties.
257 metadata = self.getMetadata()
258 outDict[
'metadata'] = metadata
261 kernelLength, smallLength, nObs = self.
getLengthsgetLengths()
263 outDict[
'means'] = {amp: np.array(self.
meansmeans[amp]).tolist()
for amp
in self.
meansmeans}
264 outDict[
'variances'] = {amp: np.array(self.
variancesvariances[amp]).tolist()
for amp
in self.
variancesvariances}
265 outDict[
'rawXcorrs'] = {amp: np.array(self.
rawXcorrsrawXcorrs[amp]).reshape(nObs*smallLength).tolist()
267 outDict[
'badAmps'] = self.
badAmpsbadAmps
268 outDict[
'gain'] = self.
gaingain
269 outDict[
'noise'] = self.
noisenoise
271 outDict[
'meanXcorrs'] = {amp: self.
meanXcorrsmeanXcorrs[amp].reshape(kernelLength).tolist()
273 outDict[
'ampKernels'] = {amp: self.
ampKernelsampKernels[amp].reshape(kernelLength).tolist()
275 outDict[
'valid'] = self.
validvalid
276 outDict[
'detKernels'] = {det: self.
detKernelsdetKernels[det].reshape(kernelLength).tolist()
282 """Construct calibration from a list of tables.
284 This method uses the `fromDict` method to create the
285 calibration, after constructing an appropriate dictionary from
290 tableList : `list` [`astropy.table.Table`]
291 List of tables to use to construct the brighter-fatter
296 calib : `lsst.ip.isr.BrighterFatterKernel`
297 The calibration defined in the tables.
299 ampTable = tableList[0]
301 metadata = ampTable.meta
303 inDict[
'metadata'] = metadata
305 amps = ampTable[
'AMPLIFIER']
307 meanList = ampTable[
'MEANS']
308 varianceList = ampTable[
'VARIANCES']
310 rawXcorrs = ampTable[
'RAW_XCORRS']
311 gainList = ampTable[
'GAIN']
312 noiseList = ampTable[
'NOISE']
314 meanXcorrs = ampTable[
'MEAN_XCORRS']
315 ampKernels = ampTable[
'KERNEL']
316 validList = ampTable[
'VALID']
318 inDict[
'means'] = {amp: mean
for amp, mean
in zip(amps, meanList)}
319 inDict[
'variances'] = {amp: var
for amp, var
in zip(amps, varianceList)}
320 inDict[
'rawXcorrs'] = {amp: kernel
for amp, kernel
in zip(amps, rawXcorrs)}
321 inDict[
'gain'] = {amp: gain
for amp, gain
in zip(amps, gainList)}
322 inDict[
'noise'] = {amp: noise
for amp, noise
in zip(amps, noiseList)}
323 inDict[
'meanXcorrs'] = {amp: kernel
for amp, kernel
in zip(amps, meanXcorrs)}
324 inDict[
'ampKernels'] = {amp: kernel
for amp, kernel
in zip(amps, ampKernels)}
325 inDict[
'valid'] = {amp: bool(valid)
for amp, valid
in zip(amps, validList)}
327 inDict[
'badAmps'] = [amp
for amp, valid
in inDict[
'valid'].items()
if valid
is False]
329 if len(tableList) > 1:
330 detTable = tableList[1]
331 inDict[
'detKernels'] = {det: kernel
for det, kernel
332 in zip(detTable[
'DETECTOR'], detTable[
'KERNEL'])}
334 inDict[
'detKernels'] = {}
339 """Construct a list of tables containing the information in this calibration.
341 The list of tables should create an identical calibration
342 after being passed to this class's fromTable method.
346 tableList : `list` [`lsst.afw.table.Table`]
347 List of tables containing the crosstalk calibration
355 kernelLength, smallLength, nObs = self.
getLengthsgetLengths()
368 for amp
in self.
meansmeans.keys():
370 meanList.append(self.
meansmeans[amp])
371 varianceList.append(self.
variancesvariances[amp])
372 rawXcorrs.append(np.array(self.
rawXcorrsrawXcorrs[amp]).reshape(nObs*smallLength).tolist())
373 gainList.append(self.
gaingain[amp])
374 noiseList.append(self.
noisenoise[amp])
376 meanXcorrsList.append(self.
meanXcorrsmeanXcorrs[amp].reshape(kernelLength).tolist())
377 kernelList.append(self.
ampKernelsampKernels[amp].reshape(kernelLength).tolist())
378 validList.append(int(self.
validvalid[amp]
and not (amp
in self.
badAmpsbadAmps)))
380 ampTable = Table({
'AMPLIFIER': ampList,
382 'VARIANCES': varianceList,
383 'RAW_XCORRS': rawXcorrs,
386 'MEAN_XCORRS': meanXcorrsList,
387 'KERNEL': kernelList,
391 ampTable.meta = self.getMetadata().
toDict()
392 tableList.append(ampTable)
399 kernelList.append(self.
detKernelsdetKernels[det].reshape(kernelLength).tolist())
401 detTable = Table({
'DETECTOR': detList,
402 'KERNEL': kernelList})
403 detTable.meta = self.getMetadata().
toDict()
404 tableList.append(detTable)
410 """Average the amplifier level kernels to create a detector level kernel.
412 inKernels = np.array([self.
ampKernelsampKernels[amp]
for amp
in
413 self.
ampKernelsampKernels
if amp
not in ampsToExclude])
414 averagingList = np.transpose(inKernels)
415 avgKernel = np.zeros_like(inKernels[0])
416 sctrl = afwMath.StatisticsControl()
417 sctrl.setNumSigmaClip(5.0)
418 for i
in range(np.shape(avgKernel)[0]):
419 for j
in range(np.shape(avgKernel)[1]):
420 avgKernel[i, j] = afwMath.makeStatistics(averagingList[i, j],
421 afwMath.MEANCLIP, sctrl).getValue()
423 self.
detKernelsdetKernels[detectorName] = avgKernel
426 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=[])