Skip to content

Commit

Permalink
Clarify ISR log messages, comments, and docstrings.
Browse files Browse the repository at this point in the history
Change isrFunctions.brighterFatterCorrection to return final
difference and iteration count, so an old log message can be restored.

Remove old documentation that was causing build warnings.
  • Loading branch information
czwa committed Jun 20, 2019
1 parent fd3334c commit 95df18a
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 110 deletions.
21 changes: 0 additions & 21 deletions python/lsst/ip/isr/assembleCcdTask.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,27 +105,6 @@ class AssembleCcdTask(pipeBase.Task):
@section ip_isr_assemble_Example A complete example of using AssembleCcdTask
This code is in runAssembleTask.py in the examples directory, and can be run as @em e.g.
@code
python examples/runAssembleTask.py
@endcode
@dontinclude runAssembleTask.py
Import the task. There are other imports. Read the source file for more info.
@skipline AssembleCcdTask
@dontinclude exampleUtils.py
Create some input images with the help of some utilities in examples/exampleUtils.py
@skip makeAssemblyInput
@until inputData
The above numbers can be changed. The assumption that the readout corner is flipped on every other amp is
hardcoded in createDetector.
@dontinclude runAssembleTask.py
Run the assembler task
@skip runAssembler
@until frame += 1
<HR>
To investigate the @ref ip_isr_assemble_Debug, put something like
@code{.py}
Expand Down
2 changes: 1 addition & 1 deletion python/lsst/ip/isr/crosstalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def run(self, exposure, crosstalkSources=None, isTrimmed=False):
detector = exposure.getDetector()
if not detector.hasCrosstalk():
raise RuntimeError("Attempted to correct crosstalk without crosstalk coefficients")
self.log.info("Applying crosstalk correction")
self.log.info("Applying crosstalk correction.")
subtractCrosstalk(exposure, minPixelToMask=self.config.minPixelToMask,
crosstalkStr=self.config.crosstalkMaskPlane, isTrimmed=isTrimmed,
backgroundMethod=self.config.crosstalkBackgroundMethod)
Expand Down
188 changes: 152 additions & 36 deletions python/lsst/ip/isr/fringe.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,31 @@ class FringeTask(Task):
_DefaultName = 'isrFringe'

