from pathlib import Path
from astropy.nddata import CCDData
from astropy.io import fits
Working with directories
Click here to comment on this section on GitHub (opens in new tab).
The cell below contains the path to the images. In this notebook we'll use it both to store the fake images we generate and to read images. In normal use, you wouldn't start by writing images there, however.
If the images are in the same directory as the notebook you can omit this, or
set it to an empty string ''
. Having images in the same directory as the
notebook is less complicated, but it's not at all uncommon to need to work with
images in a different directory.
Later, we'll look at how to generate the full path to an image (directory plus
file name) in a way that will work on any platform. One of the approaches to
loading images (using ccdproc.ImageFileCollection
) lets you mostly forget
about this.
data_directory = 'path/to/my/images'
Generate some fake images
Click here to comment on this section on GitHub (opens in new tab).
The cells below generate some fake images to use later in the notebook.
from pathlib import Path
from itertools import cycle
import numpy as np
image_path = Path(data_directory)
image_path.mkdir(parents=True, exist_ok=True)
images_to_generate = {
'BIAS': 5,
'DARK': 10,
'FLAT': 3,
'LIGHT': 10
}
exposure_times = {
'BIAS': [0.0],
'DARK': [5.0, 30.0],
'FLAT': [5.0, 6.1, 7.3],
'LIGHT': [30.0],
}
filters = {
'FLAT': 'V',
'LIGHT': 'V'
}
objects = {
'LIGHT': ['m82', 'xx cyg']
}
image_size = [300, 200]
image_number = 0
for image_type, num in images_to_generate.items():
exposures = cycle(exposure_times[image_type])
try:
filts = cycle(filters[image_type])
except KeyError:
filts = []
try:
objs = cycle(objects[image_type])
except KeyError:
objs = []
for _ in range(num):
img = CCDData(data=np.random.randn(*image_size), unit='adu')
img.meta['IMAGETYP'] = image_type
img.meta['EXPOSURE'] = next(exposures)
if filts:
img.meta['FILTER'] = next(filts)
if objs:
img.meta['OBJECT'] = next(objs)
image_name = str(image_path / f'img-{image_number:04d}.fits')
img.write(image_name)
print(image_name)
image_number += 1
Option 1: Reading a single image with astropy.io.fits
Click here to comment on this section on GitHub (opens in new tab).
This option gives you the most flexibility but is the least adapted to CCD images specifically. What you read in is a list of FITS extensions; you must first select the one you want then access the data or header as desired.
We'll open up the first of the fake images, img-0001.fits
. To combine that
with the directory name we'll use Python 3's pathlib
, which ensures that the
path combination will work on Windows too.
image_name = 'img-0001.fits'
image_path = Path(data_directory) / image_name
hdu_list = fits.open(image_path)
hdu_list.info()
The hdu_list
is a list of FITS Header-Data Units. In this case there is just
one, containing both the image header and data, which can be accessed as shown
below.
hdu = hdu_list[0]
hdu.header
hdu.data
The documentation for io.fits describes more of its capabilities.
Option 2: Use CCDData
to read in a single image
Click here to comment on this section on GitHub (opens in new tab).
Astropy contains a CCDData
object for representing a single image. It's not as
flexible as using astrop.io.fits
directly (for example, it assumes there is
only one FITS extension and that it contains image data) but it sets up several
properties that make the data easier to work with.
We'll read in the same single image we did in the example above,
img-0001.fits
.
ccd = CCDData.read(image_path)
The data and header are accessed similarly to how you access it in an HDU
returned by astropy.io.fits
:
ccd.header
ccd.data
There are a number of features of CCDData
that make it convenient for working
with WCS, slicing, and more. Some of those features will be discussed in more
detail in the notebooks that follow.
Option 3: Working with a directory of images using ImageFileCollection
Click here to comment on this section on GitHub (opens in new tab).
The affiliated package ccdproc provides an easier way
to work with collections of images in a directory: an ImageFileCollection
. The
ImageFileCollection
is initialized with the name of the directory containing
the images.
from ccdproc import ImageFileCollection
im_collection = ImageFileCollection(data_directory)
Note that we didn't need to worry about using pathlib
to combine the directory
and file name, instead we give the collection the name of the directory.
Summary of directory contents
Click here to comment on this section on GitHub (opens in new tab).
The summary
property provides an overview of the files in the directory: it's
an astropy Table
, so you can access columns in the usual way.
im_collection.summary
Filtering and iterating over images
Click here to comment on this section on GitHub (opens in new tab).
The convenient thing about ImageFileCollection
is that it provides easy ways
to filter or loop over files via FITS header keyword values.
For example, looping over just the flat files is one line of code:
for a_flat in im_collection.hdus(imagetyp='FLAT'):
print(a_flat.header['EXPOSURE'])
Instead of iterating over HDUs, as in the example above, you can iterate over
just the headers (with .headers
) or just the data (with .data
). You can use
any FITS keyword from the header as a keyword for selecting the images you want.
In addition, you can return the file name while also iterating.
for a_flat, fname in im_collection.hdus(imagetyp='LIGHT', object='m82', return_fname=True):
print(f'In file {fname} the exposure is:', a_flat.header['EXPOSURE'], 'with standard deviation ', a_flat.data.std())
The documentation for ImageFileCollection
describes more of its capabilities.
ImageFileCollection
can automatically save a copy of each image as you iterate
over them, for example.
for a_flat, fname in im_collection.ccds(bunit='ADU', return_fname=True):
print(a_flat.unit)
a_flat.header