simulates astronomical images.

Overview

This package is designed to accurately and efficiently simulate pixel-level simulations. Effects of imperfect pixel detectors, such as read noise and defects, are implemented through interfaces. Initially, the package provides realatively simple implementations of these effects, which can easily be extended by the user, or by continuing development of this package.

The package design is optimized for execution speed. The goal is to render 10 DetectorObjects per second on a reasonable machine, such as a PIII @ 1 GHz. One optimization is allowing public access to the pixel values directly. It is suggested that calling programs use the get and set methods to access pixel values, but if the code keeps the conventions in mind they can achieve some performance gain by direct access.

Conventions

The class PixeledImage manages information about one pixeled image. It can optionally point to a PixeledMask which stores a bit mask corresponding to each pixel. A Header to stores FITS header information. All pixel values are stored as a Java double. Support from gov.fnal.eag.repository package can write and read these pixel values and masks as FITS images, with appropriate header information.

Pixels are indexed as a two-dimensional array of double values, with indeces pix[row][col], where row and col are each defined as a Java int. The position within the CCD is defined in pixel space to begin with (0.0, 0.0) at the corner of the first pixel. Thus, the centroid position of an object with flux only in pixel [0][0] is double rowPixelPosition=0.5 and double columnPixelPosition=0.5.

What The Calling Program Provides

The class PixeledImageFactory produces pixel level simulations of astronomical images, using the rest of the classes in this package and their supporting classes. Its fully-specified constructor takes the following inputs:

What the factory does

The PixeledImageFactory.produce() method orchestrates the production of images. It begins by creating three PixeledImages: faingImage brightImage shapeletImage. It iterates over the input list of objects to render, using the method render(DetectorObject detectorObject) to render each object. Depending on the object, it is rendered in the brightImage, faintImage (for DetectorObjectProfile), or shapeletImage (for DetectorObjectShapelet). A significant time optimization is achieved by rendering faint objects one photon at a time, so pushing down the luminosity function does not impact performance. Simple objects are specified with analytic profiles, so random pixel positions can be rapidly generated for them from this profile, combined with the ellipticity and position angle, and the corresponding pixel incremented in the faintImage. For bright objects, we calculate the distance away from the central position that includes all pixels with more than photonsPerPixelThreshold. (Choosing this treshold well below the signal to noise per pixel due to sky noise and read noise unnecessarily slows performance.) For all pixels within the distance, the flux in the brightImage is incremented by the flux from the SimpleObject integrated numerically. This "faint object" technique does not apply to shapelet objects, so they are always generated from the integrated flux in each pixel, added to the shapeletImage.

Poisson noise is applied differently in these three techniques. For faint objects, the actual number of photons thrown is drawn from a Poisson distribution defined by the mean number of photons from the object. Whether to increment the pixel is decided by comparing a random uniform number with the effective efficiency of the pixel. For bright and shapelet objects, the mean number of photons is rendered for each object in each pixel. Poisson fluctions are applied to each pixel after iterating through the objects.

The psf is applied in different ways. The pixel position for each photon of a faint object is perturbed by a PSF. Bright objects are convolved with a PSF as they are rendered. Shapelet coefficients are multiplied by the shapelet coefficients of a PSF before the shapelet object is rendered.

The method planAndProduce() scans the list of objects, creates the necessary PixeledImage buffers, decides whether it is more efficient to convolve each bright object or the entire bright image at the end, and then iterates over the list, rendering each object (taking into account efficiency as a function of pixel location) as either a bright, faint, or shapelet object:

while (it.hasNext()) {
	Object[] oa = (Object[]) it.next();
	PSF psf = (PSF) oa[3];
	if (((String)oa[0]).equals("Simple")) {
		DetectorObjectProfile detectorObjectProfile = (DetectorObjectProfile) oa[1];
		Rectangle limits = (Rectangle) oa[2];
		boolean brightOrFaint = ((Boolean)oa[4]).booleanValue();
		if ( !doPoisson || brightOrFaint) {
			renderBright(detectorObjectSimple, limits, psf);
			status = STATUS_BRIGHT;
		} else {
			renderFaint(detectorObjectSimple, psf);
			status = STATUS_FAINT;
		}
	} else {
		DetectorObjectShapelet detectorObjectShapelet = (DetectorObjectShapelet) oa[1];
		renderShapelet(detectorObjectShapelet, psf);
	}
}	


After all objects are rendered, the bright buffer is convolved (if necessary) and the buffers are added:

// Convolve the bright image, if necessary
if (!brightConvolveEachObject) {
	bright = psfFactory.convolve(bright, backgroundLight);
}
		
// Add the images, but don't let nulls get you down
if (bright == null) {
	if (shapelet == null) {
		if (faint == null) {
			bright = new PixeledImage(pd.rowCount, pd.columnCount);					
		} else {
			bright = faint;
			faint = null;
		}
	} else {
		bright = shapelet;
		shapelet = null;
	}
}
if (shapelet != null) bright.add(shapelet);
if (faint != null) bright.add(faint);

Finally, add background light and apply electronics effects.

// Background light and electronics
for (int row=0; row < bright.getRowCount(); row++) {
	for (int col=0; col < bright.getColumnCount(); col++) {
		double sky = 
		  pd.efficiencyTransform(backgroundLight.getLightInPixel(row, col), row, col);
		if (doPoisson) {
			sky = simRandomPoisson.next(sky);
		}
		bright.pix[row][col] = 
		  pd.electronicsTransform(bright.pix[row][col] + sky, row, col);
	}
}
return bright;