def cards_gain_rdnoise(instrument="STX16803", gain=None, rdnoise=None): ''' Returns Card list of gain and rdnoise Parameters ---------- instrument : str, optional The instrument code. Currently one of ["STX16803", "FLI Kepler"]. gain, rdnoise : float, optional The gain and read noise if you want to specify. Must be in the unit of electrons per ADU and electrons, respectively. ''' cs = [] if gain is None: gainstr = f"GAIN from the intrument {instrument}." gain = GAIN_EPADU[instrument] else: gainstr = f"GAIN provided by the user as {gain}." if rdnoise is None: rdnoisestr = f"RDNOISE from the intrument {instrument}." rdnoise = RDNOISE_E[instrument] else: rdnoisestr = f"RDNOISE rovided by the user as {rdnoise}." cs = [ Card("GAIN", gain, "[e-/ADU] The electron gain factor."), Card("RDNOISE", rdnoise, "[e-] The (Gaussian) read noise."), Card("COMMENT", gainstr), Card("COMMENT", rdnoisestr) ] return cs
def _cf_add_stellar_info(self, hdul): h = hdul[0].header h.append(Card('COMMENT', '======================')) h.append(Card('COMMENT', ' Stellar information ')) h.append(Card('COMMENT', '======================')) keys = 'ticid tessmag ra_obj dec_obj teff logg radius'.upper().split() for k in keys: h.append(Card(k, self._h0[k], self._h0.comments[k]), bottom=True)
def header(self): header = self._header # This inplace update is OK; it's not bad to overwrite WCS in this # header if self.wcs is not None: header.update(self.wcs.to_header()) header['BUNIT'] = self.unit.to_string(format='fits') header.insert(2, Card(keyword='NAXIS', value=self.ndim)) for ind, sh in enumerate(self.shape[::-1]): header.insert( 3 + ind, Card(keyword='NAXIS{0:1d}'.format(ind + 1), value=sh)) return header
def _cf_add_summary_statistics(self, hdul): super()._cf_add_summary_statistics(hdul) h = hdul[0].header keys = "cdpp0_5 cdpp1_0 cdpp2_0 crowdsap flfrcsap pdcvar".upper( ).split() for k in keys: h.append(Card(k, self._h1[k], self._h1.comments[k]), bottom=True)
def make_hotpix_mask(obsset, sigma_clip1=100, sigma_clip2=10, medfilter_size=None): # caldb = helper.get_caldb() # master_obsid = obsset.obsids[0] flat_off_hdu = obsset.load_item("flat_off")[0] flat_off = flat_off_hdu.data import igrins.libs.badpixel as bp hotpix_mask = bp.badpixel_mask(flat_off, sigma_clip1=sigma_clip1, sigma_clip2=sigma_clip2, medfilter_size=medfilter_size) bg_std = flat_off[~hotpix_mask].std() flat_off_cards = [Card("BG_STD", bg_std, "IGR: stddev of combined flat")] # caldb = helper.get_caldb() obsset.store_image(item_type="hotpix_mask", data=hotpix_mask) # save fits with updated header obsset.store_image(item_type="flat_off", data=flat_off, header=flat_off_hdu.header, card_list=flat_off_cards)
def _create_fits(self): hdul = HDUList(PrimaryHDU()) h = hdul[0].header h.append(Card('name', self.name)) self._cf_pre_hook(hdul) self._cf_add_setup_info(hdul) self._cf_post_setup_hook(hdul) self._cf_add_summary_statistics(hdul) self._cf_add_pipeline_steps(hdul) self._cf_post_hook(hdul) return hdul
def header(self): header = self._header # This inplace update is OK; it's not bad to overwrite WCS in this # header if self.wcs is not None: header.update(self.wcs.to_header()) header['BUNIT'] = self.unit.to_string(format='fits') header.insert(2, Card(keyword='NAXIS', value=self.ndim)) for ind, sh in enumerate(self.shape[::-1]): header.insert( 3 + ind, Card(keyword='NAXIS{0:1d}'.format(ind + 1), value=sh)) # Preserve the spectrum's spectral units if self._spectral_unit != u.Unit(header['CUNIT1']): spectral_scale = spectral_axis.wcs_unit_scale(self._spectral_unit) header['CDELT1'] *= spectral_scale header['CRVAL1'] *= spectral_scale header['CUNIT1'] = self.unit.to_string(format='FITS') return header
def header(self): header = self._nowcs_header wcsheader = self.wcs.to_header() if self.wcs is not None else {} # When preserving metadata, copy over keywords before doing the WCS # keyword copying, since those have specific formatting requirements # and will overwrite these in many cases (e.g., BMAJ) for key in self.meta: if key.upper() not in wcsheader: if isinstance(key, str) and len(key) <= 8: try: header[key.upper()] = str(self.meta[key]) except ValueError as ex: # need a silenced-by-default warning here? # log.warn("Skipped key {0} because {1}".format(key, ex)) pass elif isinstance(key, str) and len(key) > 8: header['COMMENT'] = "{0}={1}".format(key, self.meta[key]) # Preserve non-WCS information from previous header iteration header.update(wcsheader) if self.unit == u.one and 'BUNIT' in self._meta: # preserve the BUNIT even though it's not technically valid # (Jy/Beam) header['BUNIT'] = self._meta['BUNIT'] else: header['BUNIT'] = self.unit.to_string(format='FITS') if 'beam' in self._meta: header = self._meta['beam'].attach_to_header(header) with warnings.catch_warnings(): warnings.simplefilter("ignore") header.insert(2, Card(keyword='NAXIS', value=self.ndim)) for ind, sh in enumerate(self.shape[::-1]): header.insert( 3 + ind, Card(keyword='NAXIS{0:1d}'.format(ind + 1), value=sh)) return header
def add_to_fits(self, hdul: HDUList): if self.bls is not None: h = hdul[0].header h.append(Card('COMMENT', '======================')) h.append(Card('COMMENT', ' BLS results ')) h.append(Card('COMMENT', '======================')) h.append(Card('bls_snr', self.snr, 'BLS depth signal to noise ratio'), bottom=True) h.append(Card('period', self.period, 'Orbital period [d]'), bottom=True) h.append(Card('epoch', self.zero_epoch, 'Zero epoch [BJD]'), bottom=True) h.append(Card('duration', self.duration, 'Transit duration [d]'), bottom=True) h.append(Card('depth', self.depth, 'Transit depth'), bottom=True)
def get_combined_image(data_list, destripe=True): # destripe=True): from igrins.libs.stsci_helper import stsci_median flat_off = stsci_median(data_list) flat_off_cards = [] if destripe: from igrins.libs.destriper import destriper flat_off = destriper.get_destriped(flat_off) flat_off_cards.append(Card("HISTORY", "IGR: image destriped.")) return flat_off, flat_off_cards
def _cf_add_summary_statistics(self, hdul: HDUList): h = hdul[0].header h.append(Card('COMMENT', '======================')) h.append(Card('COMMENT', ' Summary statistics ')) h.append(Card('COMMENT', '======================')) h.append(Card('fstd', self.flux.std(), "Flux standard deviation"), bottom=True) h.append(Card('fmadstd', mad_std(self.flux), "Flux MAD standard deviation"), bottom=True) ps = [0.1, 1, 5, 95, 99, 99.9] pvs = percentile(self.flux, ps) pks = [f"fpc{int(10 * p):03d}" for p in ps] for p, pk, pv in zip(ps, pks, pvs): h.append(Card(pk, pv, f"{p:4.1f} normalized flux percentile"), bottom=True)
def add_to_fits(self, hdul: HDUList): if self.ls is not None: h = hdul[0].header h.append(Card('COMMENT', '======================')) h.append(Card('COMMENT', ' Lomb-Scargle results ')) h.append(Card('COMMENT', '======================')) h.append(Card('lsper', self.period, 'Lomb-Scargle period [d]'), bottom=True) h.append(Card('lspow', self.power, 'Lomb-Scargle power'), bottom=True) h.append(Card('lsfap', self.fap, 'Lomb-Scargle false alarm probability'), bottom=True)
def add_to_fits(self, hdul: HDUList, *nargs, **kwargs): if self.result is not None: h = hdul[0].header h.append(Card('COMMENT', '======================')) h.append(Card('COMMENT', ' Celerite periodicity ')) h.append(Card('COMMENT', '======================')) h.append(Card('GPPLS', self.parameters[0], 'GP SHOT term log S0'), bottom=True) h.append(Card('GPPLQ', self.parameters[1], 'GP SHOT term log Q'), bottom=True) h.append(Card('GPPLO', log(2 * pi / self.period), 'GP SHOT term log omega'), bottom=True)
def get_header_regex(fits_file, header_regex, ext=None): '''This gets all header keys and their vals matching the specified regex string. ''' # auto-check if the image is compressed, and get the correct extension if ext is None: compressed_ext = compressed_fits_ext(fits_file) if compressed_ext is not None: ext = compressed_ext[0] else: ext = 0 hdulist = pyfits.open(fits_file) header = hdulist[ext].header hdulist.close() hdrstr = header.tostring(padding=False, sep='\n', endcard=False) hdrstr = hdrstr.split('\n') hdrstr = [x.strip() for x in hdrstr] # we can handle either a compiled re.Pattern object (useful for parallel # drivers) or a string that we'll compile ourselves if isinstance(header_regex, re.Pattern): match_regex = header_regex elif isinstance(header_regex, str): match_regex = re.compile(r'%s' % header_regex) matches = {} # go through each header string for h in hdrstr: matching = match_regex.search(h) if matching is not None: kv = Card.fromstring(h) matches[kv.keyword] = kv.value return matches
def organize_tripol( self, rename_by=["COUNTER", "FILTER", "OBJECT", "EXPOS", "RET-ANG1"], mkdir_by=["FILTER", "OBJECT"], delimiter='_', archive_dir=None, verbose=False): ''' Rename FITS files after updating theur headers. Parameters ---------- fpath : path-like The path to the target FITS file. rename_by : list of str The keywords in header to be used for the renaming of FITS files. Each keyword values are connected by ``delimiter``. mkdir_by : list of str, optional The keys which will be used to make subdirectories to classify files. If given, subdirectories will be made with the header value of the keys. delimiter : str, optional The delimiter for the renaming. archive_dir : path-like or None, optional Where to move the original FITS file. If ``None``, the original file will remain there. Deleting original FITS is dangerous so it is only supported to move the files. You may delete files manually if needed. ''' def _guess_hwpangle(hdr, fpath, imagetyp): try: # Best if OBJECT is [name_hwpangle] format. hwpangle = float(hdr[KEYMAP["OBJECT"]].split('_')[-1]) if hwpangle > 180: hwpangle = hwpangle / 10 except ValueError: # If not so, set RET-ANG1 = 0.0 for non-object images if imagetyp != "object": return 0.0 # Otherwise, get input. else: hwpangle = input( f"{fpath.name}: HWP angle not found. Enter it (0, 22.5, 45, 67.5): " ) return float(hwpangle) _valid_hwpangs = [0, 0.0, 22.5, 30, 30.0, 45, 45.0, 60, 60.0, 67.5] newpaths = [] objpaths = [] uselessdir = self.rawdir / "useless" mkdir(uselessdir) for fpath in self.rawpaths: # If it is TL image (e.g., ``g.fits``), delete it try: counter = fpath.name.split('_')[1][:4] except IndexError: print(f"{fpath.name} is not a regular TRIPOL FITS file. " + "Maybe a TL image.") fpath.rename(uselessdir / fpath.name) continue hdr = fits.getheader(fpath) try: obj = hdr[KEYMAP["OBJECT"]].lower() except KeyError: print(f"{fpath} has no OBJECT! Skipping") continue cards = [] imagetyp = None # is_dummay = False # is_object = False # is_flat = False # Do not rename useless flat/test images but move to useless directory. if (obj[:4].lower() == 'flat'): imagetyp = "flat" flatfor = obj[-1] if flatfor == hdr[KEYMAP["FILTER"]]: hdr[KEYMAP["OBJECT"]] = "flat" cards.append( Card("FLATFOR", obj[-1], "The band for which the flat is taken")) else: imagetyp = "useless" elif obj.lower() in ["bias", "dark", "test"]: imagetyp = obj.lower() else: imagetyp = "object" # If useless, finish the loop here: if imagetyp == "useless": fpath.rename(uselessdir / fpath.name) continue # Add gain and rdnoise: filt = hdr[KEYMAP["FILTER"]] grdcards = cards_gain_rdnoise(filter_str=filt) [cards.append(c) for c in grdcards] # Add counter if there is none: if "COUNTER" not in hdr: cards.append(Card("COUNTER", counter, "Image counter")) # Add unit if there is none: if "BUNIT" not in hdr: cards.append(Card("BUNIT", "ADU", "Pixel value unit")) # Calculate airmass by looking at the first 4 chars of OBJECT if imagetyp in ["flat", "object"]: # FYI: flat MAY require airmass just for check (twilight/night) try: am, full = airmass_hdr(hdr, ra_key="RA", dec_key="DEC", ut_key=KEYMAP["DATE-OBS"], exptime_key=KEYMAP["EXPTIME"], lon_key="LONGITUD", lat_key="LATITUDE", height_key="HEIGHT", equinox="J2000", frame='icrs', full=True) amcards = cards_airmass(am, full) [cards.append(c) for c in amcards] except ValueError: if verbose: print( f"{fpath} failed in airmass calculation: ValueError" ) print(am, full) except KeyError: if verbose: print( f"{fpath} failed in airmass calculation: KeyError") # Deal with RET-ANG1 if ((imagetyp in ["object", "flat"]) and (not isinstance(hdr["RET-ANG1"], Undefined))): hwpangle_orig = hdr["RET-ANG1"] # Correctly tune the RET-ANG1 to float # (otherwise, it maybe understood as int...) if hwpangle_orig in _valid_hwpangs: hdr["RET-ANG1"] = float(hdr["RET-ANG1"]) # Sometimes it has, e.g., RET-ANG1 = "TIMEOUT" or 45.11, # although TRIPOL computer tries not to make these exceptions. else: while True: hwpangle = float( input( f"{fpath.name}: HWP angle is now {hwpangle_orig}. Enter correct value (0, 22.5, 45, 67.5): " )) if hwpangle not in _valid_hwpangs: accept_novalid = input( "Your input is not a usual value. Still use? (y/n/?)" ) if str(accept_novalid) == 'y': break elif str(accept_novalid) == '?': print("Normally valid values:", _valid_hwpangs) elif str(accept_novalid) != 'n': continue else: break hdr["RET-ANG1"] = float(hwpangle) elif isinstance(hdr["RET-ANG1"], Undefined): hdr["RET-ANG1"] = None # else: # # In worst case (prior to 2019), we sometimes had to put hwp angle # # in the OBJECT after ``_``. # hwpangle = _guess_hwpangle(hdr, fpath, imagetyp) # cards.append(Card("RET-ANG1", float(hwpangle), # "The half-wave plate angle.")) add_hdr = fits.Header(cards) newpath = fitsrenamer(fpath, header=hdr, rename_by=rename_by, delimiter=delimiter, add_header=add_hdr, mkdir_by=mkdir_by, archive_dir=archive_dir, key_deprecation=True, keymap=KEYMAP, verbose=verbose) newpaths.append(newpath) if imagetyp == "object": objpaths.append(newpath) # Save list of file paths for future use. # It doesn't take much storage and easy to erase if you want. with open(self.topdir / 'newpaths.list', 'w+') as ll: for p in newpaths: ll.write(f"{str(p)}\n") with open(self.topdir / 'objpaths.list', 'w+') as ll: for p in objpaths: ll.write(f"{str(p)}\n") # Python specific pickle with open(self.topdir / 'newpaths.pkl', 'wb') as pkl: pickle.dump(newpaths, pkl) with open(self.topdir / 'objpaths.pkl', 'wb') as pkl: pickle.dump(objpaths, pkl) self.newpaths = newpaths self.objpaths = objpaths self.summary = make_summary(newpaths, output=self.topdir / "summary_raw.csv", format='ascii.csv', keywords=self.summary_keywords, verbose=verbose)
"""Draft pull requests to implement units, including arithmetic, in FITS headers """ import astropy.units as u from astropy.io.fits import Card, Header print('##### DRAFT CARD QUANTITY TESTER') print('+++Create plain card') print(">>> c = Card('EXPTIME', 10, 'Exposure time in seconds')") c = Card('EXPTIME', 10, 'Exposure time in seconds') print('print(c.image)') print(c.image) print('print(c.value)') print(c.value) print('print(c.unit)') print(c.unit) print('print(c.comment)') print(c.comment) print('+++Create card with units in comment') print(">>> c = Card('EXPTIME', 10, 'Exposure time (s)')") c = Card('EXPTIME', 10, 'Exposure time (s)') ##rkaq = 'always' #rkaq = 'recognized' #c.return_key_as_quantity = rkaq print('print(c.image)') print(c.image) print('print(c.value)') print(c.value) print('print(c.unit)')
def organize_raw(self, rename_by=[ "OBSCAM", "OBJECT", "XBINNING", "YBINNING", "YMD-HMS", "FILTER", "EXPTIME" ], mkdir_by=["OBJECT"], delimiter='-', archive_dir=None, verbose=False): ''' Rename FITS files after updating theur headers. Parameters ---------- rename_by : list of str The keywords in header to be used for the renaming of FITS files. Each keyword values are connected by ``delimiter``. mkdir_by : list of str, optional The keys which will be used to make subdirectories to classify files. If given, subdirectories will be made with the header value of the keys. delimiter : str, optional The delimiter for the renaming. archive_dir : path-like or None, optional Where to move the original FITS file. If ``None``, the original file will remain there. Deleting original FITS is dangerous so it is only supported to move the files. You may delete files manually if needed. ''' newpaths = [] objpaths = [] uselessdir = self.rawdir / "useless" yfu.mkdir(uselessdir) yfu.mkdir(self.listdir) str_imgtyp = ( "{:s}: IMAGETYP in header ({:s}) and that inferred from" + "the filename ({:s}) doesn't seem to match.") str_useless = "{} is not a regular name. Moving to {}. " str_obj = ("{:s}: OBJECT in header({:s}) != filename({:s}). " + "OBJECT in header is updated to match the filename.") # NOTE: it is better to give the filename a higher priority because # it is easier to change filename than FITS header. for fpath in self.rawpaths: if fpath.name.startswith("CCD Image"): # The image not taken correctly are saved as dummy name # "CCD Image xxx.fit". It is user's fault to have this # kind of image, so move it to useless. print(str_useless.format(fpath.name, uselessdir)) fpath.rename(uselessdir / fpath.name) continue # else: try: # Use `rsplit` because sometimes there are objnames like # `sa101-100`, i.e., includes the hyphen. # filt_or_bd : B/V/R/I/Ha/Sii/Oiii or bias/dkXX (XX=EXPTIME) hdr = fits.getheader(fpath) sp = fpath.name.rsplit('-') if len(sp) == 1: sp = fpath.name.rsplit('_') obj_raw = sp[0] counter = sp[-1].split('.')[0][:4] filt_bd = sp[-1].split('.')[0][4:] filt_bd_low = filt_bd.lower() if obj_raw.lower() == 'cali': if filt_bd_low.startswith("b"): imgtyp = "bias" elif filt_bd_low.startswith("d"): imgtyp = "dark" else: print(str_useless.format(fpath.name, uselessdir)) fpath.rename(uselessdir / fpath.name) else: imgtyp = hdr["IMAGETYP"] except IndexError: print(str_useless.format(fpath.name, uselessdir)) fpath.rename(uselessdir / fpath.name) continue cards_to_add = [] # Update header OBJECT cuz it is super messy... # Bias / Dark: understood from header IMAGETYP # Dome / Sky flat / Object frame : understood from filename if imgtyp.lower() in ["bias", "bias frame"]: if not filt_bd_low.startswith("b"): warn(str_imgtyp.format(fpath.name, imgtyp, filt_bd_low)) obj = "bias" elif imgtyp.lower() in ["dark", "dark frame"]: if not filt_bd_low.startswith("d"): warn(str_imgtyp.format(fpath.name, imgtyp, filt_bd_low)) obj = "dark" elif obj_raw.lower() in ["skyflat", "domeflat"]: obj = obj_raw.lower() elif imgtyp.lower() in ["flat", "flat field"]: obj = "flat" else: if obj_raw != str(hdr[KEYMAP["OBJECT"]]): warn( str_obj.format(fpath.name, hdr[KEYMAP["OBJECT"]], obj_raw)) obj = obj_raw hdr[KEYMAP["OBJECT"]] = obj # Add gain and rdnoise: grdcards = cards_gain_rdnoise(instrument=self.instrument) [cards_to_add.append(c) for c in grdcards] # Add counter if there is none: if "COUNTER" not in hdr: cards_to_add.append(Card("COUNTER", counter, "Image counter")) # Add unit if there is none: if "BUNIT" not in hdr: cards_to_add.append(Card("BUNIT", "ADU", "Pixel value unit")) # Calculate airmass except for bias/dark if obj not in ["bias", "dark"]: # FYI: flat require airmass just for check (twilight/night) try: hdr = yfu.airmass_from_hdr(hdr, ra_key="OBJCTRA", dec_key="OBJCTDEC", ut_key=KEYMAP["DATE-OBS"], exptime_key=KEYMAP["EXPTIME"], lon_key="SITELONG", lat_key="SITELAT", height_key="HEIGHT", equinox="J2000", frame='icrs', height=147, return_header=True) except KeyError: if verbose: print(f"{fpath} failed in airmass calculation: " + "KeyError") datetime = Time(hdr[KEYMAP["DATE-OBS"]]).strftime("%Y%m%d-%H%M%S") obscam = f"SNUO_{self.instrument}" # Add YMD-HMS, and OBS-CAM cards_to_add.append(Card("YMD-HMS", datetime, "YYYYmmdd-HHMMSS")) cards_to_add.append( Card("OBSCAM", obscam, "<observatory>_<camera>")) add_hdr = fits.Header(cards_to_add) newpath = yfu.fitsrenamer(fpath, header=hdr, rename_by=rename_by, delimiter=delimiter, add_header=add_hdr, mkdir_by=mkdir_by, archive_dir=archive_dir, key_deprecation=True, keymap=KEYMAP, verbose=verbose) newpaths.append(newpath) if obj not in ["flat", "skyflat", "domeflat", "bias", "dark"]: objpaths.append(newpath) # Save list of file paths for future use. # It doesn't take much storage and easy to erase if you want. with open(self.listdir / 'newpaths.list', 'w+') as ll: for p in newpaths: ll.write(f"{str(p)}\n") with open(self.listdir / 'objpaths.list', 'w+') as ll: for p in objpaths: ll.write(f"{str(p)}\n") # Python specific pickle with open(self.listdir / 'newpaths.pkl', 'wb') as pkl: pickle.dump(newpaths, pkl) with open(self.listdir / 'objpaths.pkl', 'wb') as pkl: pickle.dump(objpaths, pkl) self.newpaths = newpaths self.objpaths = objpaths self.summary_raw = yfu.make_summary(newpaths, output=self.topdir / "summary_raw.csv", keywords=self.summary_keywords, pandas=True, verbose=verbose)
def _cf_add_setup_info(self, hdul: HDUList): h = hdul[0].header h.append(Card('COMMENT', '======================')) h.append(Card('COMMENT', ' Transit search setup ')) h.append(Card('COMMENT', '======================')) h.append(Card('pmin', self.pmin, 'Minimum search period [d]'), bottom=True) h.append(Card('pmax', self.pmax, 'Maximum search period [d]'), bottom=True) h.append(Card('dper', (self.pmax - self.pmin) / self.nper, 'Period grid step size [d]'), bottom=True) h.append(Card('nper', self.nper, 'Period grid size'), bottom=True) h.append(Card('dmin', 0, 'Minimum search duration [d]'), bottom=True) h.append(Card('dmax', 0, 'Maximum search duration [d]'), bottom=True) h.append(Card('ddur', 0, 'Duration grid step size [d]'), bottom=True) h.append(Card('ndur', 0, 'Duration grid size'), bottom=True)
return hdus # data_list = [hdu.data for hdu in hdu_list] # return data_list def get_combined_image(hdus): #, destripe=True): # destripe=True): data_list = [hdu.data for hdu in hdus] from stsci_helper import stsci_median im = stsci_median(data_list) return im if 0: cards = [] if destripe: from destriper import destriper im = destriper.get_destriped(im) cards.append(Card("HISTORY", "IGR: image destriped.")) return im, cards
def _cards_airmass(am_eff, alldict): ''' Gives airmass and alt-az related header cards. ''' amstr = ( "ysfitsutilpy's airmass calculation uses the same algorithm as IRAF: From " + "'Some Factors Affecting the Accuracy of Stellar Photometry with CCDs' by " + "P. Stetson, DAO preprint, September 1988.") # At some times, hdr["AIRMASS"] = am, for example, did not work for # some reasons which I don't know.... So I used Card. - # YPBach 2018-05-04 cs = [ Card("AIRMASS", am_eff, "Effective airmass (Stetson 1988; see COMMENT)"), Card("ZD", alldict["zd"][0], "[deg] Zenithal distance (start of the exposure)"), Card("ALT", alldict["alt"][0], "Altitude (start of the exposure)"), Card("AZ", alldict["az"][0], "Azimuth (start of the exposure)"), Card("ALT_MID", alldict["alt"][1], "Altitude (midpoint of the exposure)"), Card("AZ_MID", alldict["az"][1], "Azimuth (midpoint of the exposure)"), Card("ZD_MID", alldict["zd"][1], "[deg] Zenithal distance (midpoint of the exposure)"), Card("ALT_END", alldict["alt"][2], "Altitude (end of the exposure)"), Card("AZ_END", alldict["az"][2], "Azimuth (end of the exposure)"), Card("ZD_END", alldict["zd"][2], "[deg] Zenithal distance (end of the exposure)"), Card("COMMENT", amstr), Card("HISTORY", "ALT-AZ calculated from ysfitsutilpy."), Card("HISTORY", "AIRMASS calculated from ysfitsutilpy.") ] return cs
def add_to_fits(self, hdul: HDUList): def fn(v): return v if isfinite(v) else -1 if self.lpf is not None: p = self.parameters c = self.mode[0] h = hdul[0].header h.append(Card('COMMENT', '======================')) h.append(Card('COMMENT', self.title)) h.append(Card('COMMENT', '======================')) h.append(Card(f'TF{c}_T0', p.tc.med, 'Transit centre [BJD]'), bottom=True) h.append(Card(f'TF{c}_T0E', p.tc.err, 'Transit centre uncertainty [d]'), bottom=True) h.append(Card(f'TF{c}_PR', p.p.med, 'Orbital period [d]'), bottom=True) h.append(Card(f'TF{c}_PRE', p.p.err, 'Orbital period uncertainty [d]'), bottom=True) h.append(Card(f'TF{c}_RHO', p.rho.med, 'Stellar density [g/cm^3]'), bottom=True) h.append(Card(f'TF{c}_RHOE', p.rho.err, 'Stellar density uncertainty [g/cm^3]'), bottom=True) h.append(Card(f'TF{c}_B', p.b.med, 'Impact parameter'), bottom=True) h.append(Card(f'TF{c}_BE', p.b.err, 'Impact parameter uncertainty'), bottom=True) h.append(Card(f'TF{c}_AR', p.k2.med, 'Area ratio'), bottom=True) h.append(Card(f'TF{c}_ARE', p.k2.err, 'Area ratio uncertainty'), bottom=True) #h.append(Card(f'TF{c}_SC', p.c_sin.med, 'Sine phase'), bottom=True) #h.append(Card(f'TF{c}_SCE', p.c_sin.err, 'Sine phase uncertainty'), bottom=True) #h.append(Card(f'TF{c}_SA', p.a_sin_0.med, 'Sine amplitude'), bottom=True) #h.append(Card(f'TF{c}_SAE', p.a_sin_0.err, 'Sine amplitude uncertainty'), bottom=True) h.append(Card(f'TF{c}_RR', p.k.med, 'Radius ratio'), bottom=True) h.append(Card(f'TF{c}_RRE', p.k.err, 'Radius ratio uncertainty'), bottom=True) h.append(Card(f'TF{c}_A', p.a.med, 'Semi-major axis'), bottom=True) h.append(Card(f'TF{c}_AE', p.a.err, 'Semi-major axis uncertainty'), bottom=True) h.append(Card(f'TF{c}_T14', fn(p.t14.med), 'Transit duration T14 [d]'), bottom=True) h.append(Card(f'TF{c}_T14E', fn(p.t14.err), 'Transit duration T14 uncertainty [d]'), bottom=True) h.append(Card(f'TF{c}_T23', fn(p.t23.med), 'Transit duration T23 [d]'), bottom=True) h.append(Card(f'TF{c}_T23E', fn(p.t23.err), 'Transit duration T23 uncertainty [d]'), bottom=True) if isfinite(p.t23.med) and isfinite(p.t23.err): h.append(Card(f'TF{c}_TDR', p.t23.med / p.t14.med, 'T23 to T14 ratio'), bottom=True) else: h.append(Card(f'TF{c}_TDR', 0, 'T23 to T14 ratio'), bottom=True) h.append(Card(f'TF{c}_WN', 10 ** p.wn_loge_0.med, 'White noise std'), bottom=True) h.append(Card(f'TF{c}_GRAZ', p.b.med + p.k.med > 1., 'Is the transit grazing'), bottom=True) ep = self.dll_epochs ll = self.dll_values lm = ll.max() h.append(Card(f'TF{c}_DLLA', log(exp(ll - lm).mean()) + lm, 'Mean per-orbit delta log likelihood'), bottom=True) if self.mode == 'all': m = ep % 2 == 0 lm = ll[m].max() h.append(Card(f'TFA_DLLO', log(exp(ll[m] - lm).mean()) + lm, 'Mean per-orbit delta log likelihood (odd)'), bottom=True) m = ep % 2 != 0 lm = ll[m].max() h.append(Card(f'TFA_DLLE', log(exp(ll[m] - lm).mean()) + lm, 'Mean per-orbit delta log likelihood (even)'), bottom=True)