def readFringes(self, dataRef, assembler=None):
"""Read the fringe frame(s)
"""Read the fringe frame(s), and pack data into a Struct
The current implementation assumes only a single fringe frame and
will have to be updated to support multi-mode fringe subtraction.
This implementation could be optimised by persisting the fringe
positions and fluxes.
@param dataRef Data reference for the science exposure
@param assembler An instance of AssembleCcdTask (for assembling fringe frames)
@return Struct(fringes: fringe exposure or list of fringe exposures;
seed: 32-bit uint derived from ccdExposureId for random number generator
Parameters
----------
dataRef : `daf.butler.butlerSubset.ButlerDataRef`
Butler reference for the exposure that will have fringing
removed.
assembler : `lsst.ip.isr.AssembleCcdTask`, optional
An instance of AssembleCcdTask (for assembling fringe
frames).
Returns
-------
fringeData : `pipeBase.Struct`
Struct containing fringe data:
- ``fringes`` : `lsst.afw.image.Exposure` or `list` thereof
Calibration fringe files containing master fringe frames.
- ``seed`` : `int`, optional
Seed for random number generation.
"""
try:
fringe = dataRef.get("fringe", immediate=True)
Expand All @@ -108,14 +121,27 @@ def run(self, exposure, fringes, seed=None):
Primary method of FringeTask. Fringes are only subtracted if the
science exposure has a filter listed in the configuration.
@param exposure Science exposure from which to remove fringes
@param fringes Exposure or list of Exposures
@param seed 32-bit unsigned integer for random number generator
Parameters
----------
exposure : `lsst.afw.image.Exposure`
Science exposure from which to remove fringes.
fringes : `lsst.afw.image.Exposure` or `list` thereof
Calibration fringe files containing master fringe frames.
seed : `int`, optional
Seed for random number generation.
Returns
-------
solution : `np.array`
Fringe solution amplitudes for each input fringe frame.
rms : `float`
RMS error for the fit solution for this exposure.
"""
import lsstDebug
display = lsstDebug.Info(__name__).display

if not self.checkFilter(exposure):
self.log.info("Filter not found in FringeTaskConfig.filters. Skipping fringe correction.")
return

if seed is None:
Expand Down Expand Up @@ -148,25 +174,57 @@ def runDataRef(self, exposure, dataRef, assembler=None):
"""Remove fringes from the provided science exposure.
Retrieve fringes from butler dataRef provided and remove from
provided science exposure.
Fringes are only subtracted if the science exposure has a filter
listed in the configuration.
provided science exposure. Fringes are only subtracted if the
science exposure has a filter listed in the configuration.
@param exposure Science exposure from which to remove fringes
@param dataRef Data reference for the science exposure
@param assembler An instance of AssembleCcdTask (for assembling fringe frames)
Parameters
----------
exposure : `lsst.afw.image.Exposure`
Science exposure from which to remove fringes.
dataRef : `daf.persistence.butlerSubset.ButlerDataRef`
Butler reference to the exposure. Used to find
appropriate fringe data.
assembler : `lsst.ip.isr.AssembleCcdTask`, optional
An instance of AssembleCcdTask (for assembling fringe
frames).
Returns
-------
solution : `np.array`
Fringe solution amplitudes for each input fringe frame.
rms : `float`
RMS error for the fit solution for this exposure.
"""
if not self.checkFilter(exposure):
self.log.info("Filter not found in FringeTaskConfig.filters. Skipping fringe correction.")
return
fringeStruct = self.readFringes(dataRef, assembler=assembler)
return self.run(exposure, **fringeStruct.getDict())

def checkFilter(self, exposure):
"""Check whether we should fringe-subtract the science exposure"""
"""Check whether we should fringe-subtract the science exposure.
Parameters
----------
exposure : `lsst.afw.image.Exposure`
Exposure to check the filter of.
Returns
-------
needsFringe : `bool`
If True, then the exposure has a filter listed in the
configuration, and should have the fringe applied.
"""
return exposure.getFilter().getName() in self.config.filters

def removePedestal(self, fringe):
"""Remove pedestal from fringe exposure"""
"""Remove pedestal from fringe exposure.
Parameters
----------
fringe : `lsst.afw.image.Exposure`
Fringe data to subtract the pedestal value from.
"""
stats = afwMath.StatisticsControl()
stats.setNumSigmaClip(self.config.stats.clip)
stats.setNumIter(self.config.stats.iterations)
Expand All @@ -176,7 +234,21 @@ def removePedestal(self, fringe):
mi -= pedestal

def generatePositions(self, exposure, rng):
"""Generate a random distribution of positions for measuring fringe amplitudes"""
"""Generate a random distribution of positions for measuring fringe amplitudes.
Parameters
----------
exposure : `lsst.afw.image.Exposure`
Exposure to measure the positions on.
rng : `numpy.random.RandomState`
Random number generator to use.
Returns
-------
positions : `numpy.array`
Two-dimensional array containing the positions to sample
for fringe amplitudes.
"""
start = self.config.large
num = self.config.num
width = exposure.getWidth() - self.config.large
Expand All @@ -192,10 +264,21 @@ def measureExposure(self, exposure, positions, title="Fringe"):
aperture. The statistic within a larger aperture are subtracted so
as to remove the background.
@param exposure Exposure to measure
@param positions Array of (x,y) for fringe measurement
@param title Title for display
@return Array of fringe measurements
Parameters
----------
exposure : `lsst.afw.image.Exposure`
Exposure to measure the positions on.
positions : `numpy.array`
Two-dimensional array containing the positions to sample
for fringe amplitudes.
title : `str`, optional
Title used for debug out plots.
Returns
-------
fringes : `numpy.array`
Array of measured exposure values at each of the positions
supplied.
"""
stats = afwMath.StatisticsControl()
stats.setNumSigmaClip(self.config.stats.clip)
Expand Down Expand Up @@ -227,11 +310,23 @@ def measureExposure(self, exposure, positions, title="Fringe"):

@timeMethod
def solve(self, science, fringes):
"""Solve (with iterative clipping) for the scale factors
@param science Array of science exposure fringe amplitudes
@param fringes Array of arrays of fringe frame fringe amplitudes
@return Array of scale factors for the fringe frames
"""Solve for the scale factors with iterative clipping.
Parameters
----------
science : `numpy.array`
Array of measured science image values at each of the
positions supplied.
fringes : `numpy.array`
Array of measured fringe values at each of the positions
supplied.
Returns
-------
solution : `np.array`
Fringe solution amplitudes for each input fringe frame.
rms : `float`
RMS error for the fit solution for this exposure.
"""
import lsstDebug
doPlot = lsstDebug.Info(__name__).plot
Expand Down Expand Up @@ -330,21 +425,42 @@ def emptyResult(msg=""):
return solution, rms

def _solve(self, science, fringes):
"""Solve for the scale factors
@param science Array of science exposure fringe amplitudes
@param fringes Array of arrays of fringe frame fringe amplitudes
@return Array of scale factors for the fringe frames
"""Solve for the scale factors.
Parameters
----------
science : `numpy.array`
Array of measured science image values at each of the
positions supplied.
fringes : `numpy.array`
Array of measured fringe values at each of the positions
supplied.
Returns
-------
solution : `np.array`
Fringe solution amplitudes for each input fringe frame.
"""
return afwMath.LeastSquares.fromDesignMatrix(fringes, science,
afwMath.LeastSquares.DIRECT_SVD).getSolution()

def subtract(self, science, fringes, solution):
"""Subtract the fringes
@param science Science exposure
@param fringes List of fringe frames
@param solution Array of scale factors for the fringe frames
"""Subtract the fringes.
Parameters
----------
science : `lsst.afw.image.Exposure`
Science exposure from which to remove fringes.
fringes : `lsst.afw.image.Exposure` or `list` thereof
Calibration fringe files containing master fringe frames.
solution : `np.array`
Fringe solution amplitudes for each input fringe frame.
Raises
------
RuntimeError :
Raised if the number of fringe frames does not match the
number of measured amplitudes.
"""
if len(solution) != len(fringes):
raise RuntimeError("Number of fringe frames (%s) != number of scale factors (%s)" %
Expand Down
14 changes: 9 additions & 5 deletions python/lsst/ip/isr/isrFunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,13 @@ def brighterFatterCorrection(exposure, kernel, maxIter, threshold, applyGain):
If True, then the exposure values are scaled by the gain prior
to correction.
Returns
-------
diff : `float`
Final difference between iterations achieved in correction.
iteration : `int`
Number of iterations used to calculate correction.
Notes
-----
This correction takes a kernel that has been derived from flat
Expand Down Expand Up @@ -827,14 +834,11 @@ def brighterFatterCorrection(exposure, kernel, maxIter, threshold, applyGain):
break
prev_image[:, :] = tmpArray[:, :]

# if iteration == maxIter - 1:
# self.log.warn("Brighter fatter correction did not converge,
# final difference %f" % diff)

# self.log.info("Finished brighter fatter in %d iterations" % (iteration + 1))
image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
corr[startY + 1:endY - 1, startX + 1:endX - 1]

return diff, iteration


@contextmanager
def gainContext(exp, image, apply):
Expand Down

0 comments on commit 95df18a

Please sign in to comment.