Image

class snappl.image.Image(full_filepath=None, filepath=None, base_path=None, base_dir=None, path=None, no_base_path=False, id=None, provenance_id=None, format=-1, is_superclass=False, **kwargs)[source]

Bases: PathedObject

Encapsulates a single 2d image.

Properties inclue the following. Some of these properties may not be defined for some subclasses of Image.

DATA PROPERTIES

  • data : 2d numpy array; the data of this image

  • noise : 2d numpy array; a 1σ noise image (if defined)

  • flags : 2d numpy array of ints; a pixel flags image (if defined)

For all implementations, the properties data, noise, and flags are lazy-loaded. That is, they start as None, but when you access them, an internal buffer gets loaded with that data. (Depending on the subclass, accessing any one of these properties may load others into memory. For instance, using RomanDatamodelImage, the first time you access either .data or .noise, both get loaded into memory.) This means it can be very easy for lots of memory to get used without your realizing it. There are a couple of solutions. The first, is to call Image.free() when you’re sure you don’t need the data any more, or if you know you want to get rid of it for a while and re-read it from disk later. The second is just not to access the data, noise, and flags properties, instead use Image.get_data(), and manage the data object lifetime yourself.

Image arrays are indexed by [y, x], with 0 being the center of the lower-left pixel. That is, .data[0, 0] gives you the lower-left pixel. .data[0, 1] gives you the pixel one to the right of the lower-left pixel. .data[1, 0] gives you the pixel one above the lower-left pixel. .data[height-1, width-1] gives you the upper-right pixel. THESE POSITIONS ARE OFFSET BY 1 FROM WHAT YOU SEE IN DS9, so be careful. They are also offset by 1 from a WCS in a FITS image header. However, they ARE the coordinates you expect when using an astropy WCS class the right way, and, more importantly, are the coordiantes you expect when using a snappl WCS class.

When considering positions on the image, as opposed to indexes in the array, the .0 position is the center of the pixel. This is an unfortunate convention (talk to Rob if you want to know why it’s unfortunate), but it’s what astronomers have been using for decades, so we’re stuck with it. For a long discussion about indexing images, see the docstring on psf.py::PSF.get_stamp.

UNITS OF THE DATA: we define the DAV “data array value” as the units of these arrays. The literal definition of this is “the units of whatever it is you get when you access the .data property of a snappl.Image object”. However, there is a further definition for the Image class, and that is that DAV are NOT a surface brightness unit, but something (in the ideal case) proportional to the number of photons that came from the angular area on the sky subtended by the pixel during the exposure. This is explored further in the zeropoint docstring below. This means that a properly-implemented Image subclass may need to do a conversion to the actual data stored on disk in the file it refers to before giving you the .data and .noise properties (which the RomanDatamodelImage subclass does). We use DAV because did not want to use DN or ADU or anything that had ever been used before; empirically, that caused discussion about DN vs. DN/sec, which got in the way of just trying to talk about the data array.

IMMEDIATE IMAGE METADATA PROPERTIES

  • width : the width (horizontal size as viewed on ds9) of the image in pixels

  • height : the height (vertial size as viewed on ds9) of the image in pixels

  • image_shape : tuple of ints, giving (height, width)

LESS IMMEDIATE IMAGE METADATA PROPERTIES

coord_center : tuple of (ra, dec) [I THINK] : center of the image as calcualted from the WCS

HEADER DATA PROPERTIES

These are things that you would traditionally find in the “header” of an image.

  • observation_id : str; a unique identifier of the exposure associated with the image

  • sca : int (str?); the SCA of this image

  • ra: float; the nominal RA at the center of the image in decimal degrees, usu. from the header

  • (so may be slightly different from what you get using coord_center)

  • dec: float; the nominal RA at the center of the image in decimal degrees, usu. from the header

    (so may be slightly different from what you get using coord_center)

  • ra_corner_00: float; decimal degrees, ra of pixel (0, 0)

  • ra_corner_10: float; decimal degrees, ra of pixel (width-1, 0)

  • ra_corner_01: float; decimal degrees, ra of pixel (0, height)

  • ra_corner_11: float; decimal degrees, ra of pixel (width-1, height-1)

  • dec_corner_00: float; decimal degrees, dec of pixel (0, 0)

  • dec_corner_10: float; decimal degrees, dec of pixel (width-1, 0)

  • dec_corner_01: float; decimal degrees, dec of pixel (0, height)

  • dec_corner_11: float; decimal degrees, dec of pixel (width-1, height-1)

  • band : str; filter

  • mjd : float; mjd of the start of the image

  • position_angle : float; position angle in degrees north of east (CHECK THIS)

  • exptimefloat; exposure time in seconds. (But be careful;

    because of the ramp readout of Roman images, not all pixels will have used the full exptime. Don’t try to think about it too hard, and just use the .data and .noise arrays.)

  • sky_level : float; an estimate of the sky level (in DAV) if known, None otherwise

