Source code for gemini_instruments.gmos.adclass

import math
import re
from datetime import date

from astrodata import astro_data_tag, astro_data_descriptor, returns_list, TagSet
from .pixel_functions import get_bias_level
from . import lookup
from .. import gmu
from ..common import Section
from ..gemini import AstroDataGemini

[docs]class AstroDataGmos(AstroDataGemini): __keyword_dict = dict(array_name = 'AMPNAME', array_section = 'CCDSEC', camera = 'INSTRUME', overscan_section = 'BIASSEC', ) @staticmethod def _matches_data(source): return source[0].header.get('INSTRUME', '').upper() in ('GMOS-N', 'GMOS-S') @astro_data_tag def _tag_instrument(self): # tags = ['GMOS', self.instrument().upper().replace('-', '_')] return TagSet(['GMOS']) @astro_data_tag def _tag_dark(self): if self.phu.get('OBSTYPE') == 'DARK': return TagSet(['DARK'], blocks=['IMAGE', 'SPECT']) @astro_data_tag def _tag_arc(self): if self.phu.get('OBSTYPE') == 'ARC': return TagSet(['ARC', 'CAL']) def _tag_is_bias(self): if self.phu.get('OBSTYPE') == 'BIAS': return True else: return False @astro_data_tag def _tag_bias(self): if self._tag_is_bias(): return TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT']) @astro_data_tag def _tag_flat(self): if self.phu.get('OBSTYPE') == 'FLAT': if self.phu.get('GRATING') == 'MIRROR': f1, f2 = self.phu.get('FILTER1'), self.phu.get('FILTER2') # This kind of filter prevents imaging to be classified as FLAT if any(('Hartmann' in f) for f in (f1, f2)): return return TagSet(['GCALFLAT', 'FLAT', 'CAL']) @astro_data_tag def _tag_twilight(self): if self.phu.get('OBJECT', '').upper() == 'TWILIGHT': # Twilight flats are of OBSTYPE == OBJECT, meaning that the generic # FLAT tag won't be triggered. Add it explicitly return TagSet(['TWILIGHT', 'CAL', 'FLAT']) @astro_data_tag def _tag_domeflat(self): if self.phu.get('OBJECT', '').upper() == 'DOMEFLAT': return TagSet(['DOMEFLAT', 'CAL', 'FLAT']) def _tag_is_spect(self): pairs = ( ('MASKTYP', 0), ('MASKNAME', 'None'), ('GRATING', 'MIRROR') ) matches = (self.phu.get(kw) == value for (kw, value) in pairs) if any(matches): return False return True @astro_data_tag def _tag_ifu(self): #if not self._tag_is_spect(): # return if self._tag_is_bias(): return mapping = { 'IFU-B': 'ONESLIT_BLUE', 'IFU-B-NS': 'ONESLIT_BLUE', 'b': 'ONESLIT_BLUE', 'IFU-R': 'ONESLIT_RED', 'IFU-R-NS': 'ONESLIT_RED', 'r': 'ONESLIT_RED', 'IFU-2': 'TWOSLIT', 'IFU-NS-2': 'TWOSLIT', 's': 'TWOSLIT' } names = set(key for key in mapping.keys() if key.startswith('IFU')) mskt, mskn = self.phu.get('MASKTYP'), self.phu.get('MASKNAME') if mskt == -1 and (mskn in names or re.match('g[ns]ifu_slit[rbs]_mdf', mskn)): if mskn not in names: mskn = re.match('g.ifu_slit(.)_mdf', mskn).groups()[0] return TagSet(['IFU', mapping[mskn]]) @astro_data_tag def _tag_mask(self): spg = self.phu.get if spg('GRATING') == 'MIRROR' and spg('MASKTYP') != 0: return TagSet(['MASK']) @astro_data_tag def _tag_image_or_spect(self): if self.phu.get('GRATING') == 'MIRROR': return TagSet(['IMAGE']) else: return TagSet(['SPECT']) @astro_data_tag def _tag_ls(self): #if not self._tag_is_spect(): # return if self._tag_is_bias(): return if self.phu.get('MASKTYP') == 1 and self.phu.get('MASKNAME', '').endswith('arcsec'): return TagSet(['LS']) @astro_data_tag def _tag_mos(self): #if not self._tag_is_spect(): # return if self._tag_is_bias(): return mskt = self.phu.get('MASKTYP') mskn = self.phu.get('MASKNAME', '') if mskt == 1 and not (mskn.startswith('IFU') or mskn.startswith('focus') or mskn.endswith('arcsec')): return TagSet(['MOS']) @astro_data_tag def _tag_nodandshuffle(self): if 'NODPIX' in self.phu: return TagSet(['NODANDSHUFFLE'])
[docs] @astro_data_descriptor def amp_read_area(self): """ Returns a list of amplifier read areas, one per extension, made by combining the amplifier name and detector section. Or returns a string if called on a single-extension slice. Returns ------- list/str read_area of each extension """ ampname = self.array_name() detsec = self.detector_section(pretty=True) # Combine the amp name(s) and detector section(s) if self.is_single: return "'{}':{}".format(ampname, detsec) if ampname and detsec else None else: return ["'{}':{}".format(a,d) if a and d else None for a,d in zip(ampname, detsec)]
[docs] @astro_data_descriptor def array_name(self): """ Returns a list of the names of the arrays of the extensions, or a string if called on a single-extension slice Returns ------- list/str names of the arrays """ return self.hdr.get('AMPNAME')
[docs] @astro_data_descriptor def central_wavelength(self, asMicrometers=False, asNanometers=False, asAngstroms=False): """ Returns the central wavelength in meters or 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" # Keywords should be the same, but CENTWAVE was only added post-2007 try: central_wavelength = self.phu['CENTWAVE'] except KeyError: central_wavelength = self.phu.get('GRWLEN', -1) if central_wavelength < 0.0: return None else: return gmu.convert_units('nanometers', central_wavelength, output_units)
[docs] @astro_data_descriptor def detector_name(self, pretty=False): """ Returns the name(s) of the detector(s), from the PHU DETID keyword. Calling with pretty=True will provide a single descriptive string. Parameters ---------- pretty : bool If True, return a single descriptive string Returns ------- str detector name """ if pretty: pretty_detname_dict = { "SDSU II CCD": "EEV", "SDSU II e2v DD CCD42-90": "e2vDD", "S10892": "Hamamatsu-S", "S10892-N": "Hamamatsu-N" } return pretty_detname_dict.get(self.phu.get('DETTYPE')) else: return self.phu.get('DETID')
[docs] @astro_data_descriptor def detector_rois_requested(self): """ Returns a list of ROIs, as tuples in a 1-based inclusive (IRAF-like) format (x1, x2, y1, y2), in physical (unbinned) pixels. Returns ------- list of tuples, one per ROI """ roi_list = [] for roi in range(1, 10): x1 = self.phu.get('DETRO{}X'.format(roi)) xs = self.phu.get('DETRO{}XS'.format(roi)) y1 = self.phu.get('DETRO{}Y'.format(roi)) ys = self.phu.get('DETRO{}YS'.format(roi)) if x1 and xs and y1 and ys: xs *= self.detector_x_bin() ys *= self.detector_y_bin() roi_section = Section(x1=x1-1, x2=x1+xs-1, y1=y1-1, y2=y1+ys-1) roi_list.append(roi_section) else: break return roi_list
[docs] @astro_data_descriptor def detector_roi_setting(self): """ Looks at the first ROI and returns a descriptive string describing it These are more or less the options in the OT Returns ------- str Name of the ROI setting used or "Custom" if the ROI doesn't match "Undefined" if there's no ROI in the header """ roi_dict = lookup.gmosRoiSettings rois = self.detector_rois_requested() if rois: roi_setting = 'Custom' for s in roi_dict: roi_tuple = (rois[0].y1, rois[0].y2, rois[0].x1, rois[0].x2) if roi_tuple in roi_dict[s]: roi_setting = s else: roi_setting = 'Undefined' return roi_setting
[docs] @astro_data_descriptor def detector_x_bin(self): """ Returns the detector binning in the x-direction Returns ------- int The detector binning """ def _get_xbin(b): try: return int(b.split()[0]) except (AttributeError, ValueError): return None binning = self.hdr.get('CCDSUM') if self.is_single: return _get_xbin(binning) else: xbin_list = [_get_xbin(b) for b in binning] # Check list is single-valued return xbin_list[0] if xbin_list == xbin_list[::-1] else None
[docs] @astro_data_descriptor def detector_y_bin(self): """ Returns the detector binning in the y-direction Returns ------- int The detector binning """ def _get_ybin(b): try: return int(b.split()[1]) except (AttributeError, ValueError, IndexError): return None binning = self.hdr.get('CCDSUM') if self.is_single: return _get_ybin(binning) else: ybin_list = [_get_ybin(b) for b in binning] # Check list is single-valued return ybin_list[0] if ybin_list == ybin_list[::-1] else None
[docs] @astro_data_descriptor def detector_x_offset(self): """ Returns the offset from the reference position in pixels along the positive x-direction of the detector Returns ------- float The offset in pixels """ try: offset = self.phu.get('POFFSET') / self.pixel_scale() except TypeError: # either is None return None # Flipped for GMOS-N if on bottom port return -offset if (self.phu.get('INPORT')==1 and self.instrument()=='GMOS-N') else offset
[docs] @astro_data_descriptor def detector_y_offset(self): """ Returns the offset from the reference position in pixels along the positive y-direction of the detector Returns ------- float The offset in pixels """ try: offset = self.phu.get('QOFFSET') / self.pixel_scale() except TypeError: # either is None return None # Flipped for GMOS-S if on bottom port return -offset if (self.phu.get('INPORT')==1 and self.instrument()=='GMOS-S') else offset
[docs] @astro_data_descriptor def disperser(self, stripID=False, pretty=False): """ Returns the name of the disperser used for the observation. In GMOS, the disperser is a grating. Parameters ---------- stripID : bool If True, removes the component ID and returns only the name of the disperser. pretty : bool, also removed the trailing '+' If True, Returns ------- str name of the grating """ stripID |= pretty disperser = self.phu.get('GRATING') if stripID: disperser = gmu.removeComponentID(disperser) if pretty: try: disperser = disperser.strip('+') except AttributeError: pass return disperser
[docs] @astro_data_descriptor def dispersion(self, asMicrometers=False, asNanometers=False, asAngstroms=False): """ Returns the dispersion in meters per pixel as a list (one value per extension) or a float if used on a single-extension slice. It is possible to control the units of wavelength using the input arguments. 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 ------- list/float The dispersion(s) """ 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" cd11 = self.hdr.get('CD1_1') try: dispersion = [gmu.convert_units('meters', d, output_units) for d in cd11] except TypeError: dispersion = gmu.convert_units('meters', cd11, output_units) return dispersion
[docs] @returns_list @astro_data_descriptor def dispersion_axis(self): """ Returns the axis along which the light is dispersed. Returns ------- (list of) int (1) Dispersion axis. """ return 1 if 'SPECT' in self.tags else None
[docs] @astro_data_descriptor def exposure_time(self): """ Returns the exposure time in seconds. Returns ------- float Exposure time. """ exp_time = self.phu.get(self._keyword_for('exposure_time'), -1) return None if exp_time < 0 or exp_time > 10000 else exp_time
[docs] @astro_data_descriptor def focal_plane_mask(self, stripID=False, pretty=False): """ Returns the name of the focal plane mask. Parameters ---------- stripID : bool Doesn't actually do anything. pretty : bool Same as for stripID Returns ------- str The name of the focal plane mask """ mask = self.phu.get('MASKNAME') return 'Imaging' if mask == 'None' else mask
[docs] @returns_list @astro_data_descriptor def gain(self): """ Returns the gain (electrons/ADU) for each extension Returns ------- list/float Gains used for the observation """ # If the file has been prepared, we trust the header keywords if 'PREPARED' in self.tags: return self.hdr.get(self._keyword_for('gain')) # Get the correct dict of gain values ut_date = self.ut_date() if ut_date is None: return None # converted to list by decorator if needed if ut_date >= date(2017, 2, 24): gain_dict = lookup.gmosampsGain elif ut_date >= date(2015, 8, 26): gain_dict = lookup.gmosampsGainBefore20170224 elif ut_date >= date(2006, 8, 31): gain_dict = lookup.gmosampsGainBefore20150826 else: gain_dict = lookup.gmosampsGainBefore20060831 read_speed_setting = self.read_speed_setting() gain_setting = self.gain_setting() # This may be a list ampname = self.array_name() # Return appropriate object if self.is_single: return gain_dict.get((read_speed_setting, gain_setting, ampname)) else: return [gain_dict.get((read_speed_setting, gain_setting, a)) for a in ampname]
[docs] @astro_data_descriptor def gain_setting(self): """ Returns the gain settings of the observation. Returns ------- str Gain setting """ def _get_setting(g): if g is None: return None else: return 'low' if g < 3.0 else 'high' # This seems to rely on obtaining the original GAIN header keyword gain_settings = None if 'PREPARED' not in self.tags: # Use the (incorrect) GAIN header keywords to determine the setting gain = self.hdr.get('GAIN') else: # For prepared data, we use the value of the gain_setting keyword try: gain_settings = self.hdr[self._keyword_for('gain_setting')] except KeyError: # This code deals with data that haven't been processed with # gemini_python (no GAINSET keyword), but have a PREPARED tag try: gain = self.hdr['GAINORIG'] # If GAINORIG is 1 in all the extensions, then the original # gain is actually in GAINMULT(!?) if gain == 1 or gain == [1] * len(self): gain = self.hdr['GAINMULT'] except KeyError: # Use the gain() descriptor as a last resort gain = self.gain() # Convert gain to gain_settings if we only got the gain if gain_settings is None: if self.is_single: gain_settings = _get_setting(gain) else: gain_settings = [_get_setting(g) for g in gain] # If multiple extensions, only allow one gain setting to be discrepant if isinstance(gain_settings, list): for item in gain_settings: if gain_settings.count(item) >= len(self) - 1: return item return None else: return gain_settings
[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. GMOS uses the detector binning, amp_read_area, gain_setting, and read_speed_setting. Flats and twilights have the pretty version of the filter name included. Science data have the pretty filter name and observation_id as well. And spectroscopic data have the grating. Got all that? Returns ------- str A group ID for compatible data. """ tags = self.tags # Things needed for all observations unique_id_descriptor_list_all = ['detector_x_bin', 'detector_y_bin', 'read_mode', 'amp_read_area'] if 'SPECT' in tags: unique_id_descriptor_list_all.append('disperser') # List to format descriptor calls using 'pretty=True' parameter call_pretty_version_list = ['filter_name', 'disperser'] # Force this to be a list force_list = ['amp_read_area'] if 'BIAS' in tags: id_descriptor_list = [] elif 'DARK' in tags: id_descriptor_list = ['exposure_time'] elif 'IMAGE' in tags and ('FLAT' in tags or 'TWILIGHT' in tags): id_descriptor_list = ['filter_name'] else: id_descriptor_list = ['observation_id', 'filter_name'] # Add in all of the common descriptors required id_descriptor_list.extend(unique_id_descriptor_list_all) # Form the group_id descriptor_object_string_list = [] for descriptor in id_descriptor_list: kw = {} if descriptor in call_pretty_version_list: kw['pretty'] = True descriptor_object = getattr(self, descriptor)(**kw) # Ensure we get a list, even if only looking at one extension if (descriptor in force_list and not isinstance(descriptor_object, list)): descriptor_object = [descriptor_object] # Convert descriptor to a string and store descriptor_object_string_list.append(str(descriptor_object)) # Create and return the final group_id string return '_'.join(descriptor_object_string_list)
[docs] @astro_data_descriptor def instrument(self, generic=False): """ Returns the name of the instrument making the observation Parameters ---------- generic: boolean If set, don't specify the specific instrument if there are clones (e.g., return "GMOS" rather than "GMOS-N" or "GMOS-S") Returns ------- str instrument name """ return 'GMOS' if generic else self.phu.get('INSTRUME')
[docs] @astro_data_descriptor def nod_count(self): """ Returns a tuple with the number of integrations made in each of the nod-and-shuffle positions Returns ------- tuple number of integrations in the A and B positions """ try: return (int(self.phu['ANODCNT']), int(self.phu['BNODCNT'])) except KeyError: return None
[docs] @astro_data_descriptor def nod_offsets(self): """ Returns a tuple with the offsets from the default telescope position of the A and B nod-and-shuffle positions (in arcseconds) Returns ------- tuple offsets in arcseconds """ # Cope with two possible situations try: ayoff = self.phu['NODAYOFF'] byoff = self.phu['NODBYOFF'] except KeyError: ayoff = 0.0 try: byoff = self.phu['NODYOFF'] except KeyError: return None return (ayoff, byoff)
[docs] @astro_data_descriptor def shuffle_pixels(self): """ Returns the number of rows that the charge has been shuffled, in nod-and-shuffle data Returns ------- int The number of rows by which the charge is shuffled """ return self.phu.get('NODPIX') if 'NODANDSHUFFLE' in self.tags else None
[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(ccd, filt, gain, in_adu): zpt = lookup.nominal_zeropoints.get((ccd, filt)) 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) ccd_name = self.hdr.get('CCDNAME') in_adu = self.is_in_adu() if self.is_single: return _zpt(ccd_name, filter_name, gain, in_adu) else: return [_zpt(c, filter_name, g, in_adu) for c, g in zip(ccd_name, gain)]
[docs] @returns_list @astro_data_descriptor def non_linear_level(self): """ Returns the level at which the data become non-linear, in ADU. For GMOS, this is just the saturation level. Returns ------- int/list Value(s) at which the data become non-linear """ return self.saturation_level()
[docs] @astro_data_descriptor def overscan_section(self, pretty=False): """ Returns the overscan (or bias) section. If pretty is False, a tuple of 0-based coordinates is returned with format (x1, x2, y1, y2). If pretty is True, a keyword value is returned without parsing as a string. In this format, the coordinates are generally 1-based. One tuple or string is return per extension/array. If more than one array, the tuples/strings are return in a list. Otherwise, the section is returned as a tuple or a string. Parameters ---------- pretty : bool If True, return the formatted string found in the header. Returns ------- tuple of integers or list of tuples Position of the overscan section using Python slice values. string or list of strings Position of the overscan section using an IRAF section format (1-based). """ return self._parse_section('BIASSEC', pretty)
[docs] @astro_data_descriptor def pixel_scale(self): """ Returns the image scale in arcsec per pixel, accounting for binning Returns ------- float pixel scale """ pixscale_dict = lookup.gmosPixelScales # Pixel scale dict is keyed by instrument ('GMOS-N' or 'GMOS-S') # and detector type pixscale_key = (self.instrument(), self.phu.get('DETTYPE')) try: raw_pixel_scale = pixscale_dict[pixscale_key] except KeyError: return None return raw_pixel_scale * self.detector_y_bin()
[docs] @astro_data_descriptor def read_mode(self): """ Returns a string describing the readout mode, which sets the gain and readout speed Returns ------- str read mode used """ # Get the right mapping (detector-dependent) det_key = 'Hamamatsu' if \ self.detector_name(pretty=True).startswith('Hamamatsu') \ else 'default' mode_dict = lookup.read_mode_map.get(det_key) mode_key = (self.gain_setting(), self.read_speed_setting()) return mode_dict.get(mode_key)
[docs] @returns_list @astro_data_descriptor def read_noise(self): """ Returns the read noise in electrons. Returns a list if multiple extensions, or a float on a single-extension slice. Returns ------- float/list read noise """ if 'PREPARED' in self.tags: return self.hdr.get(self._keyword_for('read_noise')) else: # Get the correct dict of read noise values ut_date = self.ut_date() if ut_date is None: return None # converted to list by decorator if needed if ut_date > date(2017, 2, 24): rn_dict = lookup.gmosampsRdnoise elif ut_date >= date(2015, 8, 26): rn_dict = lookup.gmosampsRdnoiseBefore20170224 elif ut_date >= date(2006, 8, 31): rn_dict = lookup.gmosampsRdnoiseBefore20150826 else: rn_dict = lookup.gmosampsRdnoiseBefore20060831 read_speed_setting = self.read_speed_setting() gain_setting = self.gain_setting() # This may be a list ampname = self.array_name() # Return appropriate object # Return appropriate object if self.is_single: return rn_dict.get((read_speed_setting, gain_setting, ampname)) else: return [rn_dict.get((read_speed_setting, gain_setting, a)) for a in ampname]
[docs] @astro_data_descriptor def read_speed_setting(self): """ Returns the setting for the readout speed (slow or fast) Returns ------- str the setting for the readout speed """ try: ampinteg = self.phu['AMPINTEG'] except KeyError: return None detector = self.detector_name(pretty=True) if detector.startswith('Hamamatsu'): return 'slow' if ampinteg > 8000 else 'fast' else: return'slow' if ampinteg > 2000 else 'fast'
[docs] @astro_data_descriptor def saturation_level(self): """ Returns the saturation level (in ADU) Returns ------- int/list saturation level """ def _well_depth(detector, amp, bin, gain, in_adu): try: return lookup.gmosThresholds[detector][amp] * bin / ( 1 if in_adu else gain) except KeyError: return None # We need to know whether the data have been bias-subtracted # First, look for keywords in PHU bias_subtracted = \ self.phu.get(self._keyword_for('bias_image')) is not None or \ self.phu.get(self._keyword_for('dark_image')) is not None # OVERSCAN keyword also means data have been bias-subtracted overscan_levels = self.hdr.get('OVERSCAN') detname = self.detector_name(pretty=True) detector = self.phu['DETECTOR'] # the only way to distinguish GMOS-S Ham pre/post video board work. xbin = self.detector_x_bin() ybin = self.detector_y_bin() bin_factor = xbin * ybin ampname = self.array_name() gain = self.gain() in_adu = self.is_in_adu() # Get estimated bias levels from LUT bias_levels = get_bias_level(self, estimate=True) if bias_levels is None: bias_levels = 0.0 if self.is_single else [0.0] * len(self) else: if not self.is_single: bias_levels = [b if b is not None else 0 for b in bias_levels] adc_limit = 65535 # Get the limit that could be processed without hitting the ADC limit # Subtracted bias level if data are bias-subtracted, and # multiply by gain if data are in electron/electrons if self.is_single: bias_subtracted |= overscan_levels is not None processed_limit = (adc_limit - (bias_levels if bias_subtracted else 0)) * (1 if in_adu else gain) else: bias_subtracted = [bias_subtracted or o is not None for o in overscan_levels] processed_limit = [(adc_limit - (blev if bsub else 0)) * (1 if in_adu else g) for blev, bsub, g in zip(bias_levels, bias_subtracted, gain)] # For old EEV data, or heavily-binned data, we're ADC-limited if detname == 'EEV' or bin_factor > 2: return processed_limit else: # Otherwise, we're limited by the electron well depths if self.is_single: saturation = _well_depth(detector, ampname, bin_factor, gain, in_adu) if saturation is None: saturation = processed_limit else: saturation += bias_levels if not bias_subtracted else 0 if saturation > processed_limit: saturation = processed_limit else: well_limit = [_well_depth(detector, a, bin_factor, g, in_adu) for a, g in zip(ampname, gain)] saturation = [None if w is None else min(w + blev if not bsub else 0, p) for w, p, blev, bsub in zip(well_limit, processed_limit, bias_levels, bias_subtracted)] return saturation
[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, IndexError): 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, IndexError): crval = self.phu.get('CRVAL2') ctype = self.phu.get('CTYPE2') return crval if ctype == 'DEC--TAN' else None