what’s your bias? (bias correction)

In this series, I’ll walk through each of the steps necessary to go from taking data at an (optical) telescope to doing science with that data. This article touches on why astronomical images need to be corrected before use, and goes into detail about the bias correction process. If you’d like to follow along and create your own image of the NGC 663 cluster you can clone the github repository. I’ll be learning something too, because I haven’t studied the NGC 663 cluster in detail. The data I use in this explainer series was taken from the Amherst College Observatory in October 2019, from a 11in Cassegrain telescope and a SBIG CCD, and the sorted raw data is available to download from this website as a tarball. Much of the code I present here is modeled after code handed down to me by Sarah Betti and Kim Ward-Duong, who taught me most of what I know about data reduction (thanks guys!).

Some basics

Astronomical images, when they first descend from the heavens and grace the camera at the bottom of an astronomer’s telescope, don’t look pretty. They pass through the atmosphere, which distorts and smudges them, before entering a telescope aperture. Any dust or other residue inside the telescope can deflect or block incoming photons. The camera detector itself introduces its own problems into the image, which have to be corrected. Additionally, most astronomers aren’t interested in every ounce of data in an image. “Data reduction” is the process by which an astronomer accounts and corrects for these affects to produce data which can answer science questions.

Correcting, combining, and reducing raw observations is easily automated, especially with highly interpreted coding languages like python. Doing so necessitates an understanding of the sources of uncertainty in an image, and the aberrations that are necessary (and feasible) to correct for. In this series we’ll discuss the ways that astronomers correct for these problems in order to produce images which enable them to extract the information needed to answer specific science questions.

