Source code for gemini_instruments.gsaoi.adclass

import math

from astrodata import astro_data_tag, TagSet, astro_data_descriptor, returns_list
from ..gemini import AstroDataGemini
from .. import gmu
from ..common import build_group_id
from . import lookup

[docs]class AstroDataGsaoi(AstroDataGemini): __keyword_dict = dict(array_section='CCDSEC', camera='DETECTOR', central_wavelength='WAVELENG', detector_name='DETECTOR', ) @staticmethod def _matches_data(source): return source[0].header.get('INSTRUME', '').upper() == 'GSAOI' @astro_data_tag def _tag_instrument(self): return TagSet(['GSAOI']) @astro_data_tag def _tag_dark(self): if self.phu.get('OBSTYPE') == 'DARK': return TagSet(['DARK', 'CAL'], blocks=['IMAGE']) @astro_data_tag def _tag_image(self): tags = ['IMAGE'] if self.phu.get('OBSTYPE') == 'FLAT': tags.extend(['FLAT', 'CAL']) if 'DOMEFLAT' in self.phu.get('OBJECT', '').upper(): tags.extend(['DOMEFLAT', 'FLAT', 'CAL']) elif 'TWILIGHT' in self.phu.get('OBJECT', '').upper(): tags.extend(['TWILIGHT', 'FLAT', 'CAL']) return TagSet(tags) # Kept separate from _tag_image, because some conditions defined # at a higher level conflict with this @astro_data_tag def _type_gcal_lamp(self): obj = self.phu.get('OBJECT', '').upper() if obj == 'DOMEFLAT': return TagSet(['LAMPON']) elif obj == 'DOMEFLAT OFF': return TagSet(['LAMPOFF'])
[docs] @returns_list @astro_data_descriptor def array_name(self): """ Returns a list of the array names of each extension Returns ------- list/str names of the arrays """ try: return self.hdr['ARRAYID'] except KeyError: # Data have been mosaicked, so return the detector name # (as a single-element list if necessary) return self.phu.get('DETECTOR')
[docs] @astro_data_descriptor def central_wavelength(self, asMicrometers=False, asNanometers=False, asAngstroms=False): """ Returns the central wavelength in meters or the specified units Parameters ---------- asMicrometers : bool If True, return the wavelength in microns asNanometers : bool If True, return the wavelength in nanometers asAngstroms : bool If True, return the wavelength in Angstroms Returns ------- float The central wavelength setting """ unit_arg_list = [asMicrometers, asNanometers, asAngstroms] if unit_arg_list.count(True) == 1: # Just one of the unit arguments was set to True. Return the # central wavelength in these units if asMicrometers: output_units = "micrometers" if asNanometers: output_units = "nanometers" if asAngstroms: output_units = "angstroms" else: # Either none of the unit arguments were set to True or more than # one of the unit arguments was set to True. In either case, # return the central wavelength in the default units of meters. output_units = "meters" central_wavelength = self.phu.get('WAVELENG', -1) if central_wavelength < 0.0: return None else: return gmu.convert_units('angstroms', central_wavelength, output_units)
[docs] @returns_list @astro_data_descriptor def gain(self): """ Returns the gain (electrons/ADU) of the extensions Returns ------- list/float gain (e/ADU) """ return self._look_up_arr_property('gain')
[docs] @astro_data_descriptor def group_id(self): """ Returns a string representing a group of data that are compatible with each other. This is used when stacking, for example. Each instrument and mode of observation will have its own rules. Returns ------- str A group ID for compatible data """ # Additional descriptors required for each frame type # Note: dark_id and flat_twilight are not in common use. # Those are therefore place holder with initial guess # # For flat_id, "on" and "off" domeflats are not taken # with the same obsID, so that association cannot be # made. The only other sensible characteristic would # be to require a timeframe check, eg. within X hours. # # The UT date and local date change in the middle of the # night. Can't reliably use that. Thought for a while # using the either the fake UT or equivalently the date # string in the file would work, but found at least one # case where the flat sequence is taken across the 2pm # filename change. # # Because the group_id is a static string, I can't use # if-tricks or or-tricks. The only thing that doesn't # change is the program ID. That's a bit procedural though # but that's the only thing left. # #dark_id = ["exposure_time", "coadds"] flat_id = ["filter_name", "exposure_time", "program_id"] #flat_twilight_id = ["filter_name"] science_id = ["observation_id", "filter_name", "exposure_time"] # Associate rules with data type # Note: add darks and twilight if necessary later. if 'FLAT' in self.tags: id_descriptor_list = flat_id else: id_descriptor_list = science_id # Add in all the common descriptors required id_descriptor_list.extend(["read_mode", "detector_section"]) return build_group_id(self, id_descriptor_list, prettify=('filter_name'))
[docs] @astro_data_descriptor def is_coadds_summed(self): """ Tells whether or not the co-adds have been summed. If not, they have been averaged. GSAOI averages them. Returns ------- bool True if the data has been summed. False if it has been averaged. """ return False
[docs] @astro_data_descriptor def nominal_photometric_zeropoint(self): """ Returns the nominal zeropoints (i.e., the magnitude corresponding to a pixel value of 1) for the extensions in an AD object. Zeropoints in table are for electrons, so subtract 2.5*lg(gain) if the data are in ADU Returns ------- float/list zeropoint values, one per SCI extension """ def _zpt(array, filt, gain, in_adu): zpt = lookup.nominal_zeropoints.get((filt, array)) try: return zpt - (2.5 * math.log10(gain) if in_adu else 0) except TypeError: return None gain = self.gain() filter_name = self.filter_name(pretty=True) array_name = self.array_name() in_adu = self.is_in_adu() if self.is_single: return _zpt(array_name, filter_name, gain, in_adu) else: return [_zpt(a, filter_name, g, in_adu) for a, g in zip(array_name, gain)]
[docs] @astro_data_descriptor def nonlinearity_coeffs(self): """ For each extension, return a tuple (a0,a1,a2) of coefficients such that the linearized counts are a0 + a1*c _ a2*c^2 for raw counts c Returns ------- tuple/list coefficients """ return self._look_up_arr_property('coeffs')
[docs] @astro_data_descriptor def non_linear_level(self): """ Returns the level at which the data become non-linear, in ADU. Returns ------- int/list Value at which the data become non-linear """ # Column 3 gives the fraction of the saturation level at which # the data become non-linear fraction = self._look_up_arr_property('linlimit') sat_level = self.saturation_level() if self.is_single: try: return fraction * sat_level except TypeError: return None else: return [f * s if f and s else None for f, s in zip(fraction, sat_level)]
[docs] @astro_data_descriptor def read_noise(self): """ Returns the read noise of each extension in electrons, as a float or a list of floats Returns ------- float/list read noise in electrons """ # Column 0 has the read noise (for 1 coadd) raw_read_noise = self._look_up_arr_property('readnoise') coadd_factor = math.sqrt(self.coadds()) if self.is_single: try: return round(raw_read_noise / coadd_factor, 2) except TypeError: return None else: return [round(r / coadd_factor, 2) if r else None for r in raw_read_noise]
[docs] @astro_data_descriptor def read_speed_setting(self): """ Returns a string describing the read speed setting, as used in the OT Returns ------- str read speed setting """ # The number of non-destructive reads is the key in the dict return lookup.read_modes.get(self.phu.get('LNRS'), 'Unknown')
[docs] @astro_data_descriptor def saturation_level(self): """ Returns the saturation level in ADU for each extension, as a list or a single value Returns ------- int/list saturation level in ADU """ return self._look_up_arr_property('welldepth')
[docs] @astro_data_descriptor def wcs_ra(self): """ Returns the Right Ascension of the center of the field based on the WCS rather than the RA keyword. This just uses the CRVAL1 keyword. Returns ------- float right ascension in degrees """ # Try the first (only if sliced) extension, then the PHU try: h = self[0].hdr crval = h['CRVAL1'] ctype = h['CTYPE1'] except KeyError: crval = self.phu.get('CRVAL1') ctype = self.phu.get('CTYPE1') return crval if ctype == 'RA---TAN' else None
[docs] @astro_data_descriptor def wcs_dec(self): """ Returns the Declination of the center of the field based on the WCS rather than the DEC keyword. This just uses the CRVAL2 keyword. Returns ------- float declination in degrees """ # Try the first (only if sliced) extension, then the PHU try: h = self[0].hdr crval = h['CRVAL2'] ctype = h['CTYPE2'] except KeyError: crval = self.phu.get('CRVAL2') ctype = self.phu.get('CTYPE2') return crval if ctype == 'DEC--TAN' else None
def _look_up_arr_property(self, attr): """ Helper function to extract information from the array_properties dict Will return a list or a value, depending on the object it's called on Returns ------- list/float the required data """ read_speed = self.read_speed_setting() array_names = self.array_name() if isinstance(array_names, list): return [getattr(lookup.array_properties.get((read_speed, a)), attr, None) for a in array_names] else: return getattr(lookup.array_properties.get((read_speed, array_names)), attr, None)