LSST Applications  21.0.0-131-g8cabc107+528f53ee53,22.0.0+00495a2688,22.0.0+0ef2527977,22.0.0+11a2aa21cd,22.0.0+269b7e55e3,22.0.0+2c6b6677a3,22.0.0+64c1bc5aa5,22.0.0+7b3a3f865e,22.0.0+e1b6d2281c,22.0.0+ff3c34362c,22.0.1-1-g1b65d06+c95cbdf3df,22.0.1-1-g7058be7+1cf78af69b,22.0.1-1-g7dab645+2a65e40b06,22.0.1-1-g8760c09+64c1bc5aa5,22.0.1-1-g949febb+64c1bc5aa5,22.0.1-1-ga324b9c+269b7e55e3,22.0.1-1-gf9d8b05+ff3c34362c,22.0.1-10-g781e53d+9b51d1cd24,22.0.1-10-gba590ab+b9624b875d,22.0.1-13-g76f9b8d+2c6b6677a3,22.0.1-14-g22236948+57af756299,22.0.1-18-g3db9cf4b+9b7092c56c,22.0.1-18-gb17765a+2264247a6b,22.0.1-2-g8ef0a89+2c6b6677a3,22.0.1-2-gcb770ba+c99495d3c6,22.0.1-24-g2e899d296+4206820b0d,22.0.1-3-g7aa11f2+2c6b6677a3,22.0.1-3-g8c1d971+f253ffa91f,22.0.1-3-g997b569+ff3b2f8649,22.0.1-4-g1930a60+6871d0c7f6,22.0.1-4-g5b7b756+6b209d634c,22.0.1-6-ga02864e+6871d0c7f6,22.0.1-7-g3402376+a1a2182ac4,22.0.1-7-g65f59fa+54b92689ce,master-gcc5351303a+e1b6d2281c,w.2021.32
LSST Data Management Base Package
Public Member Functions | Static Public Attributes | List of all members
lsst.cp.pipe.makeBrighterFatterKernel.BrighterFatterKernelSolveTask Class Reference
Inheritance diagram for lsst.cp.pipe.makeBrighterFatterKernel.BrighterFatterKernelSolveTask:

Public Member Functions

def runQuantum (self, butlerQC, inputRefs, outputRefs)
 
def run (self, inputPtc, dummy, camera, inputDims)
 
def averageCorrelations (self, xCorrList, name)
 
def quadraticCorrelations (self, xCorrList, fluxList, name)
 
def successiveOverRelax (self, source, maxIter=None, eLevel=None)
 

Static Public Attributes

 ConfigClass = BrighterFatterKernelSolveConfig
 

Detailed Description

Measure appropriate Brighter-Fatter Kernel from the PTC dataset.

Definition at line 142 of file makeBrighterFatterKernel.py.

Member Function Documentation

◆ averageCorrelations()

def lsst.cp.pipe.makeBrighterFatterKernel.BrighterFatterKernelSolveTask.averageCorrelations (   self,
  xCorrList,
  name 
)
Average input correlations.

Parameters
----------
xCorrList : `list` [`numpy.array`]
    List of cross-correlations.
name : `str`
    Name for log messages.

Returns
-------
meanXcorr : `numpy.array`
    The averaged cross-correlation.

Definition at line 327 of file makeBrighterFatterKernel.py.

327  def averageCorrelations(self, xCorrList, name):
328  """Average input correlations.
329 
330  Parameters
331  ----------
332  xCorrList : `list` [`numpy.array`]
333  List of cross-correlations.
334  name : `str`
335  Name for log messages.
336 
337  Returns
338  -------
339  meanXcorr : `numpy.array`
340  The averaged cross-correlation.
341  """
342  meanXcorr = np.zeros_like(xCorrList[0])
343  xCorrList = np.transpose(xCorrList)
344  sctrl = afwMath.StatisticsControl()
345  sctrl.setNumSigmaClip(self.config.nSigmaClip)
346  for i in range(np.shape(meanXcorr)[0]):
347  for j in range(np.shape(meanXcorr)[1]):
348  meanXcorr[i, j] = afwMath.makeStatistics(xCorrList[i, j],
349  afwMath.MEANCLIP, sctrl).getValue()
350 
351  # To match previous definitions, pad by one element.
352  meanXcorr = np.pad(meanXcorr, ((1, 1)))
353 
354  return meanXcorr
355 
Pass parameters to a Statistics object.
Definition: Statistics.h:93
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Definition: Statistics.h:360

◆ quadraticCorrelations()

