Source code for madcubapy.io.spectracontainer
import astropy
from astropy.table import Table
import astropy.units as u
import numpy as np
import os
from pathlib import Path
import zipfile
from .madcubafits import MadcubaFits
from madcubapy.utils.spectral import create_spectral_array
__all__ = [
'SpectraContainer',
'parse_row_spectral_axis',
]
[docs]
class SpectraContainer(MadcubaFits):
"""
A container for MADCUBA spectra, using the
`~madcubapy.io.madcubafits.MadcubaFits` interface.
This class is basically a wrapper to read MADCUBA exported spectra and
their history files with astropy.
Parameters
----------
bintable : `~astropy.table.Table`
Table containing the data of every spectra inside the *.spec* file
alongside the info of their headers.
hist : `~astropy.table.Table`
Table containing the history information of the FITS file, which is
stored in a separate *_hist.csv* file.
filename : `~str`
Name of the *.spec* file.
Methods
-------
add_hist(*args)
Load the history table from a csv file.
"""
def __init__(
self,
bintable=None,
hist=None,
filename=None,
):
# inherit hist
super().__init__(hist)
if bintable is not None and not isinstance(bintable, Table):
raise TypeError(
"The bintable must be an astropy Table")
self._bintable = bintable
if filename is not None and not isinstance(filename, str):
raise TypeError("The filename must be a string.")
self._filename = filename
@property
def bintable(self):
"""
`~astropy.table.Table` : Table containing the data of every spectra
inside the *.spec* file.
"""
return self._bintable
@bintable.setter
def bintable(self, value):
if value is not None and not isinstance(value, Table):
raise TypeError(
"The bintable must be an astropy Table")
self._bintable = value
@property
def filename(self):
"""
`~str` : Name of the *.spec* file.
"""
return self._filename
@filename.setter
def filename(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("The filename must be a string.")
self._filename = value
[docs]
@classmethod
def read(cls, filepath):
"""
``Classmethod`` to generate a
`~madcubapy.io.spectracontainer.SpectraContainer` from a *.spec* file.
Parameters
----------
filepath : `~str`
Name of spec file.
"""
spec_filepath = filepath
# Check if the spec file exists
if not os.path.isfile(spec_filepath):
raise FileNotFoundError(f"File {spec_filepath} not found.")
# Open the ZIP file
with zipfile.ZipFile(spec_filepath, "r") as zip_file:
fits_files = [
name for name in zip_file.namelist() if name.endswith(".fits")
]
with zip_file.open(fits_files[0]) as internal_fits_file:
bintable = Table.read(internal_fits_file, hdu=1)
hist_files = [
name for name in zip_file.namelist() if name.endswith("_hist.csv")
]
with zip_file.open(hist_files[0]) as internal_hist_file:
hist = Table.read(internal_hist_file, format='csv')
filename_terms = str(filepath).split('/')
filename = filename_terms[-1]
# Return an instance of MadcubaFits
spectra_container = cls(
bintable=bintable,
hist=hist,
filename=filename,
)
spectra_container._generate_spectral_axes()
spectra_container._parse_data_units()
return spectra_container
[docs]
def copy(self):
"""
Create a copy of the `~madcubapy.io.SpectraContainer`.
"""
if self._bintable:
new_bintable = self._bintable.copy()
else:
new_bintable = None
if self._hist:
new_hist = self._hist.copy()
else:
new_hist = None
return SpectraContainer(
bintable=new_bintable,
hist=new_hist,
)
def _generate_spectral_axes(self):
"""
Generate arrays for the spectral axes of every spectra inside the
`~madcubapy.io.SpectraContainer` and add them as a new table column.
"""
data = []
for i in range(len(self.bintable)):
spectral_axis = parse_row_spectral_axis(self.bintable[i])
if isinstance(data, u.Quantity):
data.append(spectral_axis.value)
else: data.append(spectral_axis)
self.bintable['XAXIS'] = data
self.bintable['XAXIS'].unit = self.bintable['RESTFRQ'].unit
def _parse_data_units(self):
"""
Parse the BUNIT column values. If all are equal, set it as the unit for
the DATA column.
"""
if np.all(self.bintable["BUNIT"] == self.bintable["BUNIT"][0]):
unit_code = self.bintable["BUNIT"][0]
try:
unit = astropy.units.Unit(unit_code)
except ValueError:
print("Unit string for data could not be parsed")
unit = unit_code
finally:
self.bintable["DATA"].unit = unit
else: self.bintable["DATA"].unit = None
def __repr__(self):
# If hist is None, display that it's missing
if self._hist is None:
hist_repr = "hist=None"
# If hist is present, display a summary of the table
else:
hist_repr = (
f"hist=<Table length={len(self._hist)} rows, "
+ f"{len(self._hist.columns)} columns>"
)
if self._bintable is None:
bintable_repr = "bintable=None"
# If hist is present, display a summary of the table
else:
bintable_repr = (
f"bintable=<Table length={len(self._hist)} rows, "
+ f"{len(self._hist.columns)} columns>"
)
return f"<SpectraContainer({bintable_repr}, {hist_repr})>"
[docs]
def parse_row_spectral_axis(table_row):
"""
Generate an array for the spectral axis of a spectrum inside a
`~madcubapy.io.SpectraContainer`.
Parameters
----------
table_row : `astropy.table.Row`
Row of a `~madcubapy.io.SpectraContainer`'s bintable. This is the data
for a spectrum inside a MADCUBA FITS file.
Returns
-------
spectral_array : `~numpy.ndarray` or `~astropy.units.Quantity`
Returned spectral axis array with units if correctly parsed from the
FITS file.
"""
# Get spectrum data
nchan = table_row['CHANNELS']
cdelt3 = table_row['CDELT3']
crval3 = table_row['CRVAL3']
crpix3 = table_row['CRPIX3'] - 1 # fits to numpy
if isinstance(table_row.table['RESTFRQ'].unit, astropy.units.UnitBase):
spectral_unit = table_row.table['RESTFRQ'].unit
else:
spectral_unit = None
spectral_array = create_spectral_array(nchan,cdelt3,crpix3,crval3)
if spectral_unit:
spectral_array = spectral_array * spectral_unit
return spectral_array