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.
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
.
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:
PixelDetector
-- describes how the detector behaves: gain, read noise, dark current, bad pixels, efficiency, and size.
BackgroundLight
-- describes the background light per pixel, from the sky, zodiacal light, and stray light.
PSFFactory
-- provides a description of the PSF at a specific row,col pixel position
Collection DetectorObjects
-- a list of astronomical objects that have been propagated to the detector. The position and size are defined in pixel space, and the flux is the number of photons.
meanCosmicRays
-- the mean number of cosmic rays on the detector.
The
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
After all objects are rendered, the bright buffer is convolved (if necessary) and the buffers are added:
Finally, add background light and apply electronics effects.
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
.
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);
}
}
// 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);
// 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;