def lsst.cp.pipe.makeBrighterFatterKernel.BrighterFatterKernelSolveTask.quadraticCorrelations (   self,
  xCorrList,
  fluxList,
  name 
)
Measure a quadratic correlation model.

Parameters
----------
xCorrList : `list` [`numpy.array`]
    List of cross-correlations.
fluxList : `numpy.array`
    Associated list of fluxes.
name : `str`
    Name for log messages.

Returns
-------
meanXcorr : `numpy.array`
    The averaged cross-correlation.

Definition at line 356 of file makeBrighterFatterKernel.py.

356  def quadraticCorrelations(self, xCorrList, fluxList, name):
357  """Measure a quadratic correlation model.
358 
359  Parameters
360  ----------
361  xCorrList : `list` [`numpy.array`]
362  List of cross-correlations.
363  fluxList : `numpy.array`
364  Associated list of fluxes.
365  name : `str`
366  Name for log messages.
367 
368  Returns
369  -------
370  meanXcorr : `numpy.array`
371  The averaged cross-correlation.
372  """
373  meanXcorr = np.zeros_like(xCorrList[0])
374  fluxList = np.square(fluxList)
375  xCorrList = np.array(xCorrList)
376 
377  for i in range(np.shape(meanXcorr)[0]):
378  for j in range(np.shape(meanXcorr)[1]):
379  # Fit corrlation_i(x, y) = a0 + a1 * (flux_i)^2 The
380  # i,j indices are inverted to apply the transposition,
381  # as is done in the averaging case.
382  linearFit, linearFitErr, chiSq, weights = irlsFit([0.0, 1e-4], fluxList,
383  xCorrList[:, j, i], funcPolynomial)
384  meanXcorr[i, j] = linearFit[1] # Discard the intercept.
385  self.log.debug("Quad fit meanXcorr[%d,%d] = %g", i, j, linearFit[1])
386 
387  # To match previous definitions, pad by one element.
388  meanXcorr = np.pad(meanXcorr, ((1, 1)))
389 
390  return meanXcorr
391 
def irlsFit(initialParams, dataX, dataY, function, weightsY=None, weightType='Cauchy')
Definition: utils.py:327

◆ run()

def lsst.cp.pipe.makeBrighterFatterKernel.BrighterFatterKernelSolveTask.run (   self,
  inputPtc,
  dummy,
  camera,
  inputDims 
)
Combine covariance information from PTC into brighter-fatter kernels.

Parameters
----------
inputPtc : `lsst.ip.isr.PhotonTransferCurveDataset`
    PTC data containing per-amplifier covariance measurements.
dummy : `lsst.afw.image.Exposure`
    The exposure used to select the appropriate PTC dataset.
    In almost all circumstances, one of the input exposures
    used to generate the PTC dataset is the best option.
camera : `lsst.afw.cameraGeom.Camera`
    Camera to use for camera geometry information.
inputDims : `lsst.daf.butler.DataCoordinate` or `dict`
    DataIds to use to populate the output calibration.

Returns
-------
results : `lsst.pipe.base.Struct`
    The resulst struct containing:

    ``outputBfk`` : `lsst.ip.isr.BrighterFatterKernel`
        Resulting Brighter-Fatter Kernel.

Definition at line 169 of file makeBrighterFatterKernel.py.

