diff --git a/python/lvmdrp/core/image.py b/python/lvmdrp/core/image.py index 1ce502b3..09074036 100644 --- a/python/lvmdrp/core/image.py +++ b/python/lvmdrp/core/image.py @@ -1124,22 +1124,23 @@ def convertUnit(self, to, assume="adu", gain_field="GAIN", inplace=False): return new_image if current != to: + camera = self.getHdrValue("CCD").upper() exptime = self.getHdrValue("EXPTIME") - gains = self.getHdrValue(f"AMP? {gain_field}") - sects = self.getHdrValue("AMP? TRIMSEC") + gains = self.getHdrValue(f"{camera} AMP? {gain_field}") + sects = self.getHdrValue(f"{camera} AMP? TRIMSEC") n_amp = len(gains) for i in range(n_amp): if current == "adu" and to == "electron": factor = gains[i] - elif current == "adu" and to == "electron/s": + elif current == "adu" and to == "electron / s": factor = gains[i] / exptime elif current == "electron" and to == "adu": factor = 1 / gains[i] - elif current == "electron" and to == "electron/s": + elif current == "electron" and to == "electron / s": factor = 1 / exptime - elif current == "electron/s" and to == "adu": + elif current == "electron / s" and to == "adu": factor = gains[i] * exptime - elif current == "electron/s" and to == "electron": + elif current == "electron / s" and to == "electron": factor = exptime else: raise ValueError(f"Cannot convert from {current} to {to}") @@ -1343,7 +1344,7 @@ def loadFitsData( elif hdu[i].header["EXTNAME"].split()[0] == "FRAMES": self._individual_frames = Table(hdu[i].data) elif hdu[i].header["EXTNAME"].split()[0] == "SLITMAP": - self._slitmap = Table(hdu[i].data) + self._slitmap = Table.read(hdu[i]) else: if extension_data is not None: @@ -1360,7 +1361,7 @@ def loadFitsData( if extension_frames is not None: self._individual_frames = Table(hdu[extension_frames].data) if extension_slitmap is not None: - self._slitmap = Table(hdu[extension_slitmap].data) + self._slitmap = Table.read(hdu[extension_slitmap]) # set is_masked attribute self.is_masked = numpy.isnan(self._data).any() @@ -1454,15 +1455,12 @@ def writeFitsData( # hdus[0].update_ext_name('T') if len(hdus) > 0: - hdus[0].header['DRPVER'] = drpver hdu = pyfits.HDUList(hdus) # create an HDUList object if self._header is not None: - hdu[0].header = self.getHeader() # add the primary header to the HDU - try: - hdu[0].header["BZERO"] = 0 - except KeyError: - pass + hdu[0].header = self.getHeader() + hdu[0].header['DRPVER'] = drpver hdu[0].update_header() + hdu[0].scale(bzero=0, bscale=1) os.makedirs(os.path.dirname(filename), exist_ok=True) hdu.writeto(filename, output_verify="silentfix", overwrite=True) @@ -3154,7 +3152,7 @@ def getSlitmap(self): def setSlitmap(self, slitmap): if isinstance(slitmap, pyfits.BinTableHDU): - self._slitmap = Table(slitmap.data) + self._slitmap = Table.read(slitmap) else: self._slitmap = slitmap @@ -3376,7 +3374,8 @@ def combineImages( stack_error[stack_mask] = numpy.nan if background_subtract: - quad_sections = images[0].getHdrValues("AMP? TRIMSEC") + camera = images[0].getHdrValue("CCD").upper() + quad_sections = images[0].getHdrValues(f"{camera} AMP? TRIMSEC") stack_image, _, _, _ = _bg_subtraction( images=stack_image, quad_sections=quad_sections, diff --git a/python/lvmdrp/core/plot.py b/python/lvmdrp/core/plot.py index df97845d..3f1af073 100644 --- a/python/lvmdrp/core/plot.py +++ b/python/lvmdrp/core/plot.py @@ -271,7 +271,8 @@ def plot_detrend(ori_image, det_image, axs, mbias=None, mdark=None, labels=False """ # define quadrant sections - sections = ori_image.getHdrValue("AMP? TRIMSEC") + camera = ori_image.getHdrValue("CCD").upper() + sections = ori_image.getHdrValue(f"{camera} AMP? TRIMSEC") # convert all images to adu unit = "adu" diff --git a/python/lvmdrp/core/rss.py b/python/lvmdrp/core/rss.py index bfd5c716..2f2d2634 100644 --- a/python/lvmdrp/core/rss.py +++ b/python/lvmdrp/core/rss.py @@ -913,21 +913,29 @@ def eval_wcs(self, wave=None, data=None, as_dict=True): if wave is not None and len(wave.shape) == 1: wcs_dict = {"NAXIS": 2, "NAXIS1": data.shape[1], "NAXIS2": data.shape[0], - "CDELT1": wave[1]-wave[0], - "CRVAL1": wave[0], - "CUNIT1": "Angstrom", "CTYPE1": "WAVE", "CRPIX1": 1, - "CDELT2": 1, - "CRVAL2": 1, - "CUNIT2": "", "CTYPE2": "FIBERID", "CRPIX2": 1} + "CDELT1": (wave[1]-wave[0], "coordinate increment along axis"), + "CRVAL1": (wave[0], "coordinate system value at reference pixel"), + "CUNIT1": ("Angstrom", "physical unit of the coordinate axis"), + "CTYPE1": ("WAVE", "name of the coordinate axis"), + "CRPIX1": (1, "coordinate system reference pixel"), + "CDELT2": (1, "coordinate increment along axis"), + "CRVAL2": (1, "coordinate system value at reference pixel"), + "CUNIT2": ("", "physical unit of the coordinate axis"), + "CTYPE2": ("FIBERID", "name of the coordinate axis"), + "CRPIX2": (1, "coordinate system reference pixel")} elif wave is None or len(wave.shape) == 2: wcs_dict = {"NAXIS": 2, "NAXIS1": data.shape[1], "NAXIS2": data.shape[0], - "CDELT1": 1, - "CRVAL1": 1, - "CUNIT1": "", "CTYPE1": "XAXIS", "CRPIX1": 1, - "CDELT2": 1, - "CRVAL2": 1, - "CUNIT2": "", "CTYPE2": "FIBERID", "CRPIX2": 1} + "CDELT1": (1, "coordinate increment along axis"), + "CRVAL1": (1, "coordinate system value at reference pixel"), + "CUNIT1": ("", "physical unit of the coordinate axis"), + "CTYPE1": ("XAXIS", "name of the coordinate axis"), + "CRPIX1": (1, "coordinate system reference pixel"), + "CDELT2": (1, "coordinate increment along axis"), + "CRVAL2": (1, "coordinate system value at reference pixel"), + "CUNIT2": ("", "physical unit of the coordinate axis"), + "CTYPE2": ("FIBERID", "name of the coordinate axis"), + "CRPIX2": (1, "coordinate system reference pixel")} if as_dict: return wcs_dict @@ -1801,7 +1809,7 @@ def rectify_wave(self, wave=None, wave_range=None, wave_disp=None): if rss._header is None: rss._header = pyfits.Header() unit = rss._header["BUNIT"] - if not unit.endswith("/angstrom"): + if not unit.endswith(" / Angstrom"): dlambda = numpy.gradient(rss._wave, axis=1) rss._data /= dlambda if rss._error is not None: @@ -1810,10 +1818,10 @@ def rectify_wave(self, wave=None, wave_range=None, wave_disp=None): rss._sky /= dlambda if rss._sky_error is not None: rss._sky_error /= dlambda - unit = unit + "/angstrom" + unit = unit + " / Angstrom" rss._header["BUNIT"] = unit - rss._header["WAVREC"] = True + rss._header["WAVREC"] = (True, "is wavelength rectified?") # create output RSS new_rss = RSS( data=numpy.zeros((rss._fibers, wave.size), dtype="float32"), @@ -3097,7 +3105,7 @@ def setSlitmap(self, slitmap): self._slitmap = None return if isinstance(slitmap, pyfits.BinTableHDU): - self._slitmap = Table(slitmap.data) + self._slitmap = Table.read(slitmap) elif isinstance(slitmap, Table): self._slitmap = slitmap else: @@ -3141,7 +3149,7 @@ def set_fluxcal(self, fluxcal, source="std"): setattr(self, f"_fluxcal_{source}", None) return if isinstance(fluxcal, pyfits.BinTableHDU): - setattr(self, f"_fluxcal_{source}", Table(fluxcal.data)) + setattr(self, f"_fluxcal_{source}", Table.read(fluxcal)) elif isinstance(fluxcal, Table): setattr(self, f"_fluxcal_{source}", fluxcal) else: @@ -3210,34 +3218,34 @@ def get_helio_rv(self, apply_hrv_corr=False): if ra == 0 or dec == 0: log.warning(f"on heliocentric velocity correction, missing RA/Dec information in header, assuming: {ra = }, {dec = }") self.add_header_comment(f"on heliocentric velocity correction, missing RA/Dec information in header, assuming: {ra = }, {dec = }") - self._header[f"HIERARCH WAVE HELIORV_{tel}"] = (numpy.round(0.0, 4), f"Heliocentric velocity correction for {tel} [km/s]") + self._header[f"HIERARCH WAVE HELIORV_{tel}"] = (numpy.round(0.0, 4), f"heliocentric vel. corr. for {tel} [km/s]") hrv_corrs[tel] = numpy.round(0.0, 4) else: radec = SkyCoord(ra, dec, unit="deg") # center of the pointing or coordinates of the fiber hrv_corr = radec.radial_velocity_correction(kind='heliocentric', obstime=obs_time, location=EarthLocation.of_site('lco')).to(u.km / u.s).value - self._header[f"HIERARCH WAVE HELIORV_{tel}"] = (numpy.round(hrv_corr, 4), f"Heliocentric velocity correction for {tel} [km/s]") + self._header[f"HIERARCH WAVE HELIORV_{tel}"] = (numpy.round(hrv_corr, 4), f"heliocentric vel. corr. for {tel} [km/s]") hrv_corrs[tel] = numpy.round(hrv_corr, 4) # calculate standard stars heliocentric corrections for istd in range(1, 15+1): is_acq = self._header.get(f"STD{istd}ACQ") if not is_acq or is_acq is None: - self._header[f"STD{istd}HRV"] = (0.0, f"Standard {istd} heliocentric vel. corr. [km/s]") + self._header[f"STD{istd}HRV"] = (0.0, f"standard {istd} heliocentric vel. corr. [km/s]") continue time_str = self._header.get(f"STD{istd}T0") if time_str is None: - self._header[f"STD{istd}HRV"] = (0.0, f"Standard {istd} heliocentric vel. corr. [km/s]") + self._header[f"STD{istd}HRV"] = (0.0, f"standard {istd} heliocentric vel. corr. [km/s]") continue std_obstime = Time(time_str) std_ra, std_dec = self._header.get(f"STD{istd}RA", 0.0), self._header.get(f"STD{istd}DE", 0.0) if std_ra == 0 or std_dec == 0: - self._header[f"STD{istd}HRV"] = (0.0, f"Standard {istd} heliocentric vel. corr. [km/s]") + self._header[f"STD{istd}HRV"] = (0.0, f"standard {istd} heliocentric vel. corr. [km/s]") continue std_radec = SkyCoord(std_ra, std_dec, unit="deg") std_hrv_corr = std_radec.radial_velocity_correction(kind="heliocentric", obstime=std_obstime, location=EarthLocation.of_site("lco")).to(u.km / u.s).value - self._header[f"STD{istd}HRV"] = (numpy.round(std_hrv_corr, 4), f"Standard {istd} heliocentric vel. corr. [km/s]") + self._header[f"STD{istd}HRV"] = (numpy.round(std_hrv_corr, 4), f"standard {istd} heliocentric vel. corr. [km/s]") if apply_hrv_corr: ... # if helio_vel is None or helio_vel == 0.0: @@ -3325,11 +3333,12 @@ def writeFitsData(self, out_rss, replace_masked=True, include_wave=False): hdus.append(pyfits.BinTableHDU(self._wave_trace, name="WAVE_TRACE")) elif self._wave is not None: if len(self._wave.shape) == 1: - # wcs = WCS( - # header={"CDELT1": self._wave_disp, "CRVAL1": self._wave_start, - # "CUNIT1": "Angstrom", "CTYPE1": "WAVE", "CRPIX1": 1.0}) - self._header.update({"CDELT1": self._wave_disp, "CRVAL1": self._wave_start, - "CUNIT1": "Angstrom", "CTYPE1": "WAVE", "CRPIX1": 1.0}) + self._header.update({ + "CDELT1": (self._wave_disp, "coordinate increment along axis"), + "CRVAL1": (self._wave_start, "coordinate system value at reference pixel"), + "CUNIT1": ("Angstrom", "physical unit of the coordinate axis"), + "CTYPE1": ("WAVE", "name of the coordinate axis"), + "CRPIX1": (1, "coordinate system reference pixel")}) elif len(self._wave.shape) == 2: hdus.append(pyfits.ImageHDU(self._wave.astype("float32"), name="WAVE")) else: @@ -3377,6 +3386,7 @@ def writeFitsData(self, out_rss, replace_masked=True, include_wave=False): os.makedirs(os.path.dirname(out_rss), exist_ok=True) hdus[0].header["FILENAME"] = os.path.basename(out_rss) hdus[0].header['DRPVER'] = drpver + hdus[0].scale(bzero=0, bscale=1) hdus.writeto(out_rss, overwrite=True, output_verify="silentfix") def loadRSS(in_rss): @@ -3387,7 +3397,7 @@ def loadRSS(in_rss): class lvmBaseProduct(RSS): """Base class to define an LVM product""" - _BPARS = {"BUNIT": None, "BSCALE": 1.0, "BZERO": 0.0} + _BPARS = {"BUNIT": None} @classmethod def header_from_hdulist(cls, hdulist): @@ -3405,24 +3415,17 @@ def set_header(self, orig_header, **kwargs): """Set header""" blueprint = dp.load_blueprint(name="lvmFrame") new_header = orig_header - new_cards = [] - for card in blueprint["hdu0"]["header"]: + for card in blueprint["hdu0"].get("header", []): kw = card["key"] cm = card["comment"] if kw.lower() in kwargs: - new_cards.append((kw, kwargs[kw.lower()], cm)) - new_header.update(new_cards) + new_header.append((kw, kwargs[kw.lower()], cm)) self._header = new_header return self._header def update_header(self): - # update flux header - for kw in ["BUNIT", "BSCALE", "BZERO"]: - if kw in self._header: - self._template["FLUX"].header[kw] = self._header.get(kw) - # update primary header - self._template["PRIMARY"].header.update(self._header) + self._template["PRIMARY"].header = self._header del self._template["PRIMARY"].header["WCS*"] del self._template["PRIMARY"].header["CDELT*"] del self._template["PRIMARY"].header["CRVAL*"] @@ -3453,7 +3456,7 @@ def from_hdulist(cls, hdulist): wave_trace = Table(hdulist["WAVE_TRACE"].data) lsf_trace = Table(hdulist["LSF_TRACE"].data) superflat = hdulist["SUPERFLAT"].data - slitmap = Table(hdulist["SLITMAP"].data) + slitmap = Table.read(hdulist["SLITMAP"]) return cls(data=data, error=error, mask=mask, header=header, wave_trace=wave_trace, lsf_trace=lsf_trace, cent_trace=cent_trace, width_trace=width_trace, @@ -3473,6 +3476,12 @@ def __init__(self, data=None, error=None, mask=None, if header is not None: self.set_header(header, **kwargs) + def set_units(self): + """Sets physical units in relevant extensions""" + flux_units = self._header.get("BUNIT") + self._template["FLUX"].header["BUNIT"] = flux_units + self._template["IVAR"].header["BUNIT"] = (1 / u.Unit(flux_units)**2).unit.to_string() + def get_superflat(self): """Get superflat representation as numpy array""" return self._superflat @@ -3493,6 +3502,7 @@ def writeFitsData(self, out_file, replace_masked=True): # update headers self.update_header() + self.set_units() # fill in rest of the template self._template["FLUX"].data = self._data self._template["IVAR"].data = numpy.divide(1, self._error**2, where=self._error != 0, out=numpy.zeros_like(self._error)) @@ -3508,6 +3518,7 @@ def writeFitsData(self, out_file, replace_masked=True): os.makedirs(os.path.dirname(out_file), exist_ok=True) self._template[0].header["FILENAME"] = os.path.basename(out_file) self._template[0].header['DRPVER'] = drpver + self._template[0].scale(bzero=0, bscale=1) self._template.writeto(out_file, overwrite=True) @@ -3530,9 +3541,9 @@ def from_hdulist(cls, hdulist): sky_west = hdulist["SKY_WEST"].data sky_west_error = numpy.divide(1, hdulist["SKY_WEST_IVAR"].data, where=hdulist["SKY_WEST_IVAR"].data != 0, out=numpy.zeros_like(hdulist["SKY_WEST_IVAR"].data)) sky_west_error = numpy.sqrt(sky_west_error) - fluxcal_std = Table(hdulist["FLUXCAL_STD"].data) - fluxcal_sci = Table(hdulist["FLUXCAL_SCI"].data) - slitmap = Table(hdulist["SLITMAP"].data) + fluxcal_std = Table.read(hdulist["FLUXCAL_STD"]) + fluxcal_sci = Table.read(hdulist["FLUXCAL_SCI"]) + slitmap = Table.read(hdulist["SLITMAP"]) return cls(data=data, error=error, mask=mask, header=header, wave=wave, lsf=lsf, sky_east=sky_east, sky_east_error=sky_east_error, @@ -3557,6 +3568,18 @@ def __init__(self, data=None, error=None, mask=None, header=None, wave=None, lsf else: self._header = None + def set_units(self): + """Sets physical units in relevant extensions""" + flux_units = self._header.get("BUNIT") + self._template["FLUX"].header["BUNIT"] = flux_units + self._template["IVAR"].header["BUNIT"] = (1 / u.Unit(flux_units)**2).unit.to_string() + self._template["WAVE"].header["BUNIT"] = "Angstrom" + self._template["LSF"].header["BUNIT"] = "Angstrom" + self._template["SKY_EAST"].header["BUNIT"] = flux_units + self._template["SKY_EAST_IVAR"].header["BUNIT"] = flux_units + self._template["SKY_WEST"].header["BUNIT"] = flux_units + self._template["SKY_WEST_IVAR"].header["BUNIT"] = flux_units + def loadFitsData(self, in_file): self = lvmFFrame.from_file(in_file) return self @@ -3568,6 +3591,7 @@ def writeFitsData(self, out_file, replace_masked=True): # update headers self.update_header() + self.set_units() # fill in rest of the template self._template["FLUX"].data = self._data self._template["IVAR"].data = numpy.divide(1, self._error**2, where=self._error != 0, out=numpy.zeros_like(self._error)) @@ -3586,6 +3610,7 @@ def writeFitsData(self, out_file, replace_masked=True): os.makedirs(os.path.dirname(out_file), exist_ok=True) self._template[0].header["FILENAME"] = os.path.basename(out_file) self._template[0].header['DRPVER'] = drpver + self._template[0].scale(bzero=0, bscale=1) self._template.writeto(out_file, overwrite=True) @@ -3608,7 +3633,7 @@ def from_hdulist(cls, hdulist): sky_west = hdulist["SKY_WEST"].data sky_west_error = numpy.divide(1, hdulist["SKY_WEST_IVAR"].data, where=hdulist["SKY_WEST_IVAR"].data != 0, out=numpy.zeros_like(hdulist["SKY_WEST_IVAR"].data)) sky_west_error = numpy.sqrt(sky_west_error) - slitmap = Table(hdulist["SLITMAP"].data) + slitmap = Table.read(hdulist["SLITMAP"]) return cls(data=data, error=error, mask=mask, header=header, wave=wave, lsf=lsf, sky_east=sky_east, sky_east_error=sky_east_error, @@ -3631,6 +3656,18 @@ def __init__(self, data=None, error=None, mask=None, header=None, slitmap=None, else: self._header = None + def set_units(self): + """Sets physical units in relevant extensions""" + flux_units = self._header.get("BUNIT") + self._template["FLUX"].header["BUNIT"] = flux_units + self._template["IVAR"].header["BUNIT"] = (1 / u.Unit(flux_units)**2).unit.to_string() + self._template["WAVE"].header["BUNIT"] = "Angstrom" + self._template["LSF"].header["BUNIT"] = "Angstrom" + self._template["SKY_EAST"].header["BUNIT"] = flux_units + self._template["SKY_EAST_IVAR"].header["BUNIT"] = (1 / u.Unit(flux_units)**2).unit.to_string() + self._template["SKY_WEST"].header["BUNIT"] = flux_units + self._template["SKY_WEST_IVAR"].header["BUNIT"] = (1 / u.Unit(flux_units)**2).unit.to_string() + def loadFitsData(self, in_file): self = lvmCFrame.from_file(in_file) return self @@ -3642,6 +3679,7 @@ def writeFitsData(self, out_file, replace_masked=True): # update headers self.update_header() + self.set_units() # fill in rest of the template self._template["FLUX"].data = self._data self._template["IVAR"].data = numpy.divide(1, self._error**2, where=self._error != 0, out=numpy.zeros_like(self._error)) @@ -3658,6 +3696,7 @@ def writeFitsData(self, out_file, replace_masked=True): os.makedirs(os.path.dirname(out_file), exist_ok=True) self._template[0].header["FILENAME"] = os.path.basename(out_file) self._template[0].header['DRPVER'] = drpver + self._template[0].scale(bzero=0, bscale=1) self._template.writeto(out_file, overwrite=True) @@ -3677,7 +3716,7 @@ def from_hdulist(cls, hdulist): sky = hdulist["SKY"].data sky_error = numpy.divide(1, hdulist["SKY_IVAR"].data, where=hdulist["SKY_IVAR"].data != 0, out=numpy.zeros_like(hdulist["SKY_IVAR"].data)) sky_error = numpy.sqrt(sky_error) - slitmap = Table(hdulist["SLITMAP"].data) + slitmap = Table.read(hdulist["SLITMAP"]) return cls(data=data, error=error, mask=mask, header=header, wave=wave, lsf=lsf, sky=sky, sky_error=sky_error, slitmap=slitmap) @@ -3694,6 +3733,16 @@ def __init__(self, data=None, error=None, mask=None, header=None, slitmap=None, else: self._header = None + def set_units(self): + """Sets physical units in relevant extensions""" + flux_units = self._header.get("BUNIT") + self._template["FLUX"].header["BUNIT"] = flux_units + self._template["IVAR"].header["BUNIT"] = (1 / u.Unit(flux_units)**2).unit.to_string() + self._template["WAVE"].header["BUNIT"] = "Angstrom" + self._template["LSF"].header["BUNIT"] = "Angstrom" + self._template["SKY"].header["BUNIT"] = flux_units + self._template["SKY_IVAR"].header["BUNIT"] = (1 / u.Unit(flux_units)**2).unit.to_string() + def loadFitsData(self, in_file): self = lvmSFrame.from_file(in_file) return self @@ -3705,6 +3754,7 @@ def writeFitsData(self, out_file, replace_masked=True): # update headers self.update_header() + self.set_units() # fill in rest of the template self._template["FLUX"].data = self._data self._template["IVAR"].data = numpy.divide(1, self._error**2, where=self._error != 0, out=numpy.zeros_like(self._error)) @@ -3719,6 +3769,7 @@ def writeFitsData(self, out_file, replace_masked=True): os.makedirs(os.path.dirname(out_file), exist_ok=True) self._template[0].header["FILENAME"] = os.path.basename(out_file) self._template[0].header['DRPVER'] = drpver + self._template[0].scale(bzero=0, bscale=1) self._template.writeto(out_file, overwrite=True) diff --git a/python/lvmdrp/core/sky.py b/python/lvmdrp/core/sky.py index 1ceecc65..5538b394 100644 --- a/python/lvmdrp/core/sky.py +++ b/python/lvmdrp/core/sky.py @@ -246,9 +246,9 @@ def skymodel_pars_header(header): skymodel_pars: dict output dict with header keywords, values and comments None/NAN/Null values replaced with -999 - + """ - + # extract useful header information sci_ra, sci_dec = header["TESCIRA"], header["TESCIDE"] skye_ra, skye_dec = header["TESKYERA"], header["TESKYEDE"] @@ -325,7 +325,7 @@ def skymodel_pars_header(header): # Moon illumination fraction moon_fli = m.fraction_illuminated(sun) - + # altitude of sun ('altsun', -90 -- 90) altsun, _, _ = s.altaz() @@ -387,37 +387,37 @@ def skymodel_pars_header(header): skymodel_pars = { - "HIERARCH SKYMODEL SM_H": (sm_h.to(u.km).value, "observatory height in km"), - "HIERARCH SKYMODEL SM_HMIN": ((2.0 * u.km).value, "lower height limit in km"), - "HIERARCH SKYMODEL SCI_ALT": (np.round(sci_alt.to(u.deg).value, 4), "altitude of object above horizon (deg)"), - "HIERARCH SKYMODEL SKYE_ALT": (np.round(skye_alt.to(u.deg).value, 4), "altitude of object above horizon (deg)"), - "HIERARCH SKYMODEL SKYW_ALT": (np.round(skyw_alt.to(u.deg).value, 4), "altitude of object above horizon (deg)"), - "HIERARCH SKYMODEL ALPHA": (np.round(alpha.to(u.deg).value, 4), "separation of Sun and Moon from Earth (deg)"), - "HIERARCH SKYMODEL SCI_RHO": (np.round(sci_rho.to(u.deg).value, 4), "separation of Moon and object (deg)"), - "HIERARCH SKYMODEL SKYE_RHO": (np.round(skye_rho.to(u.deg).value, 4), "separation of Moon and object (deg)"), - "HIERARCH SKYMODEL SKYW_RHO": (np.round(skyw_rho.to(u.deg).value, 4), "separation of Moon and object (deg)"), - "HIERARCH SKYMODEL MOONALT": (np.round(altmoon.to(u.deg).value, 4), "altitude of Moon above horizon (deg)"), - "HIERARCH SKYMODEL SUNALT": (np.round(altsun.to(u.deg).value, 4), "altitude of Sun above horizon (deg)"), + "HIERARCH SKYMODEL SM_H": (sm_h.to(u.km).value, "observatory height [km]"), + "HIERARCH SKYMODEL SM_HMIN": ((2.0 * u.km).value, "lower height limit [km]"), + "HIERARCH SKYMODEL SCI_ALT": (np.round(sci_alt.to(u.deg).value, 4), "altitude of object above horizon [deg]"), + "HIERARCH SKYMODEL SKYE_ALT": (np.round(skye_alt.to(u.deg).value, 4), "altitude of object above horizon [deg]"), + "HIERARCH SKYMODEL SKYW_ALT": (np.round(skyw_alt.to(u.deg).value, 4), "altitude of object above horizon [deg]"), + "HIERARCH SKYMODEL ALPHA": (np.round(alpha.to(u.deg).value, 4), "separation of Sun and Moon from Earth [deg]"), + "HIERARCH SKYMODEL SCI_RHO": (np.round(sci_rho.to(u.deg).value, 4), "separation of Moon and object [deg]"), + "HIERARCH SKYMODEL SKYE_RHO": (np.round(skye_rho.to(u.deg).value, 4), "separation of Moon and object [deg]"), + "HIERARCH SKYMODEL SKYW_RHO": (np.round(skyw_rho.to(u.deg).value, 4), "separation of Moon and object [deg]"), + "HIERARCH SKYMODEL MOONALT": (np.round(altmoon.to(u.deg).value, 4), "altitude of Moon above horizon [deg]"), + "HIERARCH SKYMODEL SUNALT": (np.round(altsun.to(u.deg).value, 4), "altitude of Sun above horizon [deg]"), "HIERARCH SKYMODEL MOONDIST": (np.round(moondist.value, 4), "distance to Moon (mean distance=1)"), - "HIERARCH SKYMODEL PRES": ((744 * u.hPa).value, "pressure at observer altitude (hPa), set: 744"), + "HIERARCH SKYMODEL PRES": ((744 * u.hPa).value, "pressure at observer altitude, set: 744 [hPa]"), "HIERARCH SKYMODEL SSA": (0.97, "aerosols' single scattering albedo, set: 0.97"), - "HIERARCH SKYMODEL CALCDS": ( "N", "cal double scattering of moon (Y or N)"), - "HIERARCH SKYMODEL O2COLUMN": (1.0, "relative ozone column density (1->258 DU)"), + "HIERARCH SKYMODEL CALCDS": ( "N", "cal double scattering of Moon (Y or N)"), + "HIERARCH SKYMODEL O2COLUMN": (1.0, "relative ozone column density (1->258) [DU]"), "HIERARCH SKYMODEL MOONSCAL": (1.0, "scaling factor for scattered moonlight"), - "HIERARCH SKYMODEL SCI_LON_ECL": (np.round(sci_lon_ecl.to(u.deg).value, 5), "heliocen ecliptic longitude (deg)"), - "HIERARCH SKYMODEL SCI_LAT_ECL": (np.round(sci_lat_ecl.to(u.deg).value, 5), "ecliptic latitude (deg)"), - "HIERARCH SKYMODEL SKYE_LON_ECL": (np.round(skye_lon_ecl.to(u.deg).value, 5), "heliocen ecliptic longitude (deg)"), - "HIERARCH SKYMODEL SKYE_LAT_ECL": (np.round(skye_lat_ecl.to(u.deg).value, 5), "ecliptic latitude (deg)"), - "HIERARCH SKYMODEL SKYW_LON_ECL": (np.round(skyw_lon_ecl.to(u.deg).value, 5), "heliocen ecliptic longitude (deg)"), - "HIERARCH SKYMODEL SKYW_LAT_ECL": (np.round(skyw_lat_ecl.to(u.deg).value, 5), "ecliptic latitude (deg)"), + "HIERARCH SKYMODEL SCI_LON_ECL": (np.round(sci_lon_ecl.to(u.deg).value, 5), "heliocen ecliptic longitude [deg]"), + "HIERARCH SKYMODEL SCI_LAT_ECL": (np.round(sci_lat_ecl.to(u.deg).value, 5), "ecliptic latitude [deg]"), + "HIERARCH SKYMODEL SKYE_LON_ECL": (np.round(skye_lon_ecl.to(u.deg).value, 5), "heliocen ecliptic longitude [deg]"), + "HIERARCH SKYMODEL SKYE_LAT_ECL": (np.round(skye_lat_ecl.to(u.deg).value, 5), "ecliptic latitude [deg]"), + "HIERARCH SKYMODEL SKYW_LON_ECL": (np.round(skyw_lon_ecl.to(u.deg).value, 5), "heliocen ecliptic longitude [deg]"), + "HIERARCH SKYMODEL SKYW_LAT_ECL": (np.round(skyw_lat_ecl.to(u.deg).value, 5), "ecliptic latitude [deg]"), "HIERARCH SKYMODEL EMIS_STR": (0.2, "grey-body emissivity"), - "HIERARCH SKYMODEL TEMP_STR": ((290.0 * u.K).value, "grey-body temperature (K)"), + "HIERARCH SKYMODEL TEMP_STR": ((290.0 * u.K).value, "grey-body temperature [K]"), "HIERARCH SKYMODEL MSOLFLUX": (130.0, "monthly-averaged solar radio flux, set: 130"), "HIERARCH SKYMODEL SEASON": (season, "bimonthly period (1:Dec/Jan, 6:Oct/Nov.; 0:year)"), "HIERARCH SKYMODEL TIME": (time, "period of night (x/3 night, x=1,2,3; 0:night)"), - "HIERARCH SKYMODEL MOON_RA": (np.round(moonra.to(u.deg).value, 5), "RA of the moon (deg)"), - "HIERARCH SKYMODEL MOON_DEC": (np.round(moondec.to(u.deg).value, 5), "DEC of the moon (deg)"), - "HIERARCH SKYMODEL MOON_PHASE": (np.round(moon_phase, 2), "Phase of moon (deg; 0=new, 90=1st qt)"), + "HIERARCH SKYMODEL MOON_RA": (np.round(moonra.to(u.deg).value, 5), "RA of the Moon [deg]"), + "HIERARCH SKYMODEL MOON_DEC": (np.round(moondec.to(u.deg).value, 5), "DEC of the Moon [deg]"), + "HIERARCH SKYMODEL MOON_PHASE": (np.round(moon_phase, 2), "phase of Moon (0=new, 90=1st qt) [deg]"), "HIERARCH SKYMODEL MOON_FLI": (np.round(moon_fli, 4), "Moon fraction lunar illumination") } diff --git a/python/lvmdrp/etc/dataproducts/calibration/lvmThroughput_bp.yaml b/python/lvmdrp/etc/dataproducts/calibration/lvmThroughput_bp.yaml index 9bf3faed..b6ddcb53 100644 --- a/python/lvmdrp/etc/dataproducts/calibration/lvmThroughput_bp.yaml +++ b/python/lvmdrp/etc/dataproducts/calibration/lvmThroughput_bp.yaml @@ -33,7 +33,7 @@ hdu1: name: IVAR type: float64 unit: null - description: associated inverse variance (1/sigma^2) of the above + description: associated inverse variance of the above MASK: name: MASK type: int32 diff --git a/python/lvmdrp/etc/dataproducts/lvmCFrame_bp.yaml b/python/lvmdrp/etc/dataproducts/lvmCFrame_bp.yaml index f7a99423..98ea3106 100644 --- a/python/lvmdrp/etc/dataproducts/lvmCFrame_bp.yaml +++ b/python/lvmdrp/etc/dataproducts/lvmCFrame_bp.yaml @@ -1,16 +1,16 @@ name: lvmCFrame short: the camera-combined science frames description: | - Per-exposure combinations of the lvmFrame files by stitching the blue, red, and nir channels + Per-exposure combinations of the lvmFFrame files by stitching the blue, red, and NIR channels together across the dichroic break, adding rows for fibers from spectrograph 2 and 3 atop those from spectrograph 1 (i.e., in order of fiberid). All spectra in this file have been resampled to a common wavelength grid for ALL LVM observations. Note that 'C' in the - name means Spectrograph Combined. + name means Spectrograph Channel Combined. location: $LVM_SPECTRO_REDUX/[DRPVER]/[TILEID]/[MJD]/ created_by: module.py used_by: module.py naming_convention: | - lvmCFrame-[CHANNEL]-[EXPNUM].fits.gz, where [CHANNEL] is the channel (brz) and [EXPNUM] is the (zero-padded) 8-digit exposure number. + lvmCFrame-[EXPNUM].fits.gz, where [EXPNUM] is the (zero-padded) 8-digit exposure number. hdu0: name: PRIMARY description: primary header metadata @@ -21,56 +21,73 @@ hdu0: comment: conforms to FITS standard hdu1: name: FLUX - description: Flux in flat fielded electrons / angstrom + description: calibrated flux in erg / (Angstrom s cm2) is_image: true + shape: NWAVE x NFIBER header: - key: BUNIT - comment: flux units - - key: BSCALE - comment: linear scaling factor - - key: BZERO - comment: zero point - shape: NWAVE x NFIBER + comment: physical units of the array values hdu2: name: IVAR - description: Inverse variance (1/sigma2) for the above + description: inverse variance in cm4 Angstrom2 s2 / erg2 is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu3: name: MASK - description: the quality bitmask for each fiber pixel + description: quality bitmask for each fiber pixel is_image: true shape: NWAVE x NFIBER hdu4: name: WAVE - description: air wavelength solution in angstroms + description: air wavelength solution in Angstrom is_image: true shape: NWAVE + header: + - key: BUNIT + comment: physical units of the array values hdu5: name: LSF - description: LSF (FWHM) solution in angstroms + description: LSF (FWHM) solution in Angstrom is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu6: name: SKY_EAST - description: sky east in flux-calibrated units (erg/s/cm2/Ang) + description: sky east in flux-calibrated units erg / (Angstrom s cm2) is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu7: name: SKY_EAST_IVAR - description: sky east inverse variance (1/sigma^2) in flux-calibrated units (erg/s/cm2/Ang) + description: sky east inverse variance in cm4 Angstrom2 s2 / erg2 is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu8: name: SKY_WEST - description: sky west in flux-calibrated units (erg/s/cm2/Ang) + description: sky west in flux-calibrated units erg / (Angstrom s cm2) is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu9: name: SKY_WEST_IVAR - description: sky west inverse variance (1/sigma^2) in flux-calibrated units (erg/s/cm2/Ang) + description: sky west inverse variance in cm4 Angstrom2 s2 / erg2 is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu10: name: SLITMAP description: slitmap table describing fiber positions for this exposure @@ -156,17 +173,17 @@ hdu10: name: ypix_b type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel b - unit: pixels + unit: pix YPIX_R: name: ypix_r type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel r - unit: pixels + unit: pix YPIX_Z: name: ypix_z type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel z - unit: pixels + unit: pix FIBSTATUS: name: fibstatus type: uint16 diff --git a/python/lvmdrp/etc/dataproducts/lvmFFrame_bp.yaml b/python/lvmdrp/etc/dataproducts/lvmFFrame_bp.yaml index 5f336072..cbafb1c6 100644 --- a/python/lvmdrp/etc/dataproducts/lvmFFrame_bp.yaml +++ b/python/lvmdrp/etc/dataproducts/lvmFFrame_bp.yaml @@ -18,207 +18,224 @@ hdu0: comment: conforms to FITS standard hdu1: name: FLUX - description: flux in units of erg/s/cm2/Ang + description: calibrated flux in erg / (Angstrom s cm2) is_image: true + shape: NWAVE x NFIBER header: - key: BUNIT - comment: flux units - - key: BSCALE - comment: linear scaling factor - - key: BZERO - comment: zero point - shape: NWAVE x NFIBER + comment: physical units of the array values hdu2: name: IVAR - description: Inverse variance (1/sigma2) for the above + description: inverse variance in cm4 Angstrom2 s2 / erg2 is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu3: name: MASK - description: the quality bitmask for each fiber pixel + description: quality bitmask for each fiber pixel is_image: true shape: NWAVE x NFIBER hdu4: name: WAVE - description: air wavelength solution trace in angstroms + description: air wavelength solution trace in Angstrom is_image: true shape: NWAVE + header: + - key: BUNIT + comment: physical units of the array values hdu5: name: LSF - description: LSF (FWHM) solution array in angstroms + description: LSF (FWHM) solution array in Angstrom is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu6: name: SKY_EAST - description: sky east in flux-calibrated units (10(-17) erg/s/cm2/Ang/fiber) + description: sky east in flux-calibrated units erg / (Angstrom s cm2) is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu7: name: SKY_EAST_IVAR - description: sky east inverse variance (1/sigma^2) in flux-calibrated units (erg/s/cm2/Ang) + description: sky east inverse variance in cm4 Angstrom2 s2 / erg2 is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu8: name: SKY_WEST - description: sky west in flux-calibrated units (erg/s/cm2/Ang) + description: sky west in flux-calibrated units erg / (Angstrom s cm2) is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu9: name: SKY_WEST_IVAR - description: sky west inverse variance (1/sigma^2) in flux-calibrated units (erg/s/cm2/Ang) + description: sky west inverse variance in cm4 Angstrom2 s2 / erg2 is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu10: name: FLUXCAL_STD - description: sensitivity in units of (electron/s/Ang) / (erg/s/cm2/Ang) from standard fibers + description: sensitivity from standard fibers in units of electron cm2 / erg is_image: false shape: BINARY FITS TABLE columns: STD1SEN: name: STD1SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 1 STD2SEN: name: STD2SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 2 STD3SEN: name: STD3SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 3 STD4SEN: name: STD4SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 4 STD5SEN: name: STD5SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 5 STD6SEN: name: STD6SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 6 STD7SEN: name: STD7SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 7 STD8SEN: name: STD8SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 8 STD9SEN: name: STD9SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 9 STD10SEN: name: STD10SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 10 STD11SEN: name: STD11SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 11 STD12SEN: name: STD12SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 12 MEAN_SENS: name: MEAN_SENS type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: mean sensitivity STDDEV_SENS: name: STDDEV_SENS type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard deviation of sensitivity hdu11: name: FLUXCAL_SCI - description: sensitivity in units of (electron/s/Ang) / (erg/s/cm2/Ang) from science fibers + description: sensitivity in units of electron cm2 / erg from science fibers is_image: false shape: BINARY FITS TABLE columns: STD1SEN: name: STD1SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 1 STD2SEN: name: STD2SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 2 STD3SEN: name: STD3SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 3 STD4SEN: name: STD4SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 4 STD5SEN: name: STD5SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 5 STD6SEN: name: STD6SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 6 STD7SEN: name: STD7SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 7 STD8SEN: name: STD8SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 8 STD9SEN: name: STD9SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 9 STD10SEN: name: STD10SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 10 STD11SEN: name: STD11SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 11 STD12SEN: name: STD12SEN type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard star 12 MEAN_SENS: name: MEAN_SENS type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: mean sensitivity STDDEV_SENS: name: STDDEV_SENS type: float32 - unit: (electron/s/Ang) / (erg/s/cm2/Ang) + unit: electron cm2 / erg description: standard deviation of sensitivity hdu12: name: SLITMAP @@ -305,17 +322,17 @@ hdu12: name: ypix_b type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel b - unit: pixels + unit: pix YPIX_R: name: ypix_r type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel r - unit: pixels + unit: pix YPIX_Z: name: ypix_z type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel z - unit: pixels + unit: pix FIBSTATUS: name: fibstatus type: uint16 diff --git a/python/lvmdrp/etc/dataproducts/lvmFrame_bp.yaml b/python/lvmdrp/etc/dataproducts/lvmFrame_bp.yaml index 0a7c3714..e7415669 100644 --- a/python/lvmdrp/etc/dataproducts/lvmFrame_bp.yaml +++ b/python/lvmdrp/etc/dataproducts/lvmFrame_bp.yaml @@ -13,38 +13,30 @@ hdu0: name: PRIMARY description: primary header metadata is_image: true - header: - - key: FLATNAME - comment: fiberflat used to flatfield the data - - key: IFIBVAR - comment: fiber-to-fiber variance before flatfielding - - key: FFIBVAR - comment: fiber-to-fiber variance after flatfielding hdu1: name: FLUX - description: Flux in flat fielded in (electron/angstrom) - header: - - key: BUNIT - comment: flux units - - key: BSCALE - comment: linear scaling factor - - key: BZERO - comment: zero point + description: flat fielded counts in electron / Angstrom is_image: true shape: CCDROW x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu2: name: IVAR - description: Inverse variance (1/sigma2) for the above + description: inverse variance in Angstrom2 / electron2 is_image: true shape: CCDROW x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu3: name: MASK - description: the quality bitmask for each fiber pixel + description: quality bitmask for each fiber pixel is_image: true shape: CCDROW x NFIBER hdu4: name: WAVE_TRACE - description: air wavelength solution trace in angstroms + description: air wavelength solution trace in Angstrom is_image: false shape: BINARY FITS TABLE columns: @@ -70,7 +62,7 @@ hdu4: description: polynomial coefficients hdu5: name: LSF_TRACE - description: LSF (FWHM) solution traceset in angstroms + description: LSF (FWHM) solution traceset in Angstrom is_image: false shape: BINARY FITS TABLE columns: @@ -148,7 +140,7 @@ hdu7: description: polynomial coefficients hdu8: name: SUPERFLAT - description: superflat vector from quartz lamps + description: superflat vector from LDLS/quartz lamps is_image: true shape: CCDROW x NFIBER hdu9: diff --git a/python/lvmdrp/etc/dataproducts/lvmRss_bp.yaml b/python/lvmdrp/etc/dataproducts/lvmRss_bp.yaml index c27d5d52..b33d3850 100644 --- a/python/lvmdrp/etc/dataproducts/lvmRss_bp.yaml +++ b/python/lvmdrp/etc/dataproducts/lvmRss_bp.yaml @@ -219,17 +219,17 @@ hdu6: name: ypix_b type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel b - unit: pixels + unit: pix YPIX_R: name: ypix_r type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel r - unit: pixels + unit: pix YPIX_Z: name: ypix_z type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel z - unit: pixels + unit: pix FIBSTATUS: name: fibstatus type: uint16 diff --git a/python/lvmdrp/etc/dataproducts/lvmSFrame_bp.yaml b/python/lvmdrp/etc/dataproducts/lvmSFrame_bp.yaml index f5bd5f20..bfae5cd0 100644 --- a/python/lvmdrp/etc/dataproducts/lvmSFrame_bp.yaml +++ b/python/lvmdrp/etc/dataproducts/lvmSFrame_bp.yaml @@ -18,44 +18,52 @@ hdu0: comment: conforms to FITS standard hdu1: name: FLUX - description: sky-subtracted flux in units of erg/s/cm2/Ang + description: sky-subtracted flux in units of erg / (Angstrom s cm2) is_image: true + shape: NWAVE x NFIBER header: - key: BUNIT - comment: flux units - - key: BSCALE - comment: linear scaling factor - - key: BZERO - comment: zero point - shape: NWAVE x NFIBER + comment: physical units of the array values hdu2: name: IVAR - description: inverse variance (1/sigma^2) of sky-subtracted flux + description: inverse variance in cm4 Angstrom2 s2 / erg2 is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu3: name: MASK - description: the quality bitmask for each fiber pixel + description: quality bitmask for each fiber pixel is_image: true shape: NWAVE x NFIBER hdu4: name: WAVE - description: air wavelength solution in angstroms + description: air wavelength solution in Angstrom is_image: true shape: NWAVE + header: + - key: BUNIT + comment: physical units of the array values hdu5: name: LSF - description: LSF (FWHM) solution in angstroms + description: LSF (FWHM) solution in Angstrom is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu6: name: SKY - description: sky in flux-calibrated units erg/s/cm2/Ang + description: sky in flux-calibrated units erg / (Angstrom s cm2) is_image: true shape: NWAVE x NFIBER + header: + - key: BUNIT + comment: physical units of the array values hdu7: name: SKY_IVAR - description: sky inverse variance (1/sigma^2) in flux-calibrated units (erg/s/cm2/Ang) + description: sky inverse variance in cm4 Angstrom2 s2 / erg2 is_image: true shape: NWAVE x NFIBER hdu8: @@ -143,17 +151,17 @@ hdu8: name: ypix_b type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel b - unit: pixels + unit: pix YPIX_R: name: ypix_r type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel r - unit: pixels + unit: pix YPIX_Z: name: ypix_z type: uint32 description: the y coordinate in pixels of the fiber at column 2000 for channel z - unit: pixels + unit: pix FIBSTATUS: name: fibstatus type: uint16 diff --git a/python/lvmdrp/functions/fluxCalMethod.py b/python/lvmdrp/functions/fluxCalMethod.py index e3829496..8ce3d285 100644 --- a/python/lvmdrp/functions/fluxCalMethod.py +++ b/python/lvmdrp/functions/fluxCalMethod.py @@ -11,6 +11,7 @@ import numpy as np from scipy import interpolate +from astropy import units as u from astropy.stats import biweight_location, biweight_scale from astropy.table import Table @@ -133,8 +134,8 @@ def apply_fluxcal(in_rss: str, out_fframe: str, method: str = 'STD', display_plo if method != 'NONE': # update the fluxcal extension - fframe._fluxcal_std["mean"] = sens_ave - fframe._fluxcal_std["rms"] = sens_rms + fframe._fluxcal_std["mean"] = sens_ave * u.Unit("erg / (ct cm2)") + fframe._fluxcal_std["rms"] = sens_rms * u.Unit("erg / (ct cm2)") ax.set_title(f"flux calibration for {channel = } with {method = }", loc="left") for j in range(sens_arr.shape[1]): @@ -176,7 +177,7 @@ def apply_fluxcal(in_rss: str, out_fframe: str, method: str = 'STD', display_plo if fframe._sky_west_error is not None: fframe._sky_west_error /= exptimes[:, None] fframe.setHdrValue("FLUXCAL", 'NONE', "flux-calibration method") - fframe.setHdrValue("BUNIT", "electron/s/A", "flux units") + fframe.setHdrValue("BUNIT", "electron / (Angstrom s)", "physical units of the array values") else: log.info("flux-calibrating data science and sky spectra") fframe._data *= sens_ave * 10 ** (0.4 * ext * (sci_secz)) / exptimes[:, None] @@ -193,7 +194,7 @@ def apply_fluxcal(in_rss: str, out_fframe: str, method: str = 'STD', display_plo fframe._sky_west *= sens_ave * 10 ** (0.4 * ext * (sci_secz)) / exptimes[:, None] if fframe._sky_west_error is not None: fframe._sky_west_error *= sens_ave * 10 ** (0.4 * ext * (sci_secz)) / exptimes[:, None] - fframe.setHdrValue("BUNIT", "ergs/s/cm^2/A", "flux units") + fframe.setHdrValue("BUNIT", "erg / (Angstrom s cm2)", "physical units of the array values") log.info(f"writing output file in {os.path.basename(out_fframe)}") fframe.writeFitsData(out_fframe) @@ -258,7 +259,7 @@ def standard_sensitivity(stds, rss, GAIA_CACHE_DIR, ext, res, plot=False, width= sens = stdflux / spec wgood, sgood = fluxcal.filter_channel(w, sens, 2) s = interpolate.make_smoothing_spline(wgood, sgood, lam=1e4) - res[f"STD{nn}SEN"] = s(w).astype(np.float32) + res[f"STD{nn}SEN"] = s(w).astype(np.float32) * u.Unit("erg / (ct cm2)") # caluculate SDSS g band magnitudes for QC mAB_std = np.round(fluxcal.spec_to_LVM_mAB(channel, w, stdflux), 2) @@ -266,7 +267,7 @@ def standard_sensitivity(stds, rss, GAIA_CACHE_DIR, ext, res, plot=False, width= # update input file header label = channel.upper() rss.setHdrValue(f"STD{nn}{label}AB", mAB_std, f"Gaia AB mag in {channel}-band") - rss.setHdrValue(f"STD{nn}{label}IN", mAB_obs, f"Obs AB mag in {channel}-band") + rss.setHdrValue(f"STD{nn}{label}IN", mAB_obs, f"obs AB mag in {channel}-band") log.info(f"AB mag in LVM_{channel}: Gaia {mAB_std:.2f}, instrumental {mAB_obs:.2f}") if plot: @@ -363,19 +364,19 @@ def science_sensitivity(rss, res_sci, ext, GAIA_CACHE_DIR, NSCI_MAX=15, r_spaxel # calculate the normalization of the average (known) sensitivity curve in a broad band lvmflux = fluxcal.spec_to_LVM_flux(channel, obswave, obsflux) sens = fluxcal.spec_to_LVM_flux(channel, gwave, gflux) / lvmflux - res_sci[f"STD{i+1}SEN"] = (sens*np.interp(obswave, mean_sens[channel]['wavelength'], - mean_sens[channel]['sens'])).astype(np.float32) + sens *= np.interp(obswave, mean_sens[channel]['wavelength'], mean_sens[channel]['sens']) + res_sci[f"STD{i+1}SEN"] = sens.astype(np.float32) * u.Unit("erg / (ct cm2)") mAB_std = np.round(fluxcal.spec_to_LVM_mAB(channel, gwave, gflux), 2) mAB_obs = np.round(fluxcal.spec_to_LVM_mAB(channel, obswave, obsflux), 2) # update input file header cam = channel.upper() rss.setHdrValue(f"SCI{i+1}{cam}AB", mAB_std, f"Gaia AB mag in {channel}-band") - rss.setHdrValue(f"SCI{i+1}{cam}IN", mAB_obs, f"Obs AB mag in {channel}-band") - rss.setHdrValue(f"SCI{i+1}ID", data['source_id'], f"Field star {i+1} Gaia source ID") - rss.setHdrValue(f"SCI{i+1}FIB", scifibs['fiberid'][fib][0], f"Field star {i+1} fiber id") - rss.setHdrValue(f"SCI{i+1}RA", data['ra'], f"Field star {i+1} RA") - rss.setHdrValue(f"SCI{i+1}DE", data['dec'], f"Field star {i+1} DEC") + rss.setHdrValue(f"SCI{i+1}{cam}IN", mAB_obs, f"obs AB mag in {channel}-band") + rss.setHdrValue(f"SCI{i+1}ID", data['source_id'], f"field star {i+1} Gaia source ID") + rss.setHdrValue(f"SCI{i+1}FIB", scifibs['fiberid'][fib][0], f"field star {i+1} fiber id") + rss.setHdrValue(f"SCI{i+1}RA", data['ra'], f"field star {i+1} RA") + rss.setHdrValue(f"SCI{i+1}DE", data['dec'], f"field star {i+1} DEC") log.info(f"AB mag in LVM_{channel}: Gaia {mAB_std:.2f}, instrumental {mAB_obs:.2f}") # calibrate and plot against the stars for debugging: @@ -410,7 +411,7 @@ def fluxcal_standard_stars(in_rss, plot=True, GAIA_CACHE_DIR=None): if len(colnames) == 0: NSTD = 15 colnames = [f"STD{i}SEN" for i in range(1, NSTD + 1)] - res_std = Table(np.full(w.size, np.nan, dtype=list(zip(colnames, ["f8"] * len(colnames))))) + res_std = Table(np.full(w.size, np.nan, dtype=list(zip(colnames, ["f8"] * len(colnames)))), units=[u.Unit("erg / (ct cm2)")]*len(colnames)) mean_std, rms_std = np.full(w.size, np.nan), np.full(w.size, np.nan) # load extinction curve @@ -460,8 +461,8 @@ def fluxcal_standard_stars(in_rss, plot=True, GAIA_CACHE_DIR=None): label = rss._header['CCD'] channel = label.lower() - rss.setHdrValue(f"STDSENM{label}", np.nanmean(mean_std[1000:3000]), f"Mean stdstar sensitivity in {channel}") - rss.setHdrValue(f"STDSENR{label}", np.nanmean(rms_std[1000:3000]), f"Mean stdstar sensitivity rms in {channel}") + rss.setHdrValue(f"STDSENM{label}", np.nanmean(mean_std[1000:3000]), f"mean stdstar sensitivity in {channel}") + rss.setHdrValue(f"STDSENR{label}", np.nanmean(rms_std[1000:3000]), f"mean stdstar sensitivity rms in {channel}") log.info(f"Mean stdstar sensitivity in {channel} : {np.nanmean(mean_std[1000:3000])}") if plot: @@ -510,7 +511,7 @@ def fluxcal_sci_ifu_stars(in_rss, plot=True, GAIA_CACHE_DIR=None, NSCI_MAX=15): # define dummy sensitivity array in (ergs/s/cm^2/A) / (e-/s/A) for standard star fibers colnames = [f"STD{i}SEN" for i in range(1, NSCI_MAX + 1)] - res_sci = Table(np.full(w.size, np.nan, dtype=list(zip(colnames, ["f8"] * len(colnames))))) + res_sci = Table(np.full(w.size, np.nan, dtype=list(zip(colnames, ["f8"] * len(colnames)))), units=[u.Unit("erg / (ct cm2)")]*len(colnames)) mean_sci, rms_sci = np.full(w.size, np.nan), np.full(w.size, np.nan) # load extinction curve @@ -536,8 +537,8 @@ def fluxcal_sci_ifu_stars(in_rss, plot=True, GAIA_CACHE_DIR=None, NSCI_MAX=15): rms_sci_band = np.nanmean(rms_sci[1000:3000]) mean_sci_band = -999.9 if np.isnan(mean_sci_band) else mean_sci_band rms_sci_band = -999.9 if np.isnan(rms_sci_band) else rms_sci_band - rss.setHdrValue(f"SCISENM{label}", mean_sci_band, f"Mean scistar sensitivity in {channel}") - rss.setHdrValue(f"SCISENR{label}", rms_sci_band, f"Mean scistar sensitivity rms in {channel}") + rss.setHdrValue(f"SCISENM{label}", mean_sci_band, f"mean scistar sensitivity in {channel}") + rss.setHdrValue(f"SCISENR{label}", rms_sci_band, f"mean scistar sensitivity rms in {channel}") log.info(f"Mean scistar sensitivity in {channel} : {mean_sci_band}") if plot: diff --git a/python/lvmdrp/functions/imageMethod.py b/python/lvmdrp/functions/imageMethod.py index 19793641..3aa8e9c2 100644 --- a/python/lvmdrp/functions/imageMethod.py +++ b/python/lvmdrp/functions/imageMethod.py @@ -18,6 +18,7 @@ from astropy.io import fits as pyfits from astropy.visualization import simple_norm from astropy.wcs import wcs +from astropy import units as u import astropy.io.fits as fits from scipy import interpolate from scipy import signal @@ -2730,56 +2731,22 @@ def extract_spectra( ) rss.setHdrValue("NAXIS2", data.shape[0]) rss.setHdrValue("NAXIS1", data.shape[1]) - rss.setHdrValue("DISPAXIS", 1) + rss.setHdrValue("DISPAXIS", 1, "axis of spectral dispersion") + rss.setHdrValue(f"HIERARCH {camera.upper()} FIBER CENT_MIN", numpy.round(bn.nanmin(trace_mask._data),4), "min. fiber centroid [pix]") + rss.setHdrValue(f"HIERARCH {camera.upper()} FIBER CENT_MAX", numpy.round(bn.nanmax(trace_mask._data),4), "max. fiber centroid [pix]") + rss.setHdrValue(f"HIERARCH {camera.upper()} FIBER CENT_AVG", numpy.round(bn.nanmean(trace_mask._data),4), "avg. fiber centroid [pix]") + rss.setHdrValue(f"HIERARCH {camera.upper()} FIBER CENT_STD", numpy.round(bn.nanstd(trace_mask._data),4), "stddev. fiber centroid [pix]") + if method == "optimal": + rss.setHdrValue(f"HIERARCH {camera.upper()} FIBER WIDTH_MIN", numpy.round(bn.nanmin(trace_fwhm._data),4), "min. fiber width [pix]") + rss.setHdrValue(f"HIERARCH {camera.upper()} FIBER WIDTH_MAX", numpy.round(bn.nanmax(trace_fwhm._data),4), "max. fiber width [pix]") + rss.setHdrValue(f"HIERARCH {camera.upper()} FIBER WIDTH_AVG", numpy.round(bn.nanmean(trace_fwhm._data),4), "avg. fiber width [pix]") + rss.setHdrValue(f"HIERARCH {camera.upper()} FIBER WIDTH_STD", numpy.round(bn.nanstd(trace_fwhm._data),4), "stddev. fiber width [pix]") + rss.add_header_comment(f"{in_trace}, fiber centroids used for {camera}") rss.add_header_comment(f"{in_fwhm}, fiber width (FWHM) used for {camera}") rss.add_header_comment(f"{in_model}, fiber model used for {camera}") rss.add_header_comment(f"{in_acorr}, fiber aperture correction used for {camera}") - rss.setHdrValue( - "HIERARCH FIBER CENT MIN", - bn.nanmin(trace_mask._data), - ) - rss.setHdrValue( - "HIERARCH FIBER CENT MAX", - bn.nanmax(trace_mask._data), - ) - rss.setHdrValue( - "HIERARCH FIBER CENT AVG", - bn.nanmean(trace_mask._data) if data.size != 0 else 0, - ) - rss.setHdrValue( - "HIERARCH FIBER CENT MED", - bn.nanmedian(trace_mask._data) - if data.size != 0 - else 0, - ) - rss.setHdrValue( - "HIERARCH FIBER CENT SIG", - bn.nanstd(trace_mask._data) if data.size != 0 else 0, - ) - if method == "optimal": - rss.setHdrValue( - "HIERARCH FIBER WIDTH MIN", - bn.nanmin(trace_fwhm._data), - ) - rss.setHdrValue( - "HIERARCH FIBER WIDTH MAX", - bn.nanmax(trace_fwhm._data), - ) - rss.setHdrValue( - "HIERARCH FIBER WIDTH AVG", - bn.nanmean(trace_fwhm._data) if data.size != 0 else 0, - ) - rss.setHdrValue( - "HIERARCH FIBER WIDTH MED", - bn.nanmedian(trace_fwhm._data) - if data.size != 0 - else 0, - ) - rss.setHdrValue( - "HIERARCH FIBER WIDTH SIG", - bn.nanstd(trace_fwhm._data) if data.size != 0 else 0, - ) + # save extracted RSS log.info(f"writing extracted spectra to {os.path.basename(out_rss)}") rss.writeFitsData(out_rss) @@ -3224,6 +3191,7 @@ def preproc_raw_frame( gain[2] *= 1.089 if camera == "z3": gain[3] /= 1.056 + gain = numpy.round(gain, 2) log.info(f"using header GAIN = {gain.tolist()} (e-/ADU)") @@ -3262,8 +3230,8 @@ def preproc_raw_frame( os_models.append(os_model) # compute overscan stats - os_bias_med[i] = bn.nanmedian(os_quad._data, axis=None) - os_bias_std[i] = bn.nanmedian(bn.nanstd(os_quad._data, axis=1), axis=None) + os_bias_med[i] = numpy.round(bn.nanmedian(os_quad._data, axis=None), 2) + os_bias_std[i] = numpy.round(bn.nanmedian(bn.nanstd(os_quad._data, axis=1), axis=None), 2) log.info( f"median and standard deviation in OS quadrant {i+1}: " f"{os_bias_med[i]:.2f} +/- {os_bias_std[i]:.2f} (ADU)" @@ -3313,37 +3281,37 @@ def preproc_raw_frame( ysize, xsize = sc_quads[i]._dim x, y = int(QUAD_POSITIONS[i][0]), int(QUAD_POSITIONS[i][1]) proc_img.setHdrValue( - f"HIERARCH AMP{i+1} TRIMSEC", + f"HIERARCH {camera.upper()} AMP{i+1} TRIMSEC", f"[{x*xsize+1}:{xsize*(x+1)}, {y*ysize+1}:{ysize*(y+1)}]", - f"Region of amp. {i+1}", + f"region of amp. {i+1}", ) # add gain keywords for the different subimages (CCDs/Amplifiers) for i in range(NQUADS): proc_img.setHdrValue( - f"HIERARCH AMP{i+1} {gain_prefix}", + f"HIERARCH {camera.upper()} AMP{i+1} {gain_prefix}", gain[i], - f"Gain value of amp. {i+1} [electron/adu]", + f"gain value of amp. {i+1} [electron/adu]", ) # add read-out noise keywords for the different subimages (CCDs/Amplifiers) for i in range(NQUADS): proc_img.setHdrValue( - f"HIERARCH AMP{i+1} {rdnoise_prefix}", + f"HIERARCH {camera.upper()} AMP{i+1} {rdnoise_prefix}", rdnoise[i], - f"Read-out noise of amp. {i+1} [electron]", + f"read-out noise of amp. {i+1} [electron]", ) # add bias of overscan region for the different subimages (CCDs/Amplifiers) for i in range(NQUADS): proc_img.setHdrValue( - f"HIERARCH AMP{i+1} OVERSCAN", + f"HIERARCH {camera.upper()} AMP{i+1} OVERSCAN_MED", os_bias_med[i], - f"Overscan median of amp. {i+1} [adu]", + f"overscan median of amp. {i+1} [adu]", ) # add bias std of overscan region for the different subimages (CCDs/Amplifiers) for i in range(NQUADS): proc_img.setHdrValue( - f"HIERARCH AMP{i+1} OVERSCAN_STD", + f"HIERARCH {camera.upper()} AMP{i+1} OVERSCAN_STD", os_bias_std[i], - f"Overscan std of amp. {i+1} [adu]", + f"overscan std of amp. {i+1} [adu]", ) # load master pixel mask @@ -3575,7 +3543,7 @@ def getobsparam(tel): except AttributeError: RAobs=0 DECobs=0 - org_img.setHdrValue('ASTRMSRC', 'GDR coadd', comment='Source of astrometric solution: guider') + org_img.setHdrValue('ASTRMSRC', 'GDR coadd', comment='source of astrometry: guider') copy_guider_keyword(mfheader, 'FRAME0 ', org_img) copy_guider_keyword(mfheader, 'FRAMEN ', org_img) copy_guider_keyword(mfheader, 'NFRAMES ', org_img) @@ -3610,7 +3578,7 @@ def getobsparam(tel): if numpy.any([RAobs, DECobs, PAobs]) == 0: log.warning(f"some astrometry keywords for telescope '{tel}' are missing: {RAobs = }, {DECobs = }, {PAobs = }") org_img.add_header_comment(f"no astromentry keywords '{tel}': {RAobs = }, {DECobs = }, {PAobs = }, using commanded") - org_img.setHdrValue('ASTRMSRC', 'CMD position', comment='Source of astrometric solution: commanded position') + org_img.setHdrValue('ASTRMSRC', 'CMD position', comment='source of astrometry: commanded position') else: RAobs=0 DECobs=0 @@ -3655,21 +3623,19 @@ def getfibradec(tel, platescale): getfibradec('spec', platescale=FIDUCIAL_PLATESCALE) # add coordinates to slitmap - slitmap['ra']=RAfib - slitmap['dec']=DECfib + slitmap['ra']=RAfib * u.deg + slitmap['dec']=DECfib * u.deg org_img._slitmap=slitmap # set header keyword with best knowledge of IFU center - org_img.setHdrValue('IFUCENRA', RAobs_sci, 'Best SCI IFU RA (ASTRMSRC)') - org_img.setHdrValue('IFUCENDE', DECobs_sci, 'Best SCI IFU DEC (ASTRMSRC)') - org_img.setHdrValue('IFUCENPA', PAobs_sci, 'Best SCI IFU PA (ASTRMSRC)') + org_img.setHdrValue('IFUCENRA', RAobs_sci, 'best SCI IFU RA (ASTRMSRC)') + org_img.setHdrValue('IFUCENDE', DECobs_sci, 'best SCI IFU DEC (ASTRMSRC)') + org_img.setHdrValue('IFUCENPA', PAobs_sci, 'best SCI IFU PA (ASTRMSRC)') log.info(f"writing RA,DEC to slitmap in image '{os.path.basename(out_image)}'") org_img.writeFitsData(out_image) - - @skip_on_missing_input_path(["in_image"]) # @skip_if_drpqual_flags(["SATURATED"], "in_image") def detrend_frame( @@ -3790,13 +3756,13 @@ def detrend_frame( if convert_to_e: # calculate Poisson errors log.info("applying gain correction per quadrant") - for i, quad_sec in enumerate(bcorr_img.getHdrValue("AMP? TRIMSEC").values()): + for i, quad_sec in enumerate(bcorr_img.getHdrValue(f"{camera.upper()} AMP? TRIMSEC").values()): log.info(f"processing quadrant {i+1}: {quad_sec}") # extract quadrant image quad = bcorr_img.getSection(quad_sec) # extract quadrant gain and rdnoise values - gain = quad.getHdrValue(f"AMP{i+1} GAIN") - rdnoise = quad.getHdrValue(f"AMP{i+1} RDNOISE") + gain = quad.getHdrValue(f"{camera.upper()} AMP{i+1} GAIN") + rdnoise = quad.getHdrValue(f"{camera.upper()} AMP{i+1} RDNOISE") # non-linearity correction gain_map = _nonlinearity_correction(ptc_params, gain, quad, iquad=i+1) @@ -3809,11 +3775,11 @@ def detrend_frame( bcorr_img.setSection(section=quad_sec, subimg=quad, inplace=True) log.info(f"median error in quadrant {i+1}: {bn.nanmedian(quad._error):.2f} (e-)") - bcorr_img.setHdrValue("BUNIT", "electron", "physical units of the image") + bcorr_img.setHdrValue("BUNIT", "electron", "physical units of the array values") else: # convert to ADU log.info("leaving original ADU units") - bcorr_img.setHdrValue("BUNIT", "adu", "physical units of the image") + bcorr_img.setHdrValue("BUNIT", "adu", "physical units of the array values") # complete image detrending if in_dark: @@ -3835,7 +3801,7 @@ def detrend_frame( # reject cosmic rays if reject_cr: log.info("rejecting cosmic rays") - rdnoise = detrended_img.getHdrValue("AMP1 RDNOISE") + rdnoise = detrended_img.getHdrValue(f"{camera.upper()} AMP1 RDNOISE") detrended_img.reject_cosmics(gain=1.0, rdnoise=rdnoise, rlim=1.3, iterations=5, fwhm_gauss=[2.75, 2.75], replace_box=[10,2], replace_error=1e6, verbose=True, inplace=True) @@ -4189,7 +4155,8 @@ def create_pixelmask(in_short_dark, in_long_dark, out_pixmask, in_flat_a=None, i ratio_dark = short_dark / long_dark # define quadrant sections - sections = short_dark.getHdrValue("AMP? TRIMSEC") + camera = short_dark.getHdrValue("CCD") + sections = short_dark.getHdrValue(f"{camera.upper()} AMP? TRIMSEC") # define pixelmask image pixmask = Image(data=numpy.zeros_like(short_dark._data), mask=numpy.zeros_like(short_dark._data, dtype="bool")) diff --git a/python/lvmdrp/functions/rssMethod.py b/python/lvmdrp/functions/rssMethod.py index b7ba82fc..fdeb8a0b 100644 --- a/python/lvmdrp/functions/rssMethod.py +++ b/python/lvmdrp/functions/rssMethod.py @@ -798,9 +798,9 @@ def shift_wave_skylines(in_frame: str, out_frame: str, dwave: float = 8.0, skyli meanoffset = numpy.nan_to_num(meanoffset) log.info(f'Applying the offsets [Angstroms] in [1,2,3] spectrographs with means: {meanoffset}') lvmframe._wave_trace['COEFF'].data[:,0] -= fiber_offset_mod - lvmframe._header[f'HIERARCH WAVE SKYOFF_{channel.upper()}1'] = (meanoffset[0], f'Mean sky line offset in {channel}1 [Angs]') - lvmframe._header[f'HIERARCH WAVE SKYOFF_{channel.upper()}2'] = (meanoffset[1], f'Mean sky line offset in {channel}2 [Angs]') - lvmframe._header[f'HIERARCH WAVE SKYOFF_{channel.upper()}3'] = (meanoffset[2], f'Mean sky line offset in {channel}3 [Angs]') + lvmframe._header[f'HIERARCH {channel.upper()}1 WAVE SKYOFF'] = (meanoffset[0], f'avg. sky line offset in {channel}1 [Angstrom]') + lvmframe._header[f'HIERARCH {channel.upper()}2 WAVE SKYOFF'] = (meanoffset[1], f'avg. sky line offset in {channel}2 [Angstrom]') + lvmframe._header[f'HIERARCH {channel.upper()}3 WAVE SKYOFF'] = (meanoffset[2], f'avg. sky line offset in {channel}3 [Angstrom]') wave_trace = TraceMask.from_coeff_table(lvmframe._wave_trace) lvmframe._wave = wave_trace.eval_coeffs() @@ -1638,9 +1638,6 @@ def apply_fiberflat(in_rss: str, out_frame: str, in_flat: str, clip_below: float log.info(f"reading target data from {os.path.basename(in_rss)}") rss = RSS.from_file(in_rss) - # compute initial variance - ifibvar = bn.nanmean(bn.nanvar(rss._data, axis=0)) - # load fiberflat log.info(f"reading fiberflat from {os.path.basename(in_flat)}") flat = RSS.from_file(in_flat) @@ -1680,9 +1677,6 @@ def apply_fiberflat(in_rss: str, out_frame: str, in_flat: str, clip_below: float spec_new = spec_data / spec_flat._data rss.setSpec(i, spec_new) - # compute final variance - ffibvar = bn.nanmean(bn.nanvar(rss._data, axis=0)) - # load ancillary data log.info(f"writing lvmFrame to {os.path.basename(out_frame)}") @@ -1698,7 +1692,7 @@ def apply_fiberflat(in_rss: str, out_frame: str, in_flat: str, clip_below: float slitmap=rss._slitmap, superflat=flat._data ) - lvmframe.set_header(orig_header=rss._header, flatname=os.path.basename(in_flat), ifibvar=ifibvar, ffibvar=ffibvar) + lvmframe.set_header(orig_header=rss._header, flatname=os.path.basename(in_flat)) lvmframe.writeFitsData(out_frame) return rss, lvmframe @@ -3200,7 +3194,7 @@ def quickQuality( # compute statistics quads_avg, quads_std, quads_pct = [], [], [] - for section in bias_img._header["AMP? TRIMSEC"]: + for section in bias_img._header[f"{camera.upper()} AMP? TRIMSEC"]: quad = bias_img.getSection(section) quads_avg.append(numpy.mean(quad._data)) quads_std.append(numpy.std(quad._data)) diff --git a/python/lvmdrp/functions/skyMethod.py b/python/lvmdrp/functions/skyMethod.py index d1ec84f5..374cc634 100644 --- a/python/lvmdrp/functions/skyMethod.py +++ b/python/lvmdrp/functions/skyMethod.py @@ -1444,13 +1444,13 @@ def interpolate_sky( in_frame: str, out_rss: str = None, display_plots: bool = F # TODO: add same parameters for std *fibers* new_rss._header["HIERARCH GEOCORONAL SKYW_SH_HGHT"] = ( - np.round(get_telescope_shadowheight(new_rss._header, telescope="SKYW"), 5), "height of Earth's shadow (km)" + np.round(get_telescope_shadowheight(new_rss._header, telescope="SKYW"), 5), "height of Earth's shadow [km]" ) new_rss._header["HIERARCH GEOCORONAL SKYE_SH_HGHT"] = ( - np.round(get_telescope_shadowheight(new_rss._header, telescope="SKYE"), 5), "height of Earth's shadow (km)" + np.round(get_telescope_shadowheight(new_rss._header, telescope="SKYE"), 5), "height of Earth's shadow [km]" ) new_rss._header["HIERARCH GEOCORONAL SCI_SH_HGHT"] = ( - np.round(get_telescope_shadowheight(new_rss._header, telescope="SCI"), 5), "height of Earth's shadow (km)" + np.round(get_telescope_shadowheight(new_rss._header, telescope="SCI"), 5), "height of Earth's shadow [km]" ) # write output RSS @@ -1541,8 +1541,6 @@ def combine_skies(in_rss: str, out_rss, sky_weights: Tuple[float, float] = None) # write output sky-subtracted RSS log.info(f"writing output RSS file '{os.path.basename(out_rss)}'") - rss.appendHeader(sky_e._header["SKYMODEL*"]) - rss.appendHeader(sky_e._header["GEOCORONAL*"]) rss.setHdrValue("SKYEW", w_e, "SkyE weight for STD star sky subtraction") rss.setHdrValue("SKYWW", w_w, "SkyW weight for STD star sky subtraction") rss.set_sky(sky_east=sky_e._data, sky_east_error=sky_e._error, @@ -1608,7 +1606,7 @@ def quick_sky_subtraction(in_cframe, out_sframe, skymethod: str = 'farlines_near log.info(f"loading {in_cframe} for sky subtraction") cframe = lvmCFrame.from_file(in_cframe) - + # read sky table hdu sky_hdu = prep_input_simplesky_mean(in_cframe) diff --git a/python/lvmdrp/main.py b/python/lvmdrp/main.py index 31b398a5..1d5b2c1e 100644 --- a/python/lvmdrp/main.py +++ b/python/lvmdrp/main.py @@ -866,7 +866,7 @@ def build_supersky(tileid: int, mjd: int, expnum: int, imagetype: str) -> fits.B dlambda = np.diff(fsci._wave, axis=1, append=2*(fsci._wave[:, -1] - fsci._wave[:, -2])[:, None]) fsci._data /= dlambda fsci._error /= dlambda - fsci._header["BUNIT"] = "electron/angstrom" + fsci._header["BUNIT"] = "electron / Angstrom" # sky fiber selection slitmap = fsci._slitmap[fsci._slitmap["spectrographid"] == specid] @@ -885,7 +885,7 @@ def build_supersky(tileid: int, mjd: int, expnum: int, imagetype: str) -> fits.B spec.extend([specid] * (nsam_e + nsam_w)) telescope.extend(["east"] * nsam_e + ["west"] * nsam_w) sort_idx = np.argsort(sky_wave) - wave_c = fits.Column(name="wave", array=np.array(sky_wave)[sort_idx], unit="angstrom", format="E") + wave_c = fits.Column(name="wave", array=np.array(sky_wave)[sort_idx], unit="Angstrom", format="E") sky_c = fits.Column(name="sky", array=np.array(sky)[sort_idx], unit=fsci._header["BUNIT"], format="E") sky_error_c = fits.Column(name="sky_error", array=np.array(sky_error)[sort_idx], unit=fsci._header["BUNIT"], format="E") fiberidx_c = fits.Column(name="fiberidx", array=np.array(fiberidx)[sort_idx], format="J") @@ -1074,12 +1074,19 @@ def read_fibermap(as_table: bool = None, as_hdu: bool = None, with open(p, 'r') as f: data = yaml.load(f, Loader=yaml.CSafeLoader) cols = [i['name'] for i in data['schema']] - df = pd.DataFrame(data['fibers'], columns=cols) + units = [u.Unit(i['unit']) if i['unit'] is not None else None for i in data['schema']] + + # define dtypes for Table and Numpy arrays because these two can't seem to talk to each other + tb_dtypes = [i['dtype'] for i in data['schema']] + np_dtypes = list(zip(cols, [d if d != 'str' else 'object' for d in tb_dtypes])) + + # create table with units and correct types + table = Table(np.asarray([tuple(d) for d in data['fibers']], dtype=np_dtypes), units=units, dtype=tb_dtypes) if as_table: - return Table.from_pandas(df) + return table if as_hdu: - return fits.BinTableHDU(Table.from_pandas(df), name='SLITMAP') - return df + return fits.BinTableHDU(table, name='SLITMAP') + return table.to_pandas() fibermap = read_fibermap(as_hdu=True)