PATH PROPERTIES

If possible, avoid using all “path” properties, and instead use the other properties to get access to image data (i.e., .data, .noise). Trust snappl to read the files. If you don’t, the behavior may not be what you expect. Note that “noisepath” and “flagspath” are not defined for all Image subclasses, and will only be defined sometimes for some subclasses (depending on how data is stored).

  • filepathpathlib.Path ; path relative to the base path of the image file. This may just

    have the image data itself, or it may be a base filepath, or it may have everything, depending on the subclass. If you can avoid using this property, do so. Use .data, etc, instead.

  • filename : string ; just the name part of filepath (so if filepath is Path(“/foo/bar”), name is “bar”)

  • full_filepath : pathlib.Path ; absolute path to file on system. (Same as base_path / filepath.)

  • base_path : base path for images; usually will be Config value system.paths.images

  • base_dir : synonym for base_path

  • pathpathlib.Path; absolute path to the image on disk, sort of, in a complicatd way.

    DO NOT USE. HERE FOR BACKWARDS COMPATIBILITY ONLY

  • name : str; synonym for filename. DO NOT USE. HERE FOR BACKWARDS COMPATIBILITY ONLY.

POSITION ANGLE

Position angle is defined in degrees north of east. Implications of this include:

  • A PA of 0° means the negative X-axis is East and the Y-axis is North
    • i.e., increasing RA is left (lower X), increasing Dec is up (higher Y)

  • A PA of 45° means that the X-axis is Northwest and the Y-axis is Norhteast
    • i.e. increasing RA is down and to the left, increasing Dec is up and to the left

  • A PA of 90° means the Y-axis is East and the X-axis is North
    • i.e. incrasing RA is up (higher Y), increasing Dec is right (higher X)

(TODO: check this to make sure I have’t made a sign error in whether it’s the image axes or the sky axes that is rotated! I think that happened when they made OU24, using two different conventions in two different places.)

The position angle property is calculated from the Image’s WCS, unless a subclass overrides this behavior. (As of this writing, the only one that does is FITSImageStdHeaders, which will use a position angle header keyword if one was given on object construction, otherwise fall back to the implementation in Image). Of course, it can’t calculate a position angle if it doesn’t have a wcs.

Instantiate an image. You probably don’t want to do that.

This is an abstract base class that has limited functionality. You probably want to instantiate a subclass if you’re creating a new image.

If you’re trying to pull an image out of the database, then probably what you really want is to use the Image.get_image or Image.find_images class methods.

If you’re working with non-database images and are trying to get a pre-existing image, then probably what you really want to do is call the get_image() method of an ImageCollection object.

Only instantiate an image directly if you’re creating something yourself that you know you want to write in a specific format, or if you’re trying to read a file that’s not covered by an ImageCollection. When you do this, talk to the photometry working group and find out if this image should be covered by an ImageCollection. (Note that there is an ImageCollectionManualFITS collection for reading loose FITS files.)

Parameters:
  • filepath (str or Path, default None) – Path of the image relative to the base path for images, unless less no_base_path is True, in which case this is the full absolute path to the image. For datbase images, you do not want to create a path yourself, but leave it at None and let the class create the filepath. See PathedObject.

  • full_filepath (str or Path, default None) – The full path to the image. If you’re using an Image subclass to deal with an image that’s not in the database, you probably want to set this to the absolute path of the image, and you probably want to set no_base_path to True, but you might also set base_path yourself and leave no_base_path at False.

  • base_path (str or Path, default None) – Always leave this at None for images associated with database, and the default will be used. Otherwise, the absolute path of the image is base_path / filepath (which should be exactly the same as full_filepath). Must be None if no_base_path is True.

  • base_dir (str or Path, default None) – Synonym for base_path

  • no_base_path (bool, default False) – For images associated with the database, leave this at False, and make filepath relative to the base path (which may be system dependent). For images that aren’t associated with the database, you can make this True and set filepath to be just the path to the image.

  • id (UUID or str that can be converted to UUID, default None) – Database ID of the image. This is only relevant if the image is in the l2image table of the Roman SNPIT internal database (but is required in that case).

  • provenance_id (UUID or str that can be converted to UUID, default NOne) – The id of the provenance of the image. Only relevant if the image is in the l2image table of the Roman SNPIT internal database (but is required in that case).

  • width (int, default None) – The width and height of the image in pixels if known.

  • height (int, default None) – The width and height of the image in pixels if known.

  • format (int, default -1) – Index into the table Image._format_def at the bottom of this file.

  • is_superclass – Used internally, should ONLY ever be set in the super().__init__(…) lines in subclass constructors. All subclasses should set this to True when calling super().__init__(…). If you aren’t writing an Image subclass, ignore this.

Attributes Summary

coord_center

[RA, DEC] (both floats) in degrees at the center of the image

data

image data in DAV.