169  def run(self, inputPtc, dummy, camera, inputDims):
170  """Combine covariance information from PTC into brighter-fatter kernels.
171 
172  Parameters
173  ----------
174  inputPtc : `lsst.ip.isr.PhotonTransferCurveDataset`
175  PTC data containing per-amplifier covariance measurements.
176  dummy : `lsst.afw.image.Exposure`
177  The exposure used to select the appropriate PTC dataset.
178  In almost all circumstances, one of the input exposures
179  used to generate the PTC dataset is the best option.
180  camera : `lsst.afw.cameraGeom.Camera`
181  Camera to use for camera geometry information.
182  inputDims : `lsst.daf.butler.DataCoordinate` or `dict`
183  DataIds to use to populate the output calibration.
184 
185  Returns
186  -------
187  results : `lsst.pipe.base.Struct`
188  The resulst struct containing:
189 
190  ``outputBfk`` : `lsst.ip.isr.BrighterFatterKernel`
191  Resulting Brighter-Fatter Kernel.
192  """
193  if len(dummy) == 0:
194  self.log.warn("No dummy exposure found.")
195 
196  detector = camera[inputDims['detector']]
197  detName = detector.getName()
198 
199  if self.config.level == 'DETECTOR':
200  detectorCorrList = list()
201 
202  bfk = BrighterFatterKernel(camera=camera, detectorId=detector.getId(), level=self.config.level)
203  bfk.means = inputPtc.finalMeans # ADU
204  bfk.variances = inputPtc.finalVars # ADU^2
205  # Use the PTC covariances as the cross-correlations. These
206  # are scaled before the kernel is generated, which performs
207  # the conversion.
208  bfk.rawXcorrs = inputPtc.covariances # ADU^2
209  bfk.badAmps = inputPtc.badAmps
210  bfk.shape = (inputPtc.covMatrixSide*2 + 1, inputPtc.covMatrixSide*2 + 1)
211  bfk.gain = inputPtc.gain
212  bfk.noise = inputPtc.noise
213  bfk.meanXcorrs = dict()
214  bfk.valid = dict()
215 
216  for amp in detector:
217  ampName = amp.getName()
218  mask = np.array(inputPtc.expIdMask[ampName], dtype=bool)
219 
220  gain = bfk.gain[ampName]
221  fluxes = np.array(bfk.means[ampName])[mask]
222  variances = np.array(bfk.variances[ampName])[mask]
223  xCorrList = [np.array(xcorr) for xcorr in bfk.rawXcorrs[ampName]]
224  xCorrList = np.array(xCorrList)[mask]
225 
226  if gain <= 0:
227  # We've received very bad data.
228  self.log.warn("Impossible gain recieved from PTC for %s: %f. Skipping amplifier.",
229  ampName, gain)
230  bfk.meanXcorrs[ampName] = np.zeros(bfk.shape)
231  bfk.ampKernels[ampName] = np.zeros(bfk.shape)
232  bfk.valid[ampName] = False
233  continue
234 
235  fluxes = np.array([flux*gain for flux in fluxes]) # Now in e^-
236  variances = np.array([variance*gain*gain for variance in variances]) # Now in e^2-
237 
238  # This should duplicate Coulton et al. 2017 Equation 22-29 (arxiv:1711.06273)
239  scaledCorrList = list()
240  for xcorrNum, (xcorr, flux, var) in enumerate(zip(xCorrList, fluxes, variances), 1):
241  q = np.array(xcorr) * gain * gain # xcorr now in e^-
242  q *= 2.0 # Remove factor of 1/2 applied in PTC.
243  self.log.info("Amp: %s %d/%d Flux: %f Var: %f Q(0,0): %g Q(1,0): %g Q(0,1): %g",
244  ampName, xcorrNum, len(xCorrList), flux, var, q[0][0], q[1][0], q[0][1])
245 
246  # Normalize by the flux, which removes the (0,0)
247  # component attributable to Poisson noise. This
248  # contains the two "t I delta(x - x')" terms in Coulton et al. 2017 equation 29
249  q[0][0] -= 2.0*(flux)
250 
251  if q[0][0] > 0.0:
252  self.log.warn("Amp: %s %d skipped due to value of (variance-mean)=%f",
253  ampName, xcorrNum, q[0][0])
254  continue
255 
256  # This removes the "t (I_a^2 + I_b^2)" factor in Coulton et al. 2017 equation 29.
257  q /= -2.0*(flux**2)
258  scaled = self._tileArray(q)
259 
260  xcorrCheck = np.abs(np.sum(scaled))/np.sum(np.abs(scaled))
261  if (xcorrCheck > self.config.xcorrCheckRejectLevel) or not (np.isfinite(xcorrCheck)):
262  self.log.warn("Amp: %s %d skipped due to value of triangle-inequality sum %f",
263  ampName, xcorrNum, xcorrCheck)
264  continue
265 
266  scaledCorrList.append(scaled)
267  self.log.info("Amp: %s %d/%d Final: %g XcorrCheck: %f",
268  ampName, xcorrNum, len(xCorrList), q[0][0], xcorrCheck)
269 
270  if len(scaledCorrList) == 0:
271  self.log.warn("Amp: %s All inputs rejected for amp!", ampName)
272  bfk.meanXcorrs[ampName] = np.zeros(bfk.shape)
273  bfk.ampKernels[ampName] = np.zeros(bfk.shape)
274  bfk.valid[ampName] = False
275  continue
276 
277  if self.config.useAmatrix:
278  # Use the aMatrix, ignoring the meanXcorr generated above.
279  preKernel = np.pad(self._tileArray(np.array(inputPtc.aMatrix[ampName])), ((1, 1)))
280  elif self.config.correlationQuadraticFit:
281  # Use a quadratic fit to the correlations as a function of flux.
282  preKernel = self.quadraticCorrelations(scaledCorrList, fluxes, f"Amp: {ampName}")
283  else:
284  # Use a simple average of the measured correlations.
285  preKernel = self.averageCorrelations(scaledCorrList, f"Amp: {ampName}")
286 
287  center = int((bfk.shape[0] - 1) / 2)
288 
289  if self.config.forceZeroSum:
290  totalSum = np.sum(preKernel)
291 
292  if self.config.correlationModelRadius < (preKernel.shape[0] - 1) / 2:
293  # Assume a correlation model of Corr(r) = -preFactor * r^(2 * slope)
294  preFactor = np.sqrt(preKernel[center, center + 1] * preKernel[center + 1, center])
295  slopeFactor = 2.0 * np.abs(self.config.correlationModelSlope)
296  totalSum += 2.0*np.pi*(preFactor / (slopeFactor*(center + 0.5))**slopeFactor)
297 
298  preKernel[center, center] -= totalSum
299  self.log.info("%s Zero-Sum Scale: %g", ampName, totalSum)
300 
301  finalSum = np.sum(preKernel)
302  bfk.meanXcorrs[ampName] = preKernel
303 
304  postKernel = self.successiveOverRelax(preKernel)
305  bfk.ampKernels[ampName] = postKernel
306  if self.config.level == 'DETECTOR':
307  detectorCorrList.extend(scaledCorrList)
308  bfk.valid[ampName] = True
309  self.log.info("Amp: %s Sum: %g Center Info Pre: %g Post: %g",
310  ampName, finalSum, preKernel[center, center], postKernel[center, center])
311 
312  # Assemble a detector kernel?
313  if self.config.level == 'DETECTOR':
314  preKernel = self.averageCorrelations(detectorCorrList, f"Det: {detName}")
315  finalSum = np.sum(preKernel)
316  center = int((bfk.shape[0] - 1) / 2)
317 
318  postKernel = self.successiveOverRelax(preKernel)
319  bfk.detKernels[detName] = postKernel
320  self.log.info("Det: %s Sum: %g Center Info Pre: %g Post: %g",
321  detName, finalSum, preKernel[center, center], postKernel[center, center])
322 
323  return pipeBase.Struct(
324  outputBFK=bfk,
325  )
326 
daf::base::PropertyList * list
Definition: fits.cc:913
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)

