def get_fine_guidance_sensor_lock_type(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<fine_guidance_system_lock_type />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) return lookup["FGSLOCK"].strip()
def get_filter_name(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<filter_name />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) instrument = get_instrument_id(data_lookups, shm_lookup) if instrument == "ACS": filter1 = lookup["FILTER1"].strip() filter2 = lookup["FILTER2"].strip() if filter1.startswith("CLEAR"): if filter2.startswith("CLEAR") or filter2 == "N/A": return "CLEAR" else: return filter2 if filter2.startswith("CLEAR") or filter2 == "N/A": return filter1 # At this point, both filters start with "F" followed by three digits, # or "POL" for polarizers. Sort by increasing wavelength; put # polarizers second; join with a plus. filters = [filter1, filter2] filters.sort() return "+".join(filters) if instrument == "FOC": filters = [ lookup["FILTER1"].strip(), lookup["FILTER2"].strip(), lookup["FILTER3"].strip(), lookup["FILTER4"].strip(), ] filters = [f for f in filters if not f.startswith("CLEAR")] filters.sort() return "+".join(filters) if instrument in ("FOS", "HSP"): return shm_lookup["SPEC_1"].strip() if instrument == "GHRS": return lookup["GRATING"].strip() if instrument == "STIS": opt_elem = lookup["OPT_ELEM"].strip() filter = lookup["FILTER"].strip().upper().replace(" ", "_") if filter == "CLEAR": return opt_elem else: return opt_elem + "+" + filter if instrument in ("WF/PC", "WFPC2"): filtnam1 = lookup["FILTNAM1"].strip() filtnam2 = lookup["FILTNAM2"].strip() if filtnam1 == "": return filtnam2 if filtnam2 == "": return filtnam1 # At this point, both filters start with "F", followed by three digits. # Put lower value first; join with a plus. filters = [filtnam1, filtnam2] filters.sort() return "+".join(filters) # For other instruments there is just zero or one filter try: return lookup["FILTER"].strip() except KeyError: return "Not applicable"
def get_gyroscope_mode(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<gyroscope_mode />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) try: return str(lookup["GYROMODE"]).strip().replace("T", "3") except KeyError: return "3" # Three-gyro mode unless otherwise specified
def get_repeat_exposure_count(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<repeat_exposure_count />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) try: return str(lookup["NRPTEXP"]) except KeyError: return "1"
def get_exposure_duration(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return a float for the ``<exposure_duration />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) try: return str(lookup["EXPTIME"]) except KeyError: return str(lookup["TEXPTIME"])
def get_bandwidth(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return a float for the ``<bandwidth />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) # Works for STIS and WFPC2 try: return "%.4f" % (float(lookup["BANDWID"]) * 1.0e-4) except KeyError: return "0."
def get_cosmic_ray_split_count(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<cosmic_ray_split_count />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) try: return str(lookup["CRSPLIT"]) except KeyError: return "1" # no CR-splitting unless explicitly stated
def get_instrument_id(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<instrument_id />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) instrument = lookup["INSTRUME"].strip() if instrument == "HRS": return "GHRS" if instrument == "WFPC": return "WF/PC" return instrument
def get_center_filter_wavelength(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return a float for the ``<center_filter_wavelength />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) # Works for STIS and WFPC2 try: return "%.4f" % (float(lookup["CENTRWV"]) * 1.0e-4) except KeyError: return "0."
def get_spectral_resolution(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<spectral_resolution />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) # Works for STIS try: return "%.4f" % (float(lookup["SPECRES"]) * 1.0e-4) except KeyError: return "0."
def get_proposed_aperture_name(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<proposed_aperture_name />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) try: res = lookup["PROPAPER"].strip() # only a few instruments distinguish if res: return res else: return get_aperture_name(data_lookups, shm_lookup) except KeyError: return get_aperture_name(data_lookups, shm_lookup)
def get_mast_observation_id(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<mast_observation_id />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) KEYS = ["ROOTNAME", "ASN_ID"] for keyword in KEYS: try: return lookup[keyword].strip().lower() except KeyError: pass raise RuntimeError(f"lookup = {lookup}, shm_lookup = {shm_lookup}")
def get_subarray_flag(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<subarray_flag />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) try: value: str = lookup["SUBARRAY"] if value == "1" or value.startswith("T"): return "true" elif value == "0" or value.startswith("F"): return "false" raise ValueError("unrecognized SUBARRAY value (%s) in %s" % (value, fname(lookup))) except KeyError: return "false"
def get_instrument_mode_id(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<instrument_mode_id />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) instrument = get_instrument_id(data_lookups, shm_lookup) if instrument in ("WF/PC", "WFPC2"): return lookup["MODE"].strip() if instrument == "FOC": return lookup["OPTCRLY"].strip() if instrument == "HSP": return shm_lookup["OPMODE"].strip() # For most HST instrumnents, this should work... try: return lookup["OBSMODE"].strip() except KeyError: pass raise ValueError("instrument_mode_id not found for " + fname(lookup))
def get_channel_id(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<channel_id />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) instrument = get_instrument_id(data_lookups, shm_lookup) if instrument == "NICMOS": result = "NIC" + str(lookup["CAMERA"]) elif instrument == "WF/PC": result = lookup["CAMERA"].strip() else: try: ccd = lookup["DETECTOR"].strip() if instrument == "WFPC2": return WFPC2_DETECTOR_IDS[ccd] else: return ccd except KeyError: result = instrument return result
def get_binning_mode(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<binning_mode />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) instrument = get_instrument_id(data_lookups, shm_lookup) # WF/PC and WFPC2 are special cases if instrument in ("WF/PC", "WFPC2"): obsmode = lookup["MODE"].strip() if obsmode == "FULL": return "1" else: # obsmode == "AREA" return "2" # Binning info can be in the first or second FITS header for lookup in data_lookups[:2]: try: binaxis1 = lookup["BINAXIS1"] binaxis2 = lookup["BINAXIS2"] return str(max(binaxis1, binaxis2)) except KeyError: pass return "1"
def get_gain_setting(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<gain_mode_id />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) # Works for WFPC2 try: wfpc2_gain: int = int(float( lookup["ATODGAIN"])) # format WFPC2 gains as ints if wfpc2_gain in (7, 15): return str(wfpc2_gain) raise ValueError("unrecognized WFPC2 gain (%d) in %s" % (wfpc2_gain, fname(lookup))) except KeyError: pass # Works for ACS, WFC3, others try: gain: float = float(lookup["CCDGAIN"]) return "%3.1f" % gain # format other gains with one decimal except KeyError: pass return "0."
def get_plate_scale(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<plate_scale />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) # Works for STIS try: return str(lookup["PLATESC"]) except KeyError: pass # Works for any instrument tabulated in the PLATE_SCALEs dictionary above instrument = get_instrument_id(data_lookups, shm_lookup) detectors = get_detector_ids(data_lookups, shm_lookup) scale = SCALE_MAX = 1.0e99 for detector in detectors: key = (instrument, detector) if key in PLATE_SCALES: scale = min(scale, PLATE_SCALES[key]) if scale < SCALE_MAX: scale *= int(get_binning_mode(data_lookups, shm_lookup)) formatted = "%.4f" % scale # up to 4 decimal places return formatted.rstrip("0") # don't include trailing zeros return "0.0"
def get_observation_type(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<observation_type />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) try: obstype = lookup["OBSTYPE"].strip() if obstype not in ("IMAGING", "SPECTROGRAPHIC"): obstype = "" except KeyError: obstype = "" if not obstype: instrument = get_instrument_id(data_lookups, shm_lookup) if instrument in ("ACS", "NICMOS", "WFC3", "WF/PC", "WFPC2"): obstype = "IMAGING" elif instrument in ("COS", "FOS", "GHRS"): obstype = "SPECTROGRAPHIC" elif instrument == "HSP": obstype = "TIME-SERIES" else: raise ValueError("missing OBSTYPE in " + fname(lookup)) return obstype
def get_targeted_detector_ids(data_lookups: List[Lookup], shm_lookup: Lookup) -> List[str]: """ Return a list of one or more text values for the ``<targeted_detector_ids />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) instrument = get_instrument_id(data_lookups, shm_lookup) aperture = get_aperture_name(data_lookups, shm_lookup) if instrument == "WFPC2": if aperture in ("WFALL", "WFALL-FIX"): return ["PC1", "WF2", "WF3", "WF4"] if aperture in ("PC1", "PC1-FIX", "POLQP15P", "FQCH4P15"): return ["PC1"] if aperture in ( "WF2", "WF2-FIX", "FQUVN33", "POLQN33", "POLQN18", "POLQP15W", "FQCH4NW2", ): return ["WF2"] if aperture == "FQCH4N33": return ["WF2", "WF3"] if aperture in ("WF3", "WF3-FIX", "FQCH4NW3", "F160BN15"): return ["WF3"] if aperture in ("WF4", "WF4-FIX", "FQCH4NW4"): return ["WF4"] if aperture == "FQCH4N1": return ["PC1", "WF3"] if aperture == "FQCH4N15": return ["PC1"] if aperture == "FQCH4W3": return ["WF3"] raise ValueError( "unrecognized WFPC2 aperture (%s) for %s [%s]", (aperture, fname(lookup), lookup), ) channel = get_channel_id(data_lookups, shm_lookup) if instrument == "ACS" and channel == "WFC": if aperture.startswith("WFC1"): return ["WFC1"] if aperture.startswith("WFC2"): return ["WFC2"] return ["WFC1", "WFC2"] if instrument == "WFC" and channel == "UVIS": if aperture.startswith("UVIS1"): return ["UVIS1"] if aperture.startswith("UVIS2"): return ["UVIS2"] if aperture.startswith("UVIS-QUAD"): filter = lookup["FILTER"].strip() if filter in ( "FQ378N", "FQ387N", "FQ437N", "FQ492N", "FQ508N", "FQ619N", "FQ674N", "FQ750N", "FQ889N", "FQ937N", ): return ["UVIS1"] if filter in ( "FQ232N", "FQ243N", "FQ422M", "FQ436N", "FQ575N", "FQ634N", "FQ672N", "FQ727N", "FQ906N", "FQ924N", ): return ["UVIS2"] raise ValueError( "unrecognized quad aperture/filter (%s/%s) in %s" % (aperture, filter, fname(lookup))) return ["UVIS1", "UVIS2"] if instrument == "WF/PC": # I cannot find documentation for the apertures for WF/PC so this is # just an educated guess if aperture not in ("ALL", "W1", "W2", "W3", "W4", "P5", "P6", "P7", "P8"): raise ValueError("unknown WF/PC aperture (%s) in %s" % (aperture, fname(lookup))) if aperture == "ALL": return get_detector_ids(data_lookups, shm_lookup) # all detectors elif aperture.startswith("W"): return [aperture[0] + "F" + aperture[1]] # WFn else: return [aperture[0] + "C" + aperture[1]] # PCn return get_detector_ids(data_lookups, shm_lookup)
def get_detector_ids(data_lookups: List[Lookup], shm_lookup: Lookup) -> List[str]: """ Return a list of zero or more text values for the ``<detector_id />`` XML elements. """ # Interior function def get_ccds_from_lookups(data_lookups: List[Lookup], fitsname: str) -> List[int]: ccds: List[int] = [] for lookup in data_lookups: try: ccdchip = int(lookup[fitsname]) ccds.append(ccdchip) except KeyError: pass ccds = list(set(ccds)) # select unique values ccds.sort() return ccds lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) instrument = get_instrument_id(data_lookups, shm_lookup) channel = get_channel_id(data_lookups, shm_lookup) if instrument == "ACS" and channel == "WFC": ccds = get_ccds_from_lookups(data_lookups, "CCDCHIP") if -999 in ccds: ccds = [1, 2] result = [f"WFC{k}" for k in ccds] elif instrument == "COS" and channel == "FUV": segment = lookup["SEGMENT"].strip() if segment not in ("FUVA", "FUVB", "BOTH"): raise ValueError("unrecognized segment (%s) in %s" % (segment, fname(lookup))) if segment == "FUVA": result = ["FUVA"] elif segment == "FUVB": result = ["FUVB"] else: result = ["FUVA", "FUVB"] elif instrument == "GHRS": result = ["GHRS" + str(lookup["DETECTOR"])] elif instrument == "HSP": config = shm_lookup["CONFIG"].strip() # Example: config = HSP/UNK/VIS parts = config.split("/") if parts[0] != "HSP": raise ValueError(f"Invalid CONFIG value in {fname(lookup)}.") result = [p for p in parts[1:] if p != "UNK"] elif instrument == "WFC3" and channel == "UVIS": ccds = get_ccds_from_lookups(data_lookups, "CCDCHIP") if -999 in ccds: ccds = [1, 2] result = [f"UVIS{k}" for k in ccds] elif instrument == "WF/PC": # We will need to find a workaround to read the FITS table from the data # file, because that is the only way to get the actual set of detectors # if there are less than four! I hope it just doesn't come up. count = lookup["NAXIS3"] if count != 4: raise ValueError("unknown detector subset in (%d/4) in %s" % (count, fname(lookup))) if channel not in ("PC", "WFC"): raise ValueError(f"Bad channel for {fname(lookup)}.") if channel == "WFC": result = ["WF1", "WF2", "WF3", "WF4"] else: result = ["PC5", "PC6", "PC7", "PC8"] elif instrument == "WFPC2": ccds = get_ccds_from_lookups(data_lookups, "DETECTOR") result = [WFPC2_DETECTOR_IDS[k] for k in ccds] # Otherwise, return the single value of channel_id else: result = [channel] return result
def get_start_stop_date_times(data_lookups: List[Lookup], shm_lookup: Lookup) -> Tuple[str, str]: """ Return text for the ``<start_date_time />`` and ``<stop_date_time />`` XML elements. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) # HST documents indicate that times are only accurate to a second or so. # This is consistent with the fact that start times indicated by DATE-OBS # and TIME-OBS often disagree with the times as indicated by EXPSTART at the # level of a second or so. For any individual time, this is fine, but we # want to be sure that the difference between the start and stop times is # compatible with the exposure time, whenever appropriate. # # I say "whenever appropriate" because there are times when multiple images # have been drizzled or otherwise merged. In this case, the start and stop # times refer to the first and last of the set of images, respectively, and # their difference can be much greater than the exposure time. # # It takes some careful handling to get the behavior we want. # Figure out what's available in the header try: date_obs = lookup["DATE-OBS"] except KeyError: date_obs = None try: time_obs = lookup["TIME-OBS"] except KeyError: time_obs = None exptime = float(lookup["EXPTIME"]) try: # either EXPSTART or TEXPSTART should be available expstart = float(lookup["EXPSTART"]) except KeyError: expstart = float(lookup["TEXPSTART"]) try: # either EXPEND or TEXPEND should be available expend = float(lookup["EXPEND"]) except KeyError: expend = float(lookup["TEXPEND"]) # Decide which delta-time to use # Our start and stop times are only ever good to the nearest second, but we # want to ensure that the difference looks right. For this purpose, # non-integral exposure times should be rounded up to the next integer. delta_from_mjd = (expend - expstart) * 86400.0 if delta_from_mjd > exptime + 2.0: # if the delta is too large, we know # multiple images were combined delta = delta_from_mjd else: delta = -(-exptime // 1.0) # rounded up to nearest int # Fill in the start time; update the expstart in MJD units if necessary. # If DATE-OBS and TIME-OBS values are provided, we use this as the start # time because it is the value our users would expect. There exist cases # when these values are not provided, and in that case we use EXPSTART, # converted from MJD. Note that these MJD values are in UTC, not TAI. In # other words, we need to ignore leapseconds in these time conversions. if date_obs and time_obs: start_time = date_obs + "T" + time_obs + "Z" day = julian.day_from_iso(date_obs) sec = julian.sec_from_iso(time_obs) expstart = julian.mjd_from_day_sec(day, sec) else: (day, sec) = julian.day_sec_from_mjd(expstart) start_time = julian.ymdhms_format_from_day_sec(day, sec, suffix="Z") # Fill in the stop time. We ensure that this differs from the start time by # the expected amount. expend = expstart + delta / 86400.0 (day, sec) = julian.day_sec_from_mjd(expend) stop_time = julian.ymdhms_format_from_day_sec(day, sec, suffix="Z") return (start_time, stop_time)
def get_exposure_type(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<exposure_type />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) return lookup["EXPFLAG"].strip()
def get_hst_target_name(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<hst_target_name />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) return lookup["TARGNAME"]
def get_hst_proposal_id(data_lookups: List[Lookup], shm_lookup: Lookup) -> str: """ Return text for the ``<hst_proposal_id />`` XML element. """ lookup = merge_two_hdu_lookups(data_lookups[0], data_lookups[1]) return str(lookup["PROPOSID"])