For a rigorous treatment of these topics, I’d recommend the textbook “To Measure the Sky: An Introduction to Observational Astronomy” by Frederick R. Chromey. Chapter 9, “Digital images from arrays,” in particular is very helpful if my explanations make no sense. In the case where this textbook is not easily accessible to you (via a library or local bookstore), it would be incredibly irresponsible of you to visit Library Genesis (http://gen.lib.rus.ec/) and search for the title. I wholeheartedly condemn the act of piracy.

The camera and the data

Images from optical telescopes are captured by a Charged Coupled Device (CCD) camera, whose pixels act like a grid of “photon buckets.” When the photon impacts the pixel it is absorbed and transformed into an electron because of the photoelectric effect. The electrons within a pixel induce a voltage across the pixel, which can be measured with a voltmeter.

Incoming photons liberate electrons within a pixel, which can then be measured by a voltmeter. This allows a CCD to “count” the number of photons which encounter the camera. Image credit: Wikipedia user Ponor

Readout refers to the CCD’s process of recording the voltage induced in a given pixel and converting that into a digital number. The voltage across each pixel is converted into units called “counts.” A count represents the number of electrons generated in a pixel. In a perfect world, each photon would produce a single electron, and while this isn’t necessarily true, modern CCDs get pretty close. The count of each pixel is recorded in an array which is saved to a computer. This is how digital cameras like the one in your smartphone operate. These arrays are sometimes called “frames” (because a lot of images are taken in a sequence, like the frames of a movie) or “exposures” because to take the image you open the shutter to expose the CCD to the sky.

For optical images, there are three dominant sources of noise (and therefore error) in our observations. There are three common correction steps we take before analyzing our images. The CCD introduces “bias” when it reads out the image to a computer; we discuss bias correction in this post. The CCD also introduces “dark current”: excess photons which come from the heat generated by the CCD itself, and are therefore not part of the original light from the sky; we discuss dark subtraction in the next post. After correcting the CCD’s noise, we attempt to set every pixel in our images on equal footing, correcting for some of the effects of the optics in the telescope, any dust that has collected on the mirror or camera, and any variations or defects in the sensitivity of the pixels in the CCD; we discuss this “flat fielding” in a future post. After that, we’ll calibrate the brightness of our images using a standard star, align them to a reference coordinate system, and finally analyze the data to answer some questions we have about the cluster NGC 663; more on that later.

Bias correction

Biasing is an electronics term that describes the application of a voltage/current to a circuit or piece of circuit to create more optimal operating conditions. A CCD used for astronomy applies a bias to its pixels in order to avoid negative values. The CCD wants to avoid negative values because an array of negative and positive pixel values takes twice as much space to store on a computer as an array of purely positive values does.

In a perfect world, this would result in every pixel in any image having some offset from zero of one value; we could simply subtract this value from our images to get the “true” pixel values for a given image. Often this exact value must be determined by taking images, as opposed to reading from a manual. A bias image is an attempt to determine this bias voltage. By taking a zero second exposure, letting no light or heat build up on the detector, the given bias frame should have pixel values equal to only the bias voltage applied; you can then subtract this uniform image from a science image in order to remove the bias voltage.

In reality, bias voltage isn’t uniformly applied, and a CCD’s amplifier cannot convert voltages to digital units with perfect accuracy. Each pixel, even if it contains no photons, might readout a positive value. In this way, a bias frame captures information about the uncertainty (or “bias”) of the CCD’s ability to measure photons.

A bias frame is then a way to capture any spurious readout patterns or affects and remove them from science images, in addition to correcting for the applied bias voltage. Bias frames are taken by reading out the CCD when it is empty, that is, when there has been no exposure to outside light. The exposure time of every bias frame is zero.

Bias frames are relatively easy to take, and can be taken at any time during the observing run. The only time constraint on taking these calibration frames is that they take as long to create as your CCD takes to read out all the pixels (usually a few seconds, depending on how big your detector is).

A single bias frame.

Once we’ve taken a sizable number of bias frames (for the Amherst College Observatory, usually at least 11), we take the “median combination” of the bias frames. This median bias frame is called a “master bias,” and it is what we subtract from each of our subsequent images. The median combination step means that the master bias captures the most common bias signal for each pixel, and is therefore more accurate than just taking one bias frame which may have some variation from the average.

To take a median combination of astronomical images, I’ve written a python function:

# import statements
import numpy as np
from astropy.io import fits

# function
def mediancombine(filelist):
    median combine frames
    # gather some information about the images
    n = len(filelist) 
    first_frame_data = fits.getdata(filelist[0])
    imsize_y, imsize_x = first_frame_data.shape
    # construct an empty cube with proper dimensions 
    fits_stack = np.zeros((imsize_y, imsize_x, n))
    # fill cube with images in filelist
    for ii in range(0, n):
        im = fits.getdata(filelist[ii])
        fits_stack[:, :, ii] = im
    # take the median combination of the images along the 
    # correct axis
    med_frame = np.median(fits_stack, axis=2)
    return med_frame
An median “master bias” calibration frame generated from eleven bias frames.

Looking at the average pixel values in this master bias, it appears the bias that is applied to this CCD was about 900 counts. To subtract the master bias from each of our science images, I’ve written the following functions into a python script to automate the task. By running “python bias.py” from the command line, the code is executed.

# import statements
import os
import glob
from astropy.io import fits
from mediancombine import mediancombine

# functions
def bias_subtract(filename, path_to_bias, outpath):
    bias subtract frames
    # gather image, header, and bias image
    targetdata = fits.getdata(filename)
    target_header = fits.getheader(filename)
    biasdata = fits.getdata(path_to_bias)

    # perform subtraction
    b_data = targetdata - biasdata

    # check if files exist in directory other than notebook
    fitsname = filename.split('\\')[-1]
    # save image to disk
    fits.writeto(outpath + "/" + 'b_' + fitsname, b_data,
                 target_header, overwrite=True)

def run_master_bias(fil):
    create master bias
    # check that a folder for the master calibration frames
    # exists or not, if not, create it
    if not os.path.exists(f'{fil}/Masters'):
        print(f'Masters folder created at: {fil}/Masters')

    # store path for final image as string
    masterbiaspath = fil + '/Masters/MasterBias.fit'
    # if the master bias has not been created, create it
    # and write to disk
    if not os.path.exists(f'{fil}/Masters/MasterBias.fit'):
        print('Making Master Bias')
        # create master bias
        bias_fits = glob.glob(fil + '/bias/*.fit')

        median_bias = mediancombine(bias_fits)
        fits.writeto(masterbiaspath, median_bias,
                     header=fits.getheader(bias_fits[0]), overwrite=True)
    # if the master bias already exists, point user to it
        print(f'Master Bias in: {masterbiaspath}')

def bias_sub_images(fil, targs):
    bias correct science targets
    # for each target we have, run the reduction
    for target in targs:
        print('target: ', target)
        # store master bias path as string
        masterbiaspath = fil + '/Masters/MasterBias.fit'

        # get lists of target images
        bias_images = glob.glob(f'{fil}\\{target}\\b_*.fit')
        scidata = glob.glob(fil + '\\' + target + '\\*.fit')
        # if images have not been bias subtracted, subtract them
        if len(bias_images) != len(scidata):
            [os.remove(im) for im in bias_images]
            print('Bias subtracting ')
            for sci_image in scidata:
                # read in data
                filtername = fits.getheader(sci_image)['FILTER'][0]
                # store filepath as string
                sci_outpath = fil + '\\' + target + '\\' + filtername + 'band'
                # if the folder does not exist already, make it
                if not os.path.exists(sci_outpath):
                # run bias subtraction function
                bias_subtract(sci_image, masterbiaspath, sci_outpath)

    return('Bias subtracted target images')

if __name__ == '__main__':
    # Running as a script
    root = os.getcwd()
    fil = root+'\\Data\\20191025'
    targs = ['NGC 663', 'Entered Coordinates']
    bias_sub_images(fil, targs)

From here, our images are ready to be calibrated further.

The next post details “dark current” and the dark subtraction process. Until then, you can star the github repo or follow this blog. Clear skies, and happy astronomizing!

Leave a Reply

Your email address will not be published. Required fields are marked *