◆ runQuantum()

def lsst.cp.pipe.makeBrighterFatterKernel.BrighterFatterKernelSolveTask.runQuantum (   self,
  butlerQC,
  inputRefs,
  outputRefs 
)
Ensure that the input and output dimensions are passed along.

Parameters
----------
butlerQC : `lsst.daf.butler.butlerQuantumContext.ButlerQuantumContext`
    Butler to operate on.
inputRefs : `lsst.pipe.base.connections.InputQuantizedConnection`
    Input data refs to load.
ouptutRefs : `lsst.pipe.base.connections.OutputQuantizedConnection`
    Output data refs to persist.

Definition at line 149 of file makeBrighterFatterKernel.py.

149  def runQuantum(self, butlerQC, inputRefs, outputRefs):
150  """Ensure that the input and output dimensions are passed along.
151 
152  Parameters
153  ----------
154  butlerQC : `lsst.daf.butler.butlerQuantumContext.ButlerQuantumContext`
155  Butler to operate on.
156  inputRefs : `lsst.pipe.base.connections.InputQuantizedConnection`
157  Input data refs to load.
158  ouptutRefs : `lsst.pipe.base.connections.OutputQuantizedConnection`
159  Output data refs to persist.
160  """
161  inputs = butlerQC.get(inputRefs)
162 
163  # Use the dimensions to set calib/provenance information.
164  inputs['inputDims'] = inputRefs.inputPtc.dataId.byName()
165 
166  outputs = self.run(**inputs)
167  butlerQC.put(outputs, outputRefs)
168 

◆ successiveOverRelax()

def lsst.cp.pipe.makeBrighterFatterKernel.BrighterFatterKernelSolveTask.successiveOverRelax (   self,
  source,
  maxIter = None,
  eLevel = None 
)
An implementation of the successive over relaxation (SOR) method.

A numerical method for solving a system of linear equations
with faster convergence than the Gauss-Seidel method.

Parameters:
-----------
source : `numpy.ndarray`
    The input array.
maxIter : `int`, optional
    Maximum number of iterations to attempt before aborting.
eLevel : `float`, optional
    The target error level at which we deem convergence to have
    occurred.

Returns:
--------
output : `numpy.ndarray`
    The solution.

Definition at line 432 of file makeBrighterFatterKernel.py.

