Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve fits primary header when writing out spectrum1d without wcs #1105

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions specutils/io/default_loaders/mef_tabular_fits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# a hack around tabular-fits that supports putting header info in the primary hdu

from astropy.wcs import WCS
from specutils import Spectrum1D
import astropy.units as u
from astropy.io import fits
import numpy as np
from astropy.table import Table

def mef_tabular_fits_writer(spectrum, file_name='my_func_output.fits', hdu=0, update_header=False, **kwargs):

# no matter what the dimensionality of 'flux', there will always be a primary header and
# a single BinTableHDU extension. the `hdu` arg controls where `meta.header` is dumped.
hdulist = [fits.PrimaryHDU(), None]
pri_header_initial = hdulist[0].header

Check warning on line 15 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L14-L15

Added lines #L14 - L15 were not covered by tests

if hdu > 1:
raise ValueError('`hdu`, which controls which extension `meta.header` is written to, must either be 0 or 1')

Check warning on line 18 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L17-L18

Added lines #L17 - L18 were not covered by tests

# we expect anything that should be written out to the file header to be in `meta.header`
# This should be a `fits.header.Header` object, so convert to if meta.header is a dictionary
header = spectrum.meta.get('header', fits.header.Header()) # if no meta.header, create empty `fits.header.Header`

Check warning on line 22 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L22

Added line #L22 was not covered by tests

if hdu == 0:
header.update(pri_header_initial) # update with initial values created from primary header

Check warning on line 25 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L24-L25

Added lines #L24 - L25 were not covered by tests

if not isinstance(header, fits.header.Header):
if isinstance(header, dict):
header = fits.header.Header(header)

Check warning on line 29 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L27-L29

Added lines #L27 - L29 were not covered by tests
else:
raise ValueError('`Spectrum1d.meta.header must be `fits.header.Header` or dictionary.')

Check warning on line 31 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L31

Added line #L31 was not covered by tests

if update_header:
hdr_types = (str, int, float, complex, bool,

Check warning on line 34 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L33-L34

Added lines #L33 - L34 were not covered by tests
np.floating, np.integer, np.complexfloating, np.bool_)
header.update([keyword for keyword in spectrum.meta.items() if

Check warning on line 36 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L36

Added line #L36 was not covered by tests
isinstance(keyword[1], hdr_types)])

# Strip header of FITS reserved keywords
for keyword in ['NAXIS', 'NAXIS1', 'NAXIS2']:
header.remove(keyword, ignore_missing=True)

Check warning on line 41 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L40-L41

Added lines #L40 - L41 were not covered by tests

# Add dispersion array and unit
wtype = kwargs.pop('wtype', spectrum.spectral_axis.dtype)
wunit = u.Unit(kwargs.pop('wunit', spectrum.spectral_axis.unit))

Check warning on line 45 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L44-L45

Added lines #L44 - L45 were not covered by tests

disp = spectrum.spectral_axis.to(wunit, equivalencies=u.spectral())

Check warning on line 47 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L47

Added line #L47 was not covered by tests

# Mapping of spectral_axis types to header TTYPE1 (no "torque/work" types!)
dispname = str(wunit.physical_type)
if dispname == "length":
dispname = "wavelength"
elif "energy" in dispname:
dispname = "energy"

Check warning on line 54 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L50-L54

Added lines #L50 - L54 were not covered by tests

# Add flux array and unit
ftype = kwargs.pop('ftype', spectrum.flux.dtype)
funit = u.Unit(kwargs.pop('funit', spectrum.flux.unit))
flux = spectrum.flux.to(funit, equivalencies=u.spectral_density(disp))

Check warning on line 59 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L57-L59

Added lines #L57 - L59 were not covered by tests

columns = [disp.astype(wtype), flux.astype(ftype)]
colnames = [dispname, "flux"]

Check warning on line 62 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L61-L62

Added lines #L61 - L62 were not covered by tests

# Include uncertainty - units to be inferred from spectrum.flux
if spectrum.uncertainty is not None:
try:
unc = (

Check warning on line 67 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L65-L67

Added lines #L65 - L67 were not covered by tests
spectrum
.uncertainty
.represent_as(StdDevUncertainty)
.quantity
.to(funit, equivalencies=u.spectral_density(disp))
)
columns.append(unc.astype(ftype))
colnames.append("uncertainty")
except RuntimeWarning:
raise ValueError("Could not convert uncertainty to StdDevUncertainty due"

Check warning on line 77 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L74-L77

Added lines #L74 - L77 were not covered by tests
" to divide-by-zero error.")

# For > 1D data transpose from row-major format
for c in range(1, len(columns)):
if columns[c].ndim > 1:
columns[c] = columns[c].T

Check warning on line 83 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L81-L83

Added lines #L81 - L83 were not covered by tests

tab = Table(columns, names=colnames)
hdulist[1] = fits.BinTableHDU(tab)

Check warning on line 86 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L85-L86

Added lines #L85 - L86 were not covered by tests

# now figure out where meta.header (and if specified, addl meta keys) should go
if hdu==0:
print('here')
hdulist[0].header.update(header)

Check warning on line 91 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L89-L91

Added lines #L89 - L91 were not covered by tests
else: # must otherwise be 1, we already checked this
hdulist[1].header.update(header)

Check warning on line 93 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L93

Added line #L93 was not covered by tests

hdulist = fits.HDUList(hdulist)

Check warning on line 95 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L95

Added line #L95 was not covered by tests

hdulist.writeto(file_name, overwrite=True)

Check warning on line 97 in specutils/io/default_loaders/mef_tabular_fits.py

View check run for this annotation

Codecov / codecov/patch

specutils/io/default_loaders/mef_tabular_fits.py#L97

Added line #L97 was not covered by tests
Loading