"""
`~sunpy.map.Map` subclass for the EUV Imaging Spectrometer (EIS) on Hinode
"""
import pathlib
import numpy as np
import astropy.units as u
from astropy.visualization import ImageNormalize, AsinhStretch, LinearStretch
import sunpy.map
from sunpy.map.mapbase import SpatialPair
from sunpy.time import parse_time
__all__ = ['EISMap']
[docs]
class EISMap(sunpy.map.GenericMap):
"""
EIS fit parameter map.
The EUV Imaging Spectrometer (EIS) is part of the Hinode mission and was sponsored
by the Japan Aerospace Exploration Agency (JAXA), the United Kingdom Space
Agency (UKSA), and National Aeronautics and Space Administration (NASA) with
contributions from ESA and Norway. Hinode was launched on September 22, 2006
at 21:36 UTC from the Uchinoura Space Center in Japan and continues to
operate. EIS observes two wavelength ranges in the extreme ultraviolet,
171—212 Å and 245—291 Å with a spectral resolution of about 22 mÅ and a plate
scale of 100 per pixel.
This data structure is designed to hold the fit parameters derived from multi-gaussian
spectral fits to level 1, wavelength-resolved EIS rasters. These maps can contain the
intensity, doppler velocity, or line width.
Notes
-----
Measurement errors are stored in a binary table. To load them correctly,
you must pass the .fits file directly to `eispac.EISMap` instead of using
sunpy.map.Map
References
----------
* `Instrument Paper: Culhane, J. L., Harra, L. K., James, A. M., et al.
2007, Sol. Phys., 243, 19`_
"""
def __init__(self, data, header=None, **kwargs):
# Check for a fits file containing a binary table with the errs
# NB: errs are in a table with y-axis number of rows, each with x-axis
# number of values
if isinstance(data, (str, pathlib.Path)):
if pathlib.Path(data).suffix.lower().startswith('.fit'):
import sunpy.io.fits
hdu_list = sunpy.io.fits.read(data)
data = hdu_list[0][0]
header = hdu_list[0][1]
if len(hdu_list) >= 2:
rec_errs = hdu_list[1][0]
ny = rec_errs.shape[0] # y-axis
nx = rec_errs[0][0].shape[0] # x-axis
errs = rec_errs['errors'].view(type=np.ndarray).reshape(ny,nx)
kwargs['uncertainty'] = errs
super().__init__(data, header, **kwargs)
# Setup plot settings
self.plot_settings['aspect'] = self.meta['cdelt2'] / self.meta['cdelt1']
# Adjust colormap and normalization depending on whether the map contains
# intensity, velocity, or line width data
if self.meta['measrmnt'].lower().startswith('int'):
self.plot_settings['cmap'] = 'Blues_r'
self.plot_settings['norm'] = ImageNormalize(stretch=AsinhStretch())
elif self.meta['measrmnt'].lower().startswith('vel'):
self.plot_settings['cmap'] = 'RdBu_r'
# Autoscale color range to 3*std (rounded to nearest multiple of 5)
vlim = 5*round(3*self.data.std()/5)
self.plot_settings['norm'] = ImageNormalize(vmin=-vlim, vmax=vlim)
elif self.meta['measrmnt'].lower().startswith('wid'):
self.plot_settings['cmap'] = 'viridis'
@property
def spatial_units(self):
units = self.meta.get('cunit1', 'arcsec'), self.meta.get('cunit2', 'arcsec')
return SpatialPair(u.Unit(units[0]), u.Unit(units[1]))
@property
def processing_level(self):
return self.meta.get('lvl_num', 3)
@property
def waveunit(self):
return u.Unit(self.meta.get('waveunit', 'angstrom'))
@property
def wavelength(self):
line_id = self.meta.get('line_id')
if line_id is not None:
wave = float(line_id.split()[-1])
return u.Quantity(wave, self.waveunit)
@property
def measurement(self):
return self.meta.get('measrmnt', '')
@property
def observatory(self):
return 'Hinode'
@property
def nickname(self):
line_id = self.meta.get('line_id', '')
return f'{self.observatory} {self.instrument} {line_id}'
@property
def date_start(self):
# Try default key DATE-BEG. This is to future proof against
# switching to DATE-BEG when constructing the L1 headers
# NOTE: the DATE_OBS key is the beginning of the observation
# so we can use this in case DATE_BEG is missing
date_beg = self._get_date('date_beg') or super().date_start
date_beg = date_beg or self._date_obs
return date_beg
@property
def date_end(self):
# Try default key DATE-END. This is to future proof against
# switching to DATE-END when constructing the L1 headers
return self._get_date('date_end') or super().date_end
@property
def date_average(self):
return self._get_date('date_avg') or super().date_average
@property
def date(self):
# NOTE: we override this property to prioritize date_average
# over DATE-OBS (or DATE_OBS). In GenericMap, this is reversed.
# We do this because we want to make sure we are constructing our
# coordinate frames from DATE_AVG (the midpoint of the raster) and
# not DATE-OBS which is the beginning of the raster.
return self.date_average or super().date
[docs]
@classmethod
def is_datasource_for(cls, data, header, **kwargs):
"""
Determines if header corresponds to an EIS image. Used to register
EISMap with the sunpy.map.Map factory.
"""
return str(header.get('instrume', '')).startswith('EIS')