432  def successiveOverRelax(self, source, maxIter=None, eLevel=None):
433  """An implementation of the successive over relaxation (SOR) method.
434 
435  A numerical method for solving a system of linear equations
436  with faster convergence than the Gauss-Seidel method.
437 
438  Parameters:
439  -----------
440  source : `numpy.ndarray`
441  The input array.
442  maxIter : `int`, optional
443  Maximum number of iterations to attempt before aborting.
444  eLevel : `float`, optional
445  The target error level at which we deem convergence to have
446  occurred.
447 
448  Returns:
449  --------
450  output : `numpy.ndarray`
451  The solution.
452  """
453  if not maxIter:
454  maxIter = self.config.maxIterSuccessiveOverRelaxation
455  if not eLevel:
456  eLevel = self.config.eLevelSuccessiveOverRelaxation
457 
458  assert source.shape[0] == source.shape[1], "Input array must be square"
459  # initialize, and set boundary conditions
460  func = np.zeros([source.shape[0] + 2, source.shape[1] + 2])
461  resid = np.zeros([source.shape[0] + 2, source.shape[1] + 2])
462  rhoSpe = np.cos(np.pi/source.shape[0]) # Here a square grid is assumed
463 
464  # Calculate the initial error
465  for i in range(1, func.shape[0] - 1):
466  for j in range(1, func.shape[1] - 1):
467  resid[i, j] = (func[i, j - 1] + func[i, j + 1] + func[i - 1, j]
468  + func[i + 1, j] - 4*func[i, j] - source[i - 1, j - 1])
469  inError = np.sum(np.abs(resid))
470 
471  # Iterate until convergence
472  # We perform two sweeps per cycle,
473  # updating 'odd' and 'even' points separately
474  nIter = 0
475  omega = 1.0
476  dx = 1.0
477  while nIter < maxIter*2:
478  outError = 0
479  if nIter%2 == 0:
480  for i in range(1, func.shape[0] - 1, 2):
481  for j in range(1, func.shape[1] - 1, 2):
482  resid[i, j] = float(func[i, j-1] + func[i, j + 1] + func[i - 1, j]
483  + func[i + 1, j] - 4.0*func[i, j] - dx*dx*source[i - 1, j - 1])
484  func[i, j] += omega*resid[i, j]*.25
485  for i in range(2, func.shape[0] - 1, 2):
486  for j in range(2, func.shape[1] - 1, 2):
487  resid[i, j] = float(func[i, j - 1] + func[i, j + 1] + func[i - 1, j]
488  + func[i + 1, j] - 4.0*func[i, j] - dx*dx*source[i - 1, j - 1])
489  func[i, j] += omega*resid[i, j]*.25
490  else:
491  for i in range(1, func.shape[0] - 1, 2):
492  for j in range(2, func.shape[1] - 1, 2):
493  resid[i, j] = float(func[i, j - 1] + func[i, j + 1] + func[i - 1, j]
494  + func[i + 1, j] - 4.0*func[i, j] - dx*dx*source[i - 1, j - 1])
495  func[i, j] += omega*resid[i, j]*.25
496  for i in range(2, func.shape[0] - 1, 2):
497  for j in range(1, func.shape[1] - 1, 2):
498  resid[i, j] = float(func[i, j - 1] + func[i, j + 1] + func[i - 1, j]
499  + func[i + 1, j] - 4.0*func[i, j] - dx*dx*source[i - 1, j - 1])
500  func[i, j] += omega*resid[i, j]*.25
501  outError = np.sum(np.abs(resid))
502  if outError < inError*eLevel:
503  break
504  if nIter == 0:
505  omega = 1.0/(1 - rhoSpe*rhoSpe/2.0)
506  else:
507  omega = 1.0/(1 - rhoSpe*rhoSpe*omega/4.0)
508  nIter += 1
509 
510  if nIter >= maxIter*2:
511  self.log.warn("Failure: SuccessiveOverRelaxation did not converge in %s iterations."
512  "\noutError: %s, inError: %s," % (nIter//2, outError, inError*eLevel))
513  else:
514  self.log.info("Success: SuccessiveOverRelaxation converged in %s iterations."
515  "\noutError: %s, inError: %s", nIter//2, outError, inError*eLevel)
516  return func[1: -1, 1: -1]

Member Data Documentation

◆ ConfigClass

lsst.cp.pipe.makeBrighterFatterKernel.BrighterFatterKernelSolveTask.ConfigClass = BrighterFatterKernelSolveConfig
static

Definition at line 146 of file makeBrighterFatterKernel.py.


The documentation for this class was generated from the following file: