Creating an image mask
Calibration cannot compensate for every defect in a CCD. Some examples (a non-exhaustive list):
- Some hot pixels are not actually linear with exposure time.
- Some pixels in the CCD may respond less to light than others in a way that flat frames cannot compensate for.
- There may be defects in all or part of a row or column of the chip.
- Cosmic rays strike the CCD during every exposure. While those are eliminated in the combined calibrated frames with the proper choice of combination parameters, they are not removed from science images.
The first three are discussed in this notebook. Removal of cosmic rays from science images is discussed in the cosmic ray notebook
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
from astropy import units as u
from astropy.nddata import CCDData
import ccdproc as ccdp
from photutils import detect_sources
from astrowidgets import ImageWidget
from convenience_functions import show_image, image_snippet
# Use custom style for larger fonts and figures
plt.style.use('guide.mplstyle')
The ccdproc function ccdmask uses an method that is based on the IRAF task ccdmask. The method works best when the input image used to detect flaws in the CCD is the ratio of two flat frames with different counts. That may or may not be available depending on what images are collected.
In the example below, which uses images from Example 2 in the reduction notebooks, the two extreme exposure times are 1 sec and 1.2 sec, but the average counts in the images differ by 10,000. These were twilight flats taken just after sunset.
Even with dome flats where the illumination is supposed to be constant the counts may actually vary. If they do not, use a single flat for identifying bad pixels instead of a ratio.
We begin by creating an image collection and then the information for all of the calibrated, uncombined, flat images.
ex2_path = Path('example2-reduced')
ifc = ccdp.ImageFileCollection(ex2_path)
for long_values in ['history', 'comment']:
try:
ifc.summary.remove_column(long_values)
except KeyError:
# These two columns were not present, so removing them failed.
# Just keep going.
pass
flats = (ifc.summary['imagetyp'] == 'FLAT') & (ifc.summary['combined'] != True)
ifc.summary[flats]
The best we can do here is the ratio of the first and last of the flat images listed above.
first = ifc.summary['file'][flats][0]
last = ifc.summary['file'][flats][-1]
ccd1 = CCDData.read(ex2_path / first)
ccd2 = CCDData.read(ex2_path / last)
ratio = ccd2.divide(ccd1)
The ratio is roughly 0.85:
ratio.data.mean()
Running ccdmask
takes a little time but only needs to be done once, not once
for each image.
%%time
maskr = ccdp.ccdmask(ratio)
The result of ccdmask
is one where there is a defect and zero where the chip
is good, which matches the format of the mask numpy expects.
The input image and derived mask are shown below
fig, axes = plt.subplots(1, 2, figsize=(40, 20))
show_image(ratio, cmap='gray', fig=fig, ax=axes[0], show_colorbar=False)
axes[0].set_title('Ratio of two flats')
show_image(maskr, cmap='gray', fig=fig, ax=axes[1], show_colorbar=False)
axes[1].set_title('Derived mask')
Two comments are in order:
- The "starfish" pattern in the first image is an artifact of the camera shutter. Ideally, a longer exposure time would be used for the flats to avoid this.
- It appear at first glance that there were no pixels masked. The problem is that the masked regions are very small and, at the scale shown, happen to not be visible.
Two defects in this CCD are shown below. The first is a small patch of pixels
that are vastly less sensitive than the rest. The second is a column on the left
edge of the CCD. It turns out this column is not actually exposed to light.
ccdmask
correctly identifies both patches as bad.
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
width = 100
center = (3823, 2446)
plot_row = 0
image_snippet(ccd1, center, width=width, fig=fig, axis=axes[plot_row, 0])
axes[plot_row, 0].set_title('Flat, camera defect')
image_snippet(maskr, center, width=width, fig=fig, axis=axes[plot_row, 1], is_mask=True)
axes[plot_row, 1].set_title('Mask, same center')
center = (0, 2048)
plot_row = 1
image_snippet(ccd1, center, width=width, fig=fig, axis=axes[plot_row, 0], percu=99.9, percl=70)
axes[plot_row, 0].set_title('Flat, bad column')
image_snippet(maskr, center, width=width, fig=fig, axis=axes[plot_row, 1], is_mask=True)
axes[plot_row, 1].set_title('Mask, same center')
Saving the mask
Click here to comment on this section on GitHub (opens in new tab).
The mask can be saved in a FITS file as an image. We will see in the summary notebook on masking how to combine the mask generated here with a mask generated from the dark current and with a cosmic ray mask for each science image.
mask_as_ccd = CCDData(data=maskr.astype('uint8'), unit=u.dimensionless_unscaled)
mask_as_ccd.header['imagetyp'] = 'flat mask'
mask_as_ccd.write(ex2_path / 'mask_from_ccdmask.fits')
The flats we used in Example 1, taken with the Large Format Camera at Palomar,
are dome flats taken with nearly constant illumination. In that case the best we
can do is run ccdmask
on a single flat image. As we will see, this still
allows the identification of several clearly bad areas of the chip.
First, a look at the calibratted, but not combined, flat images.
ex1_path = Path('example1-reduced')
ifc1 = ccdp.ImageFileCollection(ex1_path)
flats = (ifc1.summary['imagetyp'] == 'FLATFIELD') & (ifc1.summary['combined'] != True)
ifc1.summary[flats]
We can double check that a ratio of flats will not be useful by calculating the mean counts in each flat image:
ccs = []
for c in ifc1.ccds(imagetyp='flatfield', filter="g'"):
if 'combined' in c.header:
continue
print(c.data.mean())
ccs.append(c)
The variation in counts is so small that the ratio of two flats will not be useful.
Instead, we run ccdmask
on the first flat. There is nothing special about that
one. The kind of defects that ccdmask
tries to identify are in the CCD sensor
itself and should be the same for all filters.
%%time
ccs1_mask = ccdp.ccdmask(ccs[0])
Displaying the flat we used and the mask side-by-side demonstrates that the defects which are clear in the flat are picked up in the mask.
fig, axes = plt.subplots(1, 2, figsize=(15, 10))
show_image(ccs[0], cmap='gray', fig=fig, ax=axes[0])
axes[0].set_title('Single calibrated flat')
show_image(ccs1_mask, cmap='gray', fig=fig, ax=axes[1], is_mask=False)
axes[1].set_title('Derived mask');
A couple of cutouts are shown below illustrating some of the individual defects identified.
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
width = 300
center = (512, 3976)
plot_row = 0
image_snippet(ccs[0], center, width=width, fig=fig, axis=axes[plot_row, 0])
axes[plot_row, 0].set_title('Flat, partial bad column')
image_snippet(ccs1_mask, center, width=width, fig=fig, axis=axes[plot_row, 1], is_mask=True)
axes[plot_row, 1].set_title('Mask, same center')
center = (420, 3250)
width = 100
plot_row = 1
image_snippet(ccs[0], center, width=width, fig=fig, axis=axes[plot_row, 0])
axes[plot_row, 0].set_title('Flat, bad patch')
image_snippet(ccs1_mask, center, width=width, fig=fig, axis=axes[plot_row, 1], is_mask=True)
axes[plot_row, 1].set_title('Mask, same center')