data_array_list

flags

An integer 2d numpy array of pixel masks / flags TBD

height

height (y-size, first index in numpy arrays) of the image

id

The database image uuid in the l2image table.

image_shape

(ny, nx) pixel size of image.

internal_properties

name

noise

1σ pixel noise.

path

provenance_id

The database provenance uuid of the image in the l2image table.

width

the width (x-size, second index in numpy arrays) of the image

zeropoint

Deprecated; use get_zeropoint.

Methods Summary

ap_phot(coords[, ap_r, method, subpixels, ...])

Do aperture photometry on the image at the specified coordinates.

bulk_save_to_db(images[, dbclient])

Don't use this if you don't really know what you're doing.

find_images([provenance, provenance_tag, ...])

Search the database for images.

fraction_masked()

Fraction of pixels that are masked.

free()

Try to free memory.

get_cutout(ra, dec, xsize[, ysize, mode, ...])

Make a cutout of the image at the given RA and DEC.

get_data([which, always_reload, cache])

Read the data from disk and return one or more 2d numpy arrays of data.

get_image(image_id[, dbclient])

Get an Image from the database based on its image id.

get_ra_dec_cutout(ra, dec, xsize[, ysize, ...])

Creates a new snappl image object that is a cutout of the original image, at a location in pixel-space.

get_wcs([wcsclass])

Get image WCS.

get_zeropoint([x, y, sed])

Return the Image zeropoint for AB magnitudes.

includes_radec(ra, dec)

Check to see if (ra, dec) is included within the image borders.

psf_phot(init_params, psf[, forced_phot, ...])

Do psf photometry.

save([which, path, imagepath, noisepath, ...])

Save the image to its path(s).

save_data([which, path, imagepath, ...])

Same as save; here for backwards compatibility.

save_to_db([dbclient])

Write this image record to the database.

Attributes Documentation

coord_center

[RA, DEC] (both floats) in degrees at the center of the image

data

image data in DAV. Maybe not the same as what’s in the file! See Image class docstring.

Type:

2d numpy array

data_array_list = ['all', 'data', 'noise', 'flags']
flags

An integer 2d numpy array of pixel masks / flags TBD

TODO : think about what we mean by this. Right now it’s subclass-dependent. But, for usage, we need a way of making this more general. Issue #45.

height

height (y-size, first index in numpy arrays) of the image

Type:

Int

id

The database image uuid in the l2image table.

image_shape

(ny, nx) pixel size of image.

Type:

Tuple

internal_properties = {'band': <class 'str'>, 'dec': <class 'float'>, 'dec_corner_00': <class 'float'>, 'dec_corner_01': <class 'float'>, 'dec_corner_10': <class 'float'>, 'dec_corner_11': <class 'float'>, 'exptime': <class 'float'>, 'height': <class 'int'>, 'mjd': <class 'float'>, 'observation_id': <class 'str'>, 'position_angle': <class 'float'>, 'ra': <class 'float'>, 'ra_corner_00': <class 'float'>, 'ra_corner_01': <class 'float'>, 'ra_corner_10': <class 'float'>, 'ra_corner_11': <class 'float'>, 'sca': <class 'int'>, 'sky_level': <class 'float'>, 'width': <class 'int'>}
name
noise

1σ pixel noise. Maybe not the same as what’s in the file! See Image class docstring.

Type:

2d numpy array

path
provenance_id

The database provenance uuid of the image in the l2image table.

width

the width (x-size, second index in numpy arrays) of the image

Type:

Int

zeropoint

Deprecated; use get_zeropoint.

Methods Documentation

ap_phot(coords, ap_r=9, method='subpixel', subpixels=5, bgsize=511, **kwargs)[source]

Do aperture photometry on the image at the specified coordinates.

Does background subtraction using photutils.background.Background2D with box size bgsize.

Parameters:
  • coords (astropy.table.Table) – Must have (at least) columns ‘x’ and ‘y’ representing 0-origin pixel coordinates. (CHECK THIS)

  • ap_r (float, default 9) – Aperture radius in pixels

  • method (str, default 'subpixel') – Passed to the “method” parmeter of photutils.photometry.aperture_photometry

  • subpixels (int, default 5) – Number of subpixels to use for the ‘subpixel’ method.

  • bgsize (int, default 511) – Box size for photutils Background2D background subtraction. Set to <=0 to not do background subtraction.

  • **kwargs (further arguments are passed directly to photutils.photometry.aperture_photometry)

Returns:

results – Results of photutils.aperture.aperture_photometry

Return type:

astropy.table.Table

classmethod bulk_save_to_db(images, dbclient=None)[source]

Don’t use this if you don’t really know what you’re doing.

classmethod find_images(provenance=None, provenance_tag=None, process=None, dbclient=None, **kwargs)[source]

Search the database for images.

Parameters:
  • provenance (Provenance or UUID, default None) – Either provenance, or both of provenacne_tag and process, are required. provenacne is the provenance of images to search.

  • provenance_tag (string, default None) – The provenance tag to search. Required if provenance is None.

  • process (string, deafault None) – The process, used with provenance_tag, to find the provenance. Required if provenacne_tag is not None.

  • dbclient (SNPITDBClient, default None) – The connection to the database. If None, a new connection will be created based on what’s it the config.

  • filepath (pathlib.Path or str, default None) – Path of the image (relative to the base path for all images) of the image to search for. Usually if you feed it this, you don’t want to feed it nay other parameters.

  • mjd_min (float, default None) – Only return images at this mjd or later

  • mjd_max (float, default None) – Only return images at this mjd or earlier.

  • ra (float, default None) – Only return images that contain this ra

  • dec (float, default None) – Only return images that containe this dec

  • ra_min (float, default None) – Only return images whose nominal central RA/dec are greater/lesser than the specified limits.

  • ra_max (float, default None) – Only return images whose nominal central RA/dec are greater/lesser than the specified limits.

  • dec_min (float, default None) – Only return images whose nominal central RA/dec are greater/lesser than the specified limits.

  • dec_max (float, default None) – Only return images whose nominal central RA/dec are greater/lesser than the specified limits.

  • band (str, default None) – Only include images from this band

  • exptime_min (float, default None) – Only include images with at least this exptime in seconds.

  • exptime_max (float, default None) – Only include images with at most this exptime in seconds.

  • sca (int) – Only include images from this sca.

  • order_by (str or list, default None) – By default, the returned images are not sorted in any particular way. Put a keyword here to sort by that value (or by those values). Options include ‘id’, ‘provenance_id’, ‘observation_id’, ‘sca’, ‘ra’, ‘dec’, ‘filepath’, ‘width’, ‘height’, ‘mjd’, ‘exptime’. Not all of these are necessarily useful, and some of them may be null for many objects in the database.

  • limit (int, default None) – Only return this many objects at most.

  • offset (int, default None) – Useful with limit and order_by ; offset the returned value by this many entries. You can make repeated calls to find_objects to get subsets of objects by passing the same order_by and limit, but different offsets each time, to slowly build up a list.

Returns:

imagelist – Really it will be list of objects of a subclass of snappl.image.Image, but you shouldn’t need to know that.

Return type:

list of snappl.image.Image

fraction_masked()[source]

Fraction of pixels that are masked.

free()[source]

Try to free memory.

get_cutout(ra, dec, xsize, ysize=None, mode='strict', fill_value=nan)[source]

Make a cutout of the image at the given RA and DEC.

Parameters:
  • x (int) – x pixel coordinate of the center of the cutout.

  • y (int) – y pixel coordinate of the center of the cutout.

  • xsize (int) – Width of the cutout in pixels.

  • ysize (int) – Height of the cutout in pixels. If None, set to xsize.

  • mode (str, default 'strict') – “strict” does not allow for partial overlap between the cutout and the original image, “partial” will fill in non-overlapping pixels with fill_value. This is identical to the mode parameter of astropy.nddata.Cutout2D.

  • fill_value (float, default np.nan) – Fill value for pixels that are outside the original image when mode=’partial’. This is identical to the fill_value parameter of astropy.nddata.Cutout2D.

Returns:

cutout – A new snappl image object that is a cutout of the original image.

Return type:

snappl.image.Image

get_data(which='all', always_reload=False, cache=False)[source]

Read the data from disk and return one or more 2d numpy arrays of data.

These will return the same things you’d get if you access the .data, .noise, and .flags properties of the object. See the Image docstring for the defintion of the units of the .data and .noise arrays.

Parameters:
  • which (str) –

    What to read:

    ’data’ : just the image data ‘noise’ : just the noise data ‘flags’ : just the flags data ‘all’ : data, noise, and flags

  • always_reload (bool, default False) – Whether this is supported depends on the subclass. If this is false, then get_data() has the option of returning the values of self.data, self.noise, and/or self.flags instead of always loading the data. If this is True, then get_data() will ignore the self._data et al. properties.

  • cache (bool, default False) – Normally, get_data() just reads the data and does not do any internal caching. If this is True, and the subclass supports it, then the object will cache the loaded data so that future calls with always_reload will not need to reread the data, nor will accessing the data, noise, and flags properties. (You often, but not always, want to set this to True!).

The data read not stored in the class, so when the caller goes out of scope, the data will be freed (unless the caller saved it somewhere. This does mean it’s read from disk every time.

Return type:

list (length 1 or 3 ) of 2d numpy arrays

classmethod get_image(image_id, dbclient=None)[source]

Get an Image from the database based on its image id.

Parmameters

image_idUUID or str that can be converted to UUID

The ID of the image to get.

dbclientSNPITDBClient, default None

The connection to the database. If None, a new connection will be created based on what’s it the config.

get_ra_dec_cutout(ra, dec, xsize, ysize=None, mode='strict', fill_value=nan)[source]

Creates a new snappl image object that is a cutout of the original image, at a location in pixel-space.

Parameters:
  • ra (float) – RA coordinate of the center of the cutout, in degrees.

  • dec (float) – DEC coordinate of the center of the cutout, in degrees.

  • xsize (int) – Width of the cutout in pixels.

  • ysize (int) – Height of the cutout in pixels. If None, set to xsize.

  • mode (str, default 'strict') – “strict” does not allow for partial overlap between the cutout and the original image, “partial” will fill in non-overlapping pixels with fill_value. This is identical to the mode parameter of astropy.nddata.Cutout2D.

  • fill_value (float, default np.nan) – Fill value for pixels that are outside the original image when mode=’partial’. This is identical to the fill_value parameter of astropy.nddata.Cutout2D.

Returns:

cutout – A new snappl image object that is a cutout of the original image.

Return type:

snappl.image.Image

get_wcs(wcsclass=None)[source]

Get image WCS. Will be an object of type BaseWCS (from wcs.py) (really likely a subclass).

Parameters:

wcsclass (str or None) – By default, the subclass of BaseWCS you get back will be defined by the Image subclass of the object you call this on. If you want a specific subclass of BaseWCS, you can put the name of that class here. It may not always work; not all types of images are able to return all types of wcses.

Return type:

object of a subclass of snappl.wcs.BaseWCS

get_zeropoint(x=None, y=None, sed=None)[source]

Return the Image zeropoint for AB magnitudes.

By definition, the zeropoint returned by this image is a “infinite aperture” zeropoint, or one that may be used with a snappl.psf.PSF whose normalization is done right, i.e., if the clip size were infinte, the get_clip() method of the PSF object would return an infinitely-sized numpy 2d array whose sum was 1. (This is also the definition that STPSF uses when returning PSF/PRFs.) See below for much more discussion.

x, y: integers (or, I guess, floats); optional.

Pixel position on the image. Ideally, given our definition of zeropoint, these aren’t used, because the units of the .data array for a properly flatfielded and illumination-corrected image makes the the image zeropoint constant across the image. The parameters are here to hedge our bets in case a future subclass needs it. To be safe, always pass in the x, y of the position on the image where you need the zeropoint. If you don’t pass anything, and if it matters, a properly-implemented Image subclass will assume the center of the image.

sedDEFINITION STILL INCOMING; optional.

DON’T USE THIS RIGHT NOW. The interface may well change. It’s here as a placeholder to remind us we need it, and also for the docstring below.

The SED of the object for which you want a zeropoint. Exactly how we specify SEDs is not yet known, but hopefully it will be a subclass of something we define in snappl/sed.py. If not given, different subclasses will make different (maybe implicit!) assumptions. It’s possible that the subclass will not be able to take an arbitrary SED. We hope to use snappl.sed for this, but we’re still thinking it through. For now, this parameter is ignored, and you’ll get something that’s for some SED that may be not only subclass dependent, but dependent on execution details (like, for instance, some kind of weighted average of the real SEDs of the stars used to determine the image zeropoint)

zp: float

Can be used in:

m_AB = -2.5 log10(DAV) + zp

for an object with psf-fit or aperture-corrected DAV, if that object has an SED consistent with the sed parameter you passed or that is assumed by the subclass.

So that we are very clear what we mean by zeropoint as returned by the the get_zeropoint() method of a snappl.image.Image or a snappl.image.Image subclass, this is the definition.

First, imagine that you have an Image (i.e., an object of the class defined in snappl/image.py). That image’s data property is a two-dimensional array of floats. Define “DAV” (for “data array value”) as the units of that two dimensional array. To highlight this:

THE DAV IS THE UNIT OF THE NUMBERS WE GET IN THE DATA ARRAY

(This is also what we define in the docstring of the Image class itself.)

Whatever that actually is. Importantly, this definition is agnostic as to whether the data array represents something like “counts” or “counts per second”. However, it still does have opinions about the meaning of the numbers; read on.

Second, imagine that we have an astronomical source (a star, to make it concrete), and we have an image of that star taken by the telescope. (Let’s assume that our thought-experiment stars are not at all variable, so it doesn’t matter if we’re talking about the number of photons that entered the aperture during the time of the exposure, or per second.) For our zeropoint definition, we are going to assume that the number of DAVs in the Image.data array is proportional to the number of photons that entered the telescope’s aperture. [ASIDE 1: this implicitly assumes that something like bias subtraction has already been done, so there isn’t a systematic offset from pure electronic effects.] [ASIDE 2: this defintion means that DAV is NOT a surface-brightness unit! A properly implemented Image subclass is promising to do a conversion when you access the .data and .noise arrays to make sure you aren’t getting something in surface brightness units; see RomanDatamodel Image for example.] In reality, diffraction, quantum efficiency, and electronic effects will mean that some of the light energy that entered the telescope aperture will miss the detector or otherwise not be reflected in the read-out data array, but for now, let’s assume that that is negligible. Also, for definitional purposes, assume that there are absolutely no astronomical sources contributing to the light of hitting the detector than the star we’re currently pointing at.

Third, the star’s SED matters. F_ν(ν), or “flux density”, comes in dimensionality of enery/time/area/frequency. It is defined so that:

dE = A F_ν() dt 

is the amount of light energy coming from the star at frequency ν within dν that entered a telescope aperture of area A in time dt. Right now, we’re going to assume that the star has a flat spectrum, i.e., F_ν(ν) is constant for all ν. (We will relax this later; see COLOR TERMS below.)

Fourth, when we divide the image into pixels, we want the response of every pixel in the data array to be exacly the same; by “response”, we mean the conversion from number of photons entering the telescope in the sky area subtended by the pixel to DAV of the pixel. (See CORRECTING FOR PIXEL RESPONSE below.) (Also see PIXEL AREA ISSUE below.)

Fifth, let’s assume that all backgrounds (i.e., light from anything other than the one star we’re looking at) has been subtracted from the image.

Under all these assumptions, we can define the flat-spectrum zeropoint zp (which may not be exactly what get_zeropoint returns!) to be:

m = -2.5 * log10( DAVs ) + zp

where DAVs is the sum of the whole data array, and m is an AB magnitude. An AB magnitude is defined by:

m_AB = -2.5 log10( f_ν / (erg s⁻¹ Hz⁻¹ cm⁻¹) ) - 48.60

(at least if Wikipedia can be trusted). This means that a source with a flux density 3631×10⁻²³ erg s⁻¹ Hz⁻¹ cm⁻¹=3631 Jy has m_AB=0. (Closer to 3.63078054770099×10³ Jy assuming 48.60 is a definition (not a measurement with uncertainty), but 4 sig figs is plenty for a docstring.)

CORRECTING FOR PIXEL RESPONSE

For our definition to work, it means that we’re assuming some preprocessing has been done to the image by the time we receive it. Neglecting all issues of pixel area, that means pixel-to-pixel gain variables have been corrected by flatfielding, so the same zeropoint applies to every pixel on the image. It also assumes that if there is any vignetting (e.g., if the “effective telescope aperture” is different for different pixels), an illumination correction has taken all of that out.

PIXEL AREA ISSUE

When we say “pixel area” in this context, we are NOT talking about the physical area of the pixel on the array, but rather than angular area subtended on the sky by a pixel. (Yes, if we’re going to be precise, the existence of diffraction (at the very least) means that there isn’t a hard-edged area on the sky that corresponds exactly to what a given pixel absorbs, but that’s one of the big reasons we talk about PSFs for space-based imaging (on the ground, the atmosphere is usually way more significant). It is still meaningful, by putting in the right kind of delta function or whatever in place of the actual diffraction (and/or atmospheric blurring) function, to map the physical area of a pixel on the array through optics to an angular area on the sky.) This pixel area can come in units like steradian or arcsec².

In the Roman Space Telescope, the pixel area subtended on the sky can vary by ~±2% over a single SCA. The L2 maps provided by the Roman SOC have array values in units of surface brightness, i.e., something like DN/sec/steradian. However, we have defined DAV to be more like DN/sec (though, again, we are explicitly agnostic as to whether DAV is a rate or not).

What this means is that at least for L2 Roman images, the Image.data array will do a pixel-area correction before returning the DAV values; see the docstring on the Image class and on the Image.data property.

As a result of all , pixel area is not an issue for the definition of the zeropoint.

However, that also means that this zeropoint is what you’d use for point-source photometry. It is not the zeropoint you’d use to identify isophots in a galaxy. (Also, Image.data isn’t formally the right thing to use to identify isophots in a galaxy, unless the pixel area really is constant across the array!)

Note that when Image.data corrects the data to give DAV as something proportional to photon counts, not surface brightness, it fixes just purgely optical/geometric effects. For electronic effects, espeically ones that depend on how full the well is, futher corrections that cannot be encapsulated (at least currently) by the Image.get_zeropoint() method will be needed. (Thushara, save us!)

ACTUAL PHOTOMETRY

Importantly, the zeropoint we’ve defined here DOES NOT take into account any aperture size, nor does it take into account any particular realization of a PSF. It is a property of the image, not of the method used to extract photometry. That means to use this zeropoint:

  • Aperture photometry values must be properly “aperture corrected” before the DAVs are fed into the zeropoint formula. Ideally, when things aren’t too complicated, this correction is just a single factor that multiplies the number of DAVs in the aperture to give an effective “infinite aperture” number of DAVs. This factor will, of course, be different for apertures of different sizes (and shapes), and will also in principle be different at different positions on a detector array. (For small apertures, it’s also very difficult to do right.) For real images, it’s very difficult to determine this by looking at stars on images; you find yourself stuck between needing a very big aperture to capture, within your precision, “all” the flux, and not wanting your aperture to be too big so that you can find enough isolated stars. If you have a very good estimate of the PSF/PRF, you can determine an aperture correction by integrating that.

  • PSF (or PRF) photometry must use PSFs (or PRFs) that are properly normalized to fit the defintion here. “Properly normalized” here means that if you had an infinitely-sized image-scale array of the PSF (really PRF), its sum would be 1; in practice, because you can’t get infinitely-sized data arrays, the sum of the array you get will be less than 1, though for a big enough stamp size it might be very close. The PSFs (really PRFs) returned by snappl.psf.PSF.get_stamp() (and other methods) are supposed to be normalized this way. (Also, as I understand it, the PSFS you get from STPSF are also normalized this way.)

    IT IS POSSIBLE that some further calibration post-processing of photometry after the zeropoint is applied may be entirely convolved with the definition of the PSF. At the moment, snappl’s class structure does not support this, but we will adapt if necessary. However, we should ONLY adapt if it really is necessary! If it’s just a matter of normalizing your PSFs differently, then just normalize them differently to fit our definitions!

FILTERS AND COLOR TERMS

In reality, we never measure something proportional to F_ν(ν) directly. (Spectroscopy gets a lot closer to this than photometry does.) Rather, we’re always measuring some integral of F_ν(ν). There are two things we have to consider.

First, detectors and filters (and the whole telescope system, for that matter) have a different response at different frequencies. Filters, in particular, only transmit light within a finite range of ν, though real detectors are also not sensitive to all frequencies. We will call the system response D(ν), which we will define “the number of DAVs that we get in our data array per frequency bin for light of frequency ν for a source with f(ν)=3631 Jy”, i.e., if we’re looking at that hypotetical f(ν)=3631 Jy star:

DAVs = ∫ D(ν) dν

This means that D(ν) has units of s (or, more clearly, Hz⁻¹) (or, maybe, if you don’t think of DAVs as dimensionless, units of DAV/Hz).

Second, astronomical sources do not have a flat F_ν(ν), as we assumed in our discussion above and in the definiton of the thing we called zp. The actual light source is going to have some SED S(ν) (in units of Energy/Time/Flux Binwidth/Area).

The total number of DAVs detected, therefore, is:

DAVs = ∫ S(ν) D(ν) / (3631Jy) dν

(Presumably D(ν) goes to zero outside some finite range of ν so we don’t have to think about infinite numbers.)

Given this, the flat-spectrum zeropoint (which is what we defiend as zp above) is defined as:

zp = 2.5 log10( ∫ D(ν) dν )

(To see this: consider S(ν) = 3631 Jy for all ν, which is the definition of a m_AB=0 source. In this case:

0  = -2.5 log10(DAVs) + zp
   = -2.5 log10( ∫ (3631Jy) D(ν) / (3631Jy) dν ) + zp
   = -2.5 log10( ∫ D(ν) dν ) + zp
zp = 2.5 log10( ∫ D(ν) dν )

)

A flat-spectrum source with flux density S₀ at all ν has AB magnitude:

m_AB = -2.5 log10( S₀ / 3631Jy ) = -2.5 log10( S₀/Jy ) + 8.900

(Which is where “8.900 is the AB zeropoint” comes from. You will sometimes see people using a zeropoint of 31.4; this is just the zeropoint where the flux density is in nJy rather than Jy, as 2.5log10(10⁹)=22.5.)

The number of DAVs from such a source would be:

DAVs = ∫ S₀ D(ν) / (3631Jy) dν = S₀ / 3631Jy * ∫ D(ν) dν

or:

DAVs / ( ∫ D(ν) dν ) = S₀ / 3631Jy

Taking logs of both sides:

-2.5 log10( DAVs ) + 2.5 log10( ∫ D(ν) dν ) = -2.5 log10( S₀/Jy ) + 2.5 log10( 3631 )
-2.5 log10( DAVs ) + zp = -2.5 log10( S₀ ) + 8.900 = m_AB

Where it gets painful is when S(ν) is not constant with ν. In this case, the magnitude you will calculate from the flat-spectrum zeropoint would be:

m_calc = -2.5 log10( DAVs ) + zp
       = -2.5 log10( ∫ S(ν) D(ν) / (3631Jy) dν ) + 2.5 log10( ∫ D(ν) dν )

However, for a source that doesn’t have a flat S(ν), the true AB magnitude is ill-defined, because it’s different for every ν! So, for a given filter, we have to define a fiducial frequency ν₀ (which corresponds to a fiducial wavelength λ₀ by the usual ν₀=hc/λ₀). We could then define the “true” apparent magnitude of the object with SED S(ν) as:

m = -2.5 log10( S(ν₀) / 3631 Jy )

I think all the Roman filters have a defined fiducial wavelength, so we should just use that (really, hc/that) for ν₀, but we may need to document this somewhere.

We then have a SED correction:

cor_sed ≡ m - m_calc
        = -2.5 log10( S(ν₀) / 3631 Jy ) + 2.5 log10( ∫ S(ν) D(ν) / (3631Jy) dν ) - 2.5 log10( ∫ D(ν) dν )
        = 2.5 log10( ∫ S(ν) D(ν) / S(ν₀) dν ) - 2.5 log10( ∫ D(ν) dν )

cor_sed = 2.5 log10( ∫ S(ν) D(ν) / S(ν₀) dν ) - zp

The magnitude of an object is then:

m = -2.5 log10( DAVs ) + zp + cor_sed

(Do not become confused by the fact that zp is in cor_sed; we’re not subtracting out the zeropoint from the final magnitude formula, because it’s added back, sorta, inside the integral in cor_sed, we just can’t separate it out to another obvious +zp because it’s inside the integral, and while I’ve known named-chair professors of physics (but not astronomy) to claim that we were all doing cosmology wrong and making it too complicated because he freely factored variable things out of integrals, you aren’t really supposed to do that.)

Notice that you don’t need to know the absolute S(ν) to calculate cor_sed, only S(ν)/S(ν₀). This is why we say the “shape” of the SED. The thing passed to the sed parameter of get_zeropoint() is really a SED shape (though a cautiously implemented subclass will not assume that the user is passing in a properly normalized sed).

What get_zeropoint() returns is:

zp + cor_sed

for the sed specified by the sed parameter (with an implicitly assumed ν₀), or for some default sed if you don’t specify one. IMPORTANT, don’t assume this is a flat spectrum, because in practice that may be difficult or impossible to determine. Each subclass may assume a different cor_sed (at least for now).

includes_radec(ra, dec)[source]

Check to see if (ra, dec) is included within the image borders.

Parameters:
  • ra (float) – The coordinate in decimal degrees to check.

  • dec (float) – The coordinate in decimal degrees to check.

Return type:

True if (ra, dec) is within the image borders, False otherwise.

psf_phot(init_params, psf, forced_phot=True, fit_shape=(5, 5), bginner=15, bgouter=25, return_resid_image=False)[source]

Do psf photometry.

Does local background subtraction.

Parameters:
  • init_params (something) – passed to the init_params of a call to a photutils.psf.PSFPHotometry object. IMPORTANT : photutils will accept all kinds of crazy stuff to find the x and y positions of the fit. For this function, you MUST use either (x_init, y_init) or (x, y). (But not both!)

  • psf (snappl.psf.PSF) – The PSF profile to fit to the image.

  • forced_phot (bool, default True) – If True, then the x and y positions are fixed. If False, then they will be fit along with the flux.

  • fit_shape (tuple of (int, int), default (5, 5)) – Shape of the stamp around the positions in which to do the fit.

  • bginner (float, default 15) – Radius of inner boundry of annulus in which to measure background.

  • bouter (float, default 25) – Radius of outer boundry of annulus in which to measure background.

  • return_resid_image (bool, default False) – If True, returns photutils.psf.PSFPhotometry.make_residual_image along with the photometry results.

Return type:

TODO

save(which='all', path=None, imagepath=None, noisepath=None, flagspath=None, overwrite=False)[source]

Save the image to its path(s).

May have side-effects on the internal data structure (e.g., FITS subclasses modify the internally stored header).

Parameters:
  • which (str, default "all") – One of ‘data’, ‘noise’, ‘flags’, or ‘all’

  • imagepath (str, default None) – Full Path to write the image to. If not specified, will use self.full_filepath. Does NOT update any of the path properties of the image. You can leave this at None, and the path that the Image figured out when it was constructed will be used. Usually, that’s what you should do.

  • path (str, default None) – A synonym for imagepath. Do not use. Here for backwards compatibility.

  • noisepath (str, default None) – Path to write the noise image to, if the noise image is stored as a separate image. (It isn’t always; some subclasses have it as a separate part of the data structure that also has the image.) If None, use an internally stored noisepath. If that is not set, and noisepath is None, and this isn’t a subclass that combines all the data planes into one file, then any noise data array will not be written. Usually, you don’t want to have to specify this.

  • flagspath (str, default None) – Path to write the flags image to, similar to noisepath.

  • overwrite (bool, default False) – Clobber existing images?

Not implemented for all subclasses.

save_data(which='all', path=None, imagepath=None, noisepath=None, flagspath=None, overwrite=False)[source]

Same as save; here for backwards compatibility. Use save.

save_to_db(dbclient=None)[source]

Write this image record to the database.

USE THIS WITH CARE. All fields must be properly set. In particular, the filepath and provenance_id must both be right for the database. Don’t use this if you don’t really know what you’re doing.

This does not actually write any files; it just writes a database row. Make sure files are where they need to be before calling this.