LSSTApplications  19.0.0-13-g16625d3,20.0.0+1,20.0.0+13,20.0.0+14,20.0.0+17,20.0.0+3,20.0.0+4,20.0.0+5,20.0.0+7,20.0.0-1-g10df615+13,20.0.0-1-g253301a+6,20.0.0-1-g596936a+15,20.0.0-1-g8a53f90+2,20.0.0-1-gc96f8cb+16,20.0.0-1-gd1c87d7+2,20.0.0-10-g1b4d8e16+2,20.0.0-10-g6931302+2,20.0.0-2-g04cfba9+7,20.0.0-2-gec03fae+4,20.0.0-20-g8c202bc,20.0.0-3-gbdbfa727+7,20.0.0-3-gd2e950e,20.0.0-4-g4a2362f,20.0.0-4-gde602ef96+5,20.0.0-4-ge48a6ca+10,20.0.0-4-ge987224+5,20.0.0-4-gf68bb90+1,w.2020.28
LSSTDataManagementBasePackage
crosstalk.py
Go to the documentation of this file.
1 #
2 # This file is part of obs_decam.
3 #
4 # Developed for the LSST Data Management System.
5 # This product includes software developed by the LSST Project
6 # (http://www.lsst.org).
7 # Copyright 2018 LSST Corporation.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #
22 
23 """Apply crosstalk corrections to DECam images.
24 
25 Some of this code is based on DECam_crosstalk.py, used for the THELI pipeline
26 and written by Thomas Erben (private communication, 2018)
27 """
28 
29 
30 from collections import defaultdict
31 from astropy.io import fits
32 import datetime as dt
33 import os
34 import argparse
35 import re
36 import numpy as np
37 
38 import lsst.log
39 import lsst.pipe.base as pipeBase
40 from lsst.ip.isr import CrosstalkConfig, CrosstalkTask, IsrTask
41 from lsst.obs.decam import DecamMapper
42 from lsst.utils import getPackageDir
43 
44 
45 __all__ = ["DecamCrosstalkTask"]
46 
47 
49  """Configuration for DECam crosstalk removal.
50  """
51 
52  def getSourcesAndCoeffsFile(self, filename='DECam_xtalk_20130606.txt'):
53  """File containing DECam crosstalk coefficients.
54 
55  This text file is provided by NOAO in a particular format with
56  information about the DECam crosstalk coefficients. It is available at
57  http://www.ctio.noao.edu/noao/content/DECam-Calibration-Files
58 
59  Parameters
60  ----------
61  filename : `str`, optional
62  File containing the decam crosstalk coefficients, from NOAO.
63 
64  Returns
65  -------
66  result : `str`
67  Full path to filename.
68  """
69  mapper = DecamMapper()
70  packageName = mapper.getPackageName()
71  packageDir = getPackageDir(packageName)
72  return os.path.join(packageDir, 'decam', filename)
73 
75  """Read crosstalk sources and coefficients from DECam-specific file.
76 
77  Returns
78  -------
79  sources : `defaultdict`
80  Sources of crosstalk read from crosstalk_file.
81  coeffs : `dict`
82  Linear crosstalk coefficients corresponding to sources.
83  """
84  crosstalk_file = self.getSourcesAndCoeffsFile()
85  sources = defaultdict(list)
86  coeffs = {}
87  log = lsst.log.Log.getLogger('obs.decam.DecamCrosstalkConfig')
88  log.info('Reading crosstalk coefficient data')
89  with open(crosstalk_file) as f:
90  for line in f:
91  li = line.strip()
92  if not li.startswith('#'):
93  elem = li.split()
94  # The xtalk file has image areas like 'ccd01A'; preserve only '01A'
95  sources[elem[0][3:]].append(elem[1][3:])
96  # The linear crosstalk coefficients
97  coeffs[(elem[0][3:], elem[1][3:])] = float(elem[2])
98  return sources, coeffs
99 
100 
102  """Remove crosstalk from a raw DECam image.
103 
104  This must be done after the overscan is corrected but before it is trimmed.
105  This version of crosstalk removal assumes you want to do it as part of a
106  regular stack-ISR workflow.
107  """
108  ConfigClass = DecamCrosstalkConfig
109  _DefaultName = 'decamCrosstalk'
110 
111  def prepCrosstalk(self, dataRef, crosstalk=None):
112  """Retrieve source image data and coefficients to prepare for crosstalk correction.
113 
114  This is called by readIsrData. The crosstalkSources land in isrData.
115 
116  Parameters
117  ----------
118  dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
119  DataRef of exposure to correct which must include a dataId with
120  at least one visit and a ccdnum corresponding to the detector id.
121  crosstalk : `lsst.ip.isr.CrosstalkCalib`
122  Crosstalk calibration that will be used.
123 
124  Returns
125  -------
126  crosstalkSources : `defaultdict`
127  Contains image data and corresponding crosstalk coefficients for
128  each crosstalk source of the given dataRef victim.
129  """
130  # Define a butler to prepare for accessing crosstalk 'source' image data
131  try:
132  butler = dataRef.getButler()
133  except RuntimeError:
134  self.log.fatal('Cannot get a Butler from the dataRef provided')
135 
136  # If there is no interChip CT, then we're done.
137  if crosstalk.interChip is None or len(crosstalk.interChip) == 0:
138  return None
139 
140  # Retrieve visit and ccdnum from 'victim' so we can look up 'sources'
141  visit = dataRef.dataId['visit']
142  crosstalkSources = defaultdict(list)
143 
144  decamisr = IsrTask() # needed for source_exposure overscan correction
145  for sourceDet in crosstalk.interChip:
146  try:
147  source_exposure = butler.get('raw', dataId={'visit': visit, 'ccd': sourceDet})
148  except RuntimeError:
149  self.log.warn(f"Cannot access source {sourceDet}, SKIPPING IT")
150  else:
151  self.log.info(f"Correcting victim {crosstalk._detectorName} "
152  f"from crosstalk source {sourceDet}.")
153 
154  for amp in source_exposure.getDetector():
155  decamisr.overscanCorrection(source_exposure, amp)
156 
157  # Do not assemble here, as DECam CT is corrected before assembly.
158  crosstalkSources[sourceDet] = source_exposure
159  return crosstalkSources
160 
161 
162 class DecamCrosstalkIO(pipeBase.Task):
163  """Remove crosstalk from a raw DECam Multi-Extension FITS file.
164 
165  This must be done after the overscan is corrected but before it is trimmed.
166  NOTE: This version of crosstalk removal assumes you want to do it as an
167  standalone file operation, not as part of a regular stack-ISR workflow.
168  """
169  ConfigClass = DecamCrosstalkConfig
170  _DefaultName = 'decamCrosstalkIO'
171 
173  """Parse command-line arguments to run the crosstalk correction alone.
174 
175  Examples
176  --------
177  runCrosstalkAlone.py -f decam0411673.fits.fz -o decam0411673_xtalk_corrected.fits.fz
178  """
179  parser = argparse.ArgumentParser()
180  parser.add_argument('-f', '--filename',
181  help='Location on disk of the DECam MEF file you wish to crosstalk correct')
182  parser.add_argument('-o', '--outfile',
183  help='Location on disk where crosstalk-corrected DECam MEF file will be saved')
184  args = parser.parse_args()
185  parsed = {'filename': args.filename, 'outfile': args.outfile}
186  return parsed
187 
188  def runCrosstalkAlone(self):
189  """Utility for crosstalk correction directly on a DECam MEF file.
190  """
191  log = lsst.log.Log.getLogger('obs.decam.DecamCrosstalkIO')
192  parsed = self.parseCrosstalkIOArgs()
193  filename = parsed['filename']
194  outfile = parsed['outfile']
195  mef = fits.open(filename)
196  sources, coeffs = self.config.getSourcesAndCoeffs()
197  corrected = subtractCrosstalkIO(mef, sources, coeffs)
198 
199  if outfile:
200  corrected.writeto(outfile, overwrite=True)
201  else:
202  log.warning('No outfile specified, not writing corrected image to disk')
203 
204 
205 def subtractCrosstalkIO(mef, sources, coeffs):
206  """Remove crosstalk from a DECam Multi-Extension FITS raw image.
207 
208  This version uses astropy I/O rather than the Butler due to the cross-amp
209  crosstalk and ease of accessing Multi-Extension FITS data. It does not
210  integrate with isr and does not use the Butler.
211 
212  WARNING: this will run fine on a non-overscan-corrected raw image, but the
213  crosstalk coefficients were computed post-overscan-correction. Therefore,
214  **the input raw image should be overscan corrected before this is run.**
215 
216  Parameters
217  ----------
218  mef : `astropy.io.fits.hdu.hdulist.HDUList`
219  One DECam Multi-Extension FITS image.
220  sources : `defaultdict`
221  Crosstalk source areas affecting flux in a certain victim area.
222  coeffs : `list`
223  Linear crosstalk coefficients.
224 
225  Returns
226  -------
227  mef : `astropy.io.fits.hdu.hdulist.HDUList`
228  New MEF image with crosstalk corrected data and updated header.
229  """
230  log = lsst.log.Log.getLogger('obs.decam.subtractCrosstalkIO')
231  lowx = {} # Lower x pixel index of given chip region
232  highx = {} # Upper x pixel index of given chip region
233  extension = {} # FITS extension of given chip position from CCDNUM header
234  corrected = [0] * 63 # placeholder for corrected data
235 
236  for i in range(1, 63):
237  ccd = '%02d' % mef[i].header['CCDNUM']
238  extension[ccd] = i
239  for section in 'AB':
240  ccdsec_str = mef[i].header['DATASEC' + section]
241  ccdsec_list = re.findall(r"[\w']+", ccdsec_str)
242  lowx[ccd + section] = int(ccdsec_list[0]) - 1
243  highx[ccd + section] = int(ccdsec_list[1])
244  corrected[i] = mef[i].data.astype(np.float32)
245  for ccd in ['%02d' % i for i in range(1, 63)]:
246  for section in 'AB':
247  victim = ccd + section
248  victim_data = corrected[extension[ccd]][:, lowx[victim]:highx[victim]]
249  for source in sources[victim]:
250  source_data = mef[extension[source[:2]]].data[:, lowx[source]:highx[source]]
251  if lowx[victim] != lowx[source]:
252  # If regions were read out on different sides of the CCD
253  # (i.e., an A and B mismatch) flip data in the x-direction
254  source_data = np.fliplr(source_data)
255  # Perform linear crosstalk correction
256  victim_data[:, :] = victim_data - coeffs[(victim, source)] * source_data
257  log.info('Correcting victim %s from crosstalk source %s for HDU %s' % (victim, source, ccd))
258 
259  for i in range(1, 63):
260  mef[i].header['HISTORY'] = 'Crosstalk corrected on {0}'.format(
261  dt.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"))
262  mef[i] = fits.ImageHDU(header=mef[i].header, data=corrected[i])
263 
264  return mef
lsst.obs.decam.crosstalk.DecamCrosstalkConfig
Definition: crosstalk.py:48
lsst::log.log.logContinued.warn
def warn(fmt, *args)
Definition: logContinued.py:205
lsst.obs.decam.crosstalk.subtractCrosstalkIO
def subtractCrosstalkIO(mef, sources, coeffs)
Definition: crosstalk.py:205
lsst::log.log.logContinued.info
def info(fmt, *args)
Definition: logContinued.py:201
lsst::ip::isr.crosstalk.CrosstalkTask
Definition: crosstalk.py:552
lsst.obs.decam.decamMapper.DecamMapper
Definition: decamMapper.py:45
lsst.obs.decam.crosstalk.DecamCrosstalkIO
Definition: crosstalk.py:162
pex.config.history.format
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174
ast::append
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
lsst::log::Log::getLogger
static Log getLogger(Log const &logger)
Definition: Log.h:760
lsst::utils::getPackageDir
std::string getPackageDir(std::string const &packageName)
return the root directory of a setup package
Definition: packaging.cc:33
lsst::ip::isr.isrTask.IsrTask
Definition: isrTask.py:844
lsst::log.log.logContinued.fatal
def fatal(fmt, *args)
Definition: logContinued.py:217
lsst.pipe.base.task.Task.log
log
Definition: task.py:148
lsst::log
Definition: Log.h:706
lsst::utils
Definition: Backtrace.h:29
lsst.obs.decam.crosstalk.DecamCrosstalkTask
Definition: crosstalk.py:101
lsst.obs.decam.crosstalk.DecamCrosstalkTask.prepCrosstalk
def prepCrosstalk(self, dataRef, crosstalk=None)
Definition: crosstalk.py:111
lsst::ip::isr
Definition: applyLookupTable.h:34
lsst.obs.decam
Definition: __init__.py:1
lsst.obs.decam.crosstalk.DecamCrosstalkConfig.getSourcesAndCoeffs
def getSourcesAndCoeffs(self)
Definition: crosstalk.py:74
lsst.obs.decam.crosstalk.DecamCrosstalkIO.runCrosstalkAlone
def runCrosstalkAlone(self)
Definition: crosstalk.py:188
lsst.pipe.base
Definition: __init__.py:1
lsst.obs.decam.crosstalk.DecamCrosstalkIO.parseCrosstalkIOArgs
def parseCrosstalkIOArgs(self)
Definition: crosstalk.py:172
lsst::ip::isr.crosstalk.CrosstalkConfig
Definition: crosstalk.py:458
lsst.obs.decam.crosstalk.DecamCrosstalkConfig.getSourcesAndCoeffsFile
def getSourcesAndCoeffsFile(self, filename='DECam_xtalk_20130606.txt')
Definition: crosstalk.py:52