Skip to content

Commit

Permalink
Split doAmpOffset into measurement and application components
Browse files Browse the repository at this point in the history
  • Loading branch information
enourbakhsh committed Aug 1, 2024
1 parent 965a903 commit 4be5b84
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 13 deletions.
31 changes: 21 additions & 10 deletions python/lsst/ip/isr/ampOffset.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ def setDefaults(self):
dtype=bool,
default=True,
)
doApplyAmpOffset = Field(
doc="Apply amp offset corrections to the input exposure?",
dtype=bool,
default=False,
)


class AmpOffsetTask(Task):
Expand Down Expand Up @@ -235,22 +240,28 @@ def run(self, exposure):
# ensuring that no values are erroneously added/subtracted.
pedestals = np.nan_to_num(np.linalg.lstsq(A, B, rcond=None)[0])

metadata = exposure.getMetadata() # Exposure metadata.
if self.config.doApplyAmpOffset:
metadata = exposure.getMetadata() # Exposure metadata.
status = "applied"
else:
status = "not applied"

self.metadata["AMPOFFSET_PEDESTALS"] = {} # Task metadata.
for amp, pedestal in zip(amps, pedestals):
ampIm = exposure.image[amp.getBBox()].array
ampIm -= pedestal
ampName = amp.getName()
# Add the amp pedestal to the exposure metadata.
metadata.set(
f"LSST ISR AMPOFFSET PEDESTAL {ampName}",
float(pedestal),
f"Pedestal level subtracted from amp {ampName}",
)
if self.config.doApplyAmpOffset:
ampIm = exposure.image[amp.getBBox()].array
ampIm -= pedestal
# Add the amp pedestal to the exposure metadata.
metadata.set(
f"LSST ISR AMPOFFSET PEDESTAL {ampName}",
float(pedestal),
f"Pedestal level subtracted from amp {ampName}",
)
# Add the amp pedestal to the "Task" metadata as well.
# Needed for Sasquatch/Chronograf!
self.metadata["AMPOFFSET_PEDESTALS"][ampName] = float(pedestal)
self.log.info(f"amp pedestal values: {', '.join([f'{x:.4f}' for x in pedestals])}")
self.log.info(f"amp pedestal values ({status}): {', '.join([f'{x:.4f}' for x in pedestals])}")

return Struct(pedestals=pedestals)

Expand Down
42 changes: 39 additions & 3 deletions python/lsst/ip/isr/isrTask.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
from .overscan import OverscanCorrectionTask
from .straylight import StrayLightTask
from .vignette import VignetteTask
from .ampOffset import AmpOffsetTask
from .ampOffset import AmpOffsetConfig, AmpOffsetTask
from .deferredCharge import DeferredChargeTask
from .isrStatistics import IsrStatisticsTask
from .ptcDataset import PhotonTransferCurveDataset
Expand Down Expand Up @@ -768,10 +768,22 @@ class IsrTaskConfig(pipeBase.PipelineTaskConfig,
)

# Amp offset correction.
# TODO: Remove this config field on DM-45526.
doAmpOffset = pexConfig.Field(
doc="Calculate and apply amp offset corrections?",
dtype=bool,
default=False,
deprecated="This field is no longer used. Will be removed after v28."
)
doMeasureAmpOffset = pexConfig.Field(
doc="Measure amp offset corrections?",
dtype=bool,
default=False,
)
doApplyAmpOffset = pexConfig.Field(
doc="Apply amp offset corrections?",
dtype=bool,
default=False,
)
ampOffset = pexConfig.ConfigurableField(
doc="Amp offset correction task.",
Expand Down Expand Up @@ -1702,10 +1714,34 @@ def run(self, ccdExposure, *, camera=None, bias=None, linearizer=None,

self.roughZeroPoint(ccdExposure)

# Add extra config validation logic that checks for compatibility
# among the config options for amp offset correction.
if self.config.doApplyAmpOffset and not self.config.doMeasureAmpOffset:
raise RuntimeError("doApplyAmpOffset requires doMeasureAmpOffset to be True.")

# Measure amp offset corrections within the CCD.
if self.config.doMeasureAmpOffset:
ampOffsetConfig = AmpOffsetConfig()
ampOffsetConfig.doMeasureAmpOffset = True
if self.config.doApplyAmpOffset:
# Correct for amp offsets within the CCD.
ampOffsetConfig.doApplyAmpOffset = True
self.log.info("Calculating and applying amp offset corrections.")
else:
ampOffsetConfig.doApplyAmpOffset = False
self.log.info("Calculating amp offset corrections without applying them.")
ampOffsetTask = self.ampOffset(config=ampOffsetConfig)
ampOffsetTask.run(ccdExposure)

# correct for amp offsets within the CCD
# TODO: Remove this whole ``if`` clause on DM-45526.
if self.config.doAmpOffset:
self.log.info("Correcting amp offsets.")
self.ampOffset.run(ccdExposure)
if self.config.doMeasureBackground or self.config.doApplyAmpOffset:
raise RuntimeError("Enabling doMeasureBackground or doApplyAmpOffset "
"requires doAmpOffset to be False.")
else:
self.log.info("Correcting amp offsets.")
self.ampOffset.run(ccdExposure)

if self.config.doMeasureBackground:
self.log.info("Measuring background level.")
Expand Down
36 changes: 36 additions & 0 deletions python/lsst/ip/isr/isrTaskLSST.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import lsst.pipe.base.connectionTypes as cT
from lsst.meas.algorithms.detection import SourceDetectionTask

from .ampOffset import AmpOffsetConfig, AmpOffsetTask
from .overscan import SerialOverscanCorrectionTask, ParallelOverscanCorrectionTask
from .overscanAmpConfig import OverscanCameraConfig
from .assembleCcdTask import AssembleCcdTask
Expand Down Expand Up @@ -369,6 +370,22 @@ class IsrTaskLSSTConfig(pipeBase.PipelineTaskConfig,
default=False,
)

# Amp offset correction.
doMeasureAmpOffset = pexConfig.Field(
doc="Measure amp offset corrections?",
dtype=bool,
default=False,
)
doApplyAmpOffset = pexConfig.Field(
doc="Apply amp offset corrections?",
dtype=bool,
default=False,
)
ampOffset = pexConfig.ConfigurableField(
doc="Amp offset correction task.",
target=AmpOffsetTask,
)

# Initial masking options.
doSetBadRegions = pexConfig.Field(
dtype=bool,
Expand Down Expand Up @@ -1523,6 +1540,25 @@ def run(self, ccdExposure, *, dnlLUT=None, bias=None, deferredChargeCalib=None,
maskNameList=list(self.config.maskListToInterpolate)
)

# Add extra config validation logic that checks for compatibility
# among the config options for amp offset correction.
if self.config.doApplyAmpOffset and not self.config.doMeasureAmpOffset:
raise RuntimeError("doApplyAmpOffset requires doMeasureAmpOffset to be True.")

# Measure amp offset corrections within the CCD.
if self.config.doMeasureAmpOffset:
ampOffsetConfig = AmpOffsetConfig()
ampOffsetConfig.doMeasureAmpOffset = True
if self.config.doApplyAmpOffset:
# Correct for amp offsets within the CCD.
ampOffsetConfig.doApplyAmpOffset = True
self.log.info("Calculating and applying amp offset corrections.")
else:
ampOffsetConfig.doApplyAmpOffset = False
self.log.info("Calculating amp offset corrections without applying them.")
ampOffsetTask = self.ampOffset(config=ampOffsetConfig)
ampOffsetTask.run(ccdExposure)

# Calculate standard image quality statistics
if self.config.doStandardStatistics:
metadata = ccdExposure.getMetadata()
Expand Down
2 changes: 2 additions & 0 deletions tests/test_ampOffset.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ def runAmpOffsetWithBackground(self, valueType, rampBackground=False):
config.doDetection = True
config.ampEdgeWidth = 12
config.applyWeights = applyWeights
config.doApplyAmpOffset = True # Updates the exposure in place.
if valueType == "random":
# For this specific case, the fraction of unmasked pixels for
# amp interface 01 is unusually small.
Expand Down Expand Up @@ -372,6 +373,7 @@ def testAmpOffset(self, valueType):
config.doBackground = False
config.doDetection = False
config.ampEdgeWidth = 12 # Given 100x51 amps in our mock detector.
config.doApplyAmpOffset = True # Updates the exposure in place.
if valueType == "artificial":
# For this extreme case, we expect the interface offsets to be
# unusually large.
Expand Down

0 comments on commit 4be5b84

Please sign in to comment.