from pathlib import Path
from itertools import product
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.colors import LogNorm
from matplotlib.dates import ConciseDateFormatter, DateFormatter, HourLocator
from matplotlib.widgets import Slider
import astropy.units as u
from astropy.table import QTable, vstack
from astropy.time import Time
from astropy.visualization import quantity_support
from sunpy.time.timerange import TimeRange
from sunpy.util import deprecated
from stixpy.io.readers import read_subc_params
from stixpy.product.product import L1Product
__all__ = [
"ScienceData",
"RawPixelData",
"CompressedPixelData",
"SummedCompressedPixelData",
"Visibility",
"Spectrogram",
"TimesSeriesPlotMixin",
"SpectrogramPlotMixin",
"PixelPlotMixin",
"PPrintMixin",
"IndexMasks",
"DetectorMasks",
"PixelMasks",
"EnergyEdgeMasks",
]
from stixpy.visualisation.plotters import PixelPlotter
quantity_support()
SubCollimatorConfig = read_subc_params(
Path(__file__).parent.parent.parent / "config" / "data" / "detector" / "stx_subc_params.csv"
)
[docs]
class PPrintMixin:
"""
Provides pretty printing for index masks.
"""
@staticmethod
def _pprint_indices(indices):
groups = np.split(np.r_[: len(indices)], np.where(np.diff(indices) != 1)[0] + 1)
out = ""
for group in groups:
if group.size < 3:
out += f"{indices[group]}"
else:
out += f"[{indices[group[0]]}...{indices[group[-1]]}]"
return out
[docs]
class IndexMasks(PPrintMixin):
"""
Index mask class to store masked indices.
Attributes
----------
masks : `numpy.ndarray`
The mask arrays
indices : `numpy.ndarray`
The indices the mask/s applies to
"""
def __init__(self, mask_array):
masks = np.unique(mask_array, axis=0)
indices = [np.argwhere(np.all(mask_array == mask, axis=1)).reshape(-1) for mask in masks]
self.masks = masks
self.indices = indices
def __repr__(self):
text = f"{self.__class__.__name__}\n"
for m, i in zip(self.masks, self.indices):
text += (
f" {self._pprint_indices(i)}: [{','.join(np.where(m, np.arange(m.size), np.full(m.size, '_')))}]\n"
)
return text
[docs]
class DetectorMasks(IndexMasks):
"""
Detector Index Masks
"""
pass
[docs]
class EnergyEdgeMasks(IndexMasks):
"""
Energy Edges Mask
"""
@property
def energy_mask(self):
"""
Return mask of energy channels from mask of energy edges.
Returns
-------
`np.array`
"""
energy_bin_mask = (self.masks & np.roll(self.masks, 1))[0, 1:]
indices = np.where(energy_bin_mask == 1)
energy_bin_mask[indices[0][0] : indices[0][-1] + 1] = 1
return energy_bin_mask
[docs]
class PixelMasks(PPrintMixin):
"""
Pixel Index Masks
"""
def __init__(self, pixel_masks):
masks = np.unique(pixel_masks, axis=0)
indices = []
if masks.ndim == 2:
indices = [np.argwhere(np.all(pixel_masks == mask, axis=1)).reshape(-1) for mask in masks]
elif masks.ndim == 3:
indices = [np.argwhere(np.all(pixel_masks == mask, axis=(1, 2))).reshape(-1) for mask in masks]
self.masks = masks
self.indices = indices
def __repr__(self):
text = f"{self.__class__.__name__}\n"
for m, i in zip(self.masks, self.indices):
text += f" {self._pprint_indices(i)}: [{str(np.where(m.shape[0], m, np.full(m.shape, '_')))}]\n"
return text
[docs]
class SpectrogramPlotMixin:
"""
Spectrogram plot mixin providing spectrogram plotting for pixel data.
"""
[docs]
def plot_spectrogram(
self,
axes=None,
vtype="dcr",
time_indices=None,
energy_indices=None,
detector_indices="all",
pixel_indices="all",
**plot_kwargs,
):
"""
Plot a spectrogram for the selected time and energies.
Parameters
----------
axes : optional `matplotlib.axes`
The axes the plot the spectrogram.
vtype : str
Type of value to return control the default normalisation:
* 'c' - count [c]
* 'cr' - count rate [c/s]
* 'dcr' - differential count rate [c/(s keV)]
time_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `time_indices=[0, 2, 5]` would return only the first, third and
sixth times while `time_indices=[[0, 2],[3, 5]]` would sum the data between.
pixel_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `pixel_indices=[0, 2, 5]` would return only the first, third and
sixth pixels while `pixel_indices=[[0, 2],[3, 5]]` would sum the data between.
detector_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `detector_indices=[0, 2, 5]` would return only the first, third and
sixth detectors while `detector_indices=[[0, 2],[3, 5]]` would sum the data between.
energy_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `energy_indices=[0, 2, 5]` would return only the first, third and
sixth times while `energy_indices=[[0, 2],[3, 5]]` would sum the data between.
**plot_kwargs : `dict`
Any additional arguments are passed to :meth:`~matplotlib.axes.Axes.pcolormesh`.
Returns
-------
`matplotlib.axes`
Notes
-----
The units of the plotted data are determined by the `vtype` parameter:
- 'c': counts
- 'cr': counts per second
- 'dcr': counts per second per keV
"""
if axes is None:
fig, axes = plt.subplots()
counts_shape = self.data["counts"].shape
if len(counts_shape) != 4:
# if spectrogram can't do anything with pixel or detector indices
if detector_indices != "all" or pixel_indices != "all":
raise ValueError("Detector and or pixel indices have can not be used with spectrogram")
pid = None
did = None
else:
if detector_indices == "all":
did = [[0, 31]]
else:
det_idx_arr = np.array(detector_indices)
if det_idx_arr.ndim == 1 and det_idx_arr.size != 1:
raise ValueError(
"Spectrogram plots can only show data from a single "
"detector or summed over a number of detectors"
)
elif det_idx_arr.ndim == 2 and det_idx_arr.shape[0] != 1:
raise ValueError("Spectrogram plots can only one sum detector or summed over a number of detectors")
did = detector_indices
if pixel_indices == "all":
pid = [[0, 11]]
else:
pix_idx_arr = np.array(pixel_indices)
if pix_idx_arr.ndim == 1 and pix_idx_arr.size != 1:
raise ValueError(
"Spectrogram plots can only show data from a single "
"detector or summed over a number of detectors"
)
elif pix_idx_arr.ndim == 2 and pix_idx_arr.shape[0] != 1:
raise ValueError("Spectrogram plots can only one sum detector or summed over a number of detectors")
pid = pixel_indices
counts, errors, times, timedeltas, energies = self.get_data(
vtype=vtype,
detector_indices=did,
pixel_indices=pid,
time_indices=time_indices,
energy_indices=energy_indices,
)
timedeltas = timedeltas.to(u.s)
e_edges = np.hstack([energies["e_low"], energies["e_high"][-1]]).value
t_edges = Time(
np.concatenate([times - timedeltas.reshape(-1) / 2, times[-1] + timedeltas.reshape(-1)[-1:] / 2])
)
pcolor_kwargs = {"norm": LogNorm(), "shading": "flat"}
pcolor_kwargs.update(plot_kwargs)
im = axes.pcolormesh(t_edges.datetime, e_edges[1:-1], counts[:, 0, 0, 1:-1].T.value, **pcolor_kwargs) # noqa
# axes.colorbar(im).set_label(format(counts.unit))
axes.xaxis_date()
# axes.set_yticks(range(y_lims[0], y_lims[1] + 1))
# axes.set_yticklabels(labels)
minor_loc = HourLocator()
axes.xaxis.set_minor_locator(minor_loc)
axes.xaxis.set_major_formatter(DateFormatter("%d %H:%M"))
# fig.autofmt_xdate()
# fig.tight_layout()
for i in plt.get_fignums():
if axes in plt.figure(i).axes:
plt.sca(axes)
plt.sci(im)
return im
[docs]
class TimesSeriesPlotMixin:
"""
TimesSeries plot mixin providing timeseries plotting for pixel data.
"""
[docs]
def plot_timeseries(
self,
vtype="dcr",
time_indices=None,
energy_indices=None,
detector_indices="all",
pixel_indices="all",
axes=None,
error_bar=False,
**plot_kwarg,
):
"""
Plot a times series of the selected times and energies.
Parameters
----------
vtype : str
Type of value to return control the default normalisation:
* 'c' - count [c]
* 'cr' - count rate [c/s]
* 'dcr' - differential count rate [c/(s keV)]
time_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `time_indices=[0, 2, 5]` would return only the first, third and
sixth times while `time_indices=[[0, 2],[3, 5]]` would sum the data between.
energy_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `energy_indices=[0, 2, 5]` would return only the first, third and
sixth times while `energy_indices=[[0, 2],[3, 5]]` would sum the data between.
detector_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `detector_indices=[0, 2, 5]` would return only the first, third and
sixth detectors while `detector_indices=[[0, 2],[3, 5]]` would sum the data between.
pixel_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `pixel_indices=[0, 2, 5]` would return only the first, third and
sixth pixels while `pixel_indices=[[0, 2],[3, 5]]` would sum the data between.
axes : optional `matplotlib.axes`
The matplotlib axes on which to plot the time series.
error_bar : optional `bool`
Add error bars to plot.
**plot_kwargs : `dict`
Any additional arguments are passed to :meth:`~matplotlib.axes.Axes.plot`.
Returns
-------
`matplotlib.axes`
"""
if axes is None:
fig, axes = plt.subplots()
if detector_indices == "all":
detector_indices = [[0, 31]]
if pixel_indices == "all":
pixel_indices = [[0, 11]]
counts, errors, times, timedeltas, energies = self.get_data(
vtype=vtype,
detector_indices=detector_indices,
pixel_indices=pixel_indices,
time_indices=time_indices,
energy_indices=energy_indices,
)
labels = [f"{el.value} - {eh.value} keV" for el, eh in energies["e_low", "e_high"]]
n_time, n_det, n_pix, n_energy = counts.shape
for did, pid, eid in product(range(n_det), range(n_pix), range(n_energy)):
if error_bar:
lines = axes.errorbar(
times.to_datetime(),
counts[:, did, pid, eid],
yerr=errors[:, did, pid, eid],
label=labels[eid],
**plot_kwarg,
)
else:
lines = axes.plot(times.to_datetime(), counts[:, did, pid, eid], label=labels[eid], **plot_kwarg)
axes.set_yscale("log")
axes.xaxis.set_major_formatter(ConciseDateFormatter(axes.xaxis.get_major_locator()))
return lines
[docs]
class PixelPlotMixin:
"""
Pixel plot mixin providing pixel plotting for pixel data.
"""
[docs]
def plot_pixels(self, *, kind="pixel", time_indices=None, energy_indices=None, fig=None, cmap=None, **kwargs):
pixel_plotter = PixelPlotter(self, time_indices=time_indices, energy_indices=energy_indices)
pixel_plotter.plot(kind=kind, fig=fig, cmap=cmap, **kwargs)
return pixel_plotter
[docs]
class ScienceData(L1Product):
"""
Basic science data class
"""
def __init__(self, *, meta, control, data, energies, idb_versions=None):
"""
Parameters
----------
meta : `astropy.fits.Header`
Fits header
control : `astropy.table.QTable`
Fits file control extension
data :` astropy.table.QTable`
Fits file data extension
energies : `astropy.table.QTable`
Fits file energy extension
"""
super().__init__(meta=meta, control=control, data=data, energies=energies, idb_versions=idb_versions)
self.count_type = "rate"
if "detector_masks" in self.data.colnames:
self.detector_masks = DetectorMasks(self.data["detector_masks"])
if "pixel_masks" in self.data.colnames:
self.pixel_masks = PixelMasks(self.data["pixel_masks"])
if "energy_bin_edge_mask" in self.control.colnames:
self.energy_masks = EnergyEdgeMasks(self.control["energy_bin_edge_mask"])
self.dE = energies["e_high"] - energies["e_low"]
@property
def time_range(self):
"""
A `sunpy.time.TimeRange` for the data.
"""
return TimeRange(
self.data["time"][0] - self.data["timedel"][0] / 2, self.data["time"][-1] + self.data["timedel"][-1] / 2
)
@property
def pixels(self):
"""
A `stixpy.science.PixelMasks` object representing the pixels contained in the data
"""
return self.pixel_masks
@property
def detectors(self):
"""
A `stixpy.science.DetectorMasks` object representing the detectors contained in the data.
"""
return self.detector_masks
@property
def energies(self):
"""
A `astropy.table.Table` object representing the energies contained in the data.
"""
return self._energies
@property
def times(self):
"""
An `astropy.time.Time` array representing the center of the observed time bins.
"""
return self.data["time"]
@property
@deprecated(name="duration", since="0.2", message="Use `durations` instead", warning_type=DeprecationWarning)
def duration(self):
"""
An `astropy.units.Quantity` array giving the duration or integration time
"""
return self.data["timedel"]
@property
def durations(self):
"""
An `astropy.units.Quantity` array giving the duration or integration time
"""
return self.data["timedel"]
[docs]
def get_data(
self,
*,
vtype="dcr",
time_indices=None,
energy_indices=None,
detector_indices=None,
pixel_indices=None,
sum_all_times=False,
):
r"""
Return the counts, errors, times, durations and energies for selected data.
Optionally summing in time and or energy.
Parameters
----------
vtype : str
Type of value to return (vtype) controls the normalisation:
* 'c' - count [c]
* 'cr' - count rate [c/s]
* 'dcr' - differential count rate [c/(s keV)]
time_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `time_indices=[0, 2, 5]` would return only the first, third and
sixth times while `time_indices=[[0, 2],[3, 5]]` would sum the data between.
energy_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `energy_indices=[0, 2, 5]` would return only the first, third and
sixth times while `energy_indices=[[0, 2],[3, 5]]` would sum the data between.
detector_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `detector_indices=[0, 2, 5]` would return only the first, third and
sixth detectors while `detector_indices=[[0, 2],[3, 5]]` would sum the data between.
pixel_indices : `list` or `numpy.ndarray`
If an 1xN array will be treated as mask if 2XN array will sum data between given
indices. For example `pixel_indices=[0, 2, 5]` would return only the first, third and
sixth pixels while `pixel_indices=[[0, 2],[3, 5]]` would sum the data between.
sum_all_times : `bool`
Flag to sum all give time intervals into one
Returns
-------
`tuple`
Counts, errors, times, deltatimes, energies
"""
counts = self.data["counts"]
try:
counts_var = self.data["counts_comp_err"] ** 2
except KeyError:
counts_var = self.data["counts_comp_comp_err"] ** 2
shape = counts.shape
if len(shape) < 4:
counts = counts.reshape(shape[0], 1, 1, shape[-1])
counts_var = counts_var.reshape(shape[0], 1, 1, shape[-1])
energies = self.energies[:]
times = self.times
if detector_indices is not None:
detecor_indices = np.asarray(detector_indices)
if detecor_indices.ndim == 1:
detector_mask = np.full(32, False)
detector_mask[detecor_indices] = True
counts = counts[:, detector_mask, ...]
counts_var = counts_var[:, detector_mask, ...]
elif detecor_indices.ndim == 2:
counts = np.hstack(
[np.sum(counts[:, dl : dh + 1, ...], axis=1, keepdims=True) for dl, dh in detecor_indices]
)
counts_var = np.concatenate(
[np.sum(counts_var[:, dl : dh + 1, ...], axis=1, keepdims=True) for dl, dh in detecor_indices],
axis=1,
)
if pixel_indices is not None:
pixel_indices = np.asarray(pixel_indices)
if pixel_indices.ndim == 1:
pixel_mask = np.full(12, False)
pixel_mask[pixel_indices] = True
num_pixels = counts.shape[2]
counts = counts[..., pixel_mask[:num_pixels], :]
counts_var = counts_var[..., pixel_mask[:num_pixels], :]
elif pixel_indices.ndim == 2:
counts = np.concatenate(
[np.sum(counts[..., pl : ph + 1, :], axis=2, keepdims=True) for pl, ph in pixel_indices], axis=2
)
counts_var = np.concatenate(
[np.sum(counts_var[..., pl : ph + 1, :], axis=2, keepdims=True) for pl, ph in pixel_indices], axis=2
)
e_norm = self.dE
if energy_indices is not None:
energy_indices = np.asarray(energy_indices)
if energy_indices.ndim == 1:
energy_mask = np.full(shape[-1], False)
energy_mask[energy_indices] = True
counts = counts[..., energy_mask]
counts_var = counts_var[..., energy_mask]
e_norm = self.dE[energy_mask]
energies = self.energies[energy_mask]
elif energy_indices.ndim == 2:
counts = np.concatenate(
[np.sum(counts[..., el : eh + 1], axis=-1, keepdims=True) for el, eh in energy_indices], axis=-1
)
counts_var = np.concatenate(
[np.sum(counts_var[..., el : eh + 1], axis=-1, keepdims=True) for el, eh in energy_indices], axis=-1
)
e_norm = np.hstack([(energies["e_high"][eh] - energies["e_low"][el]) for el, eh in energy_indices])
energies = np.atleast_2d(
[
(self._energies["e_low"][el].value, self._energies["e_high"][eh].value)
for el, eh in energy_indices
]
)
energies = QTable(energies * u.keV, names=["e_low", "e_high"])
t_norm = self.data["timedel"]
if time_indices is not None:
time_indices = np.asarray(time_indices)
if time_indices.ndim == 1:
time_mask = np.full(times.shape, False)
time_mask[time_indices] = True
counts = counts[time_mask, ...]
counts_var = counts_var[time_mask, ...]
t_norm = self.data["timedel"][time_mask]
times = times[time_mask]
# dT = self.data['timedel'][time_mask]
elif time_indices.ndim == 2:
new_times = []
dt = []
for tl, th in time_indices:
ts = times[tl] - self.data["timedel"][tl] * 0.5
te = times[th] + self.data["timedel"][th] * 0.5
td = te - ts
tc = ts + (td * 0.5)
dt.append(td.to("s"))
new_times.append(tc)
dt = np.hstack(dt)
times = Time(new_times)
counts = np.vstack([np.sum(counts[tl : th + 1, ...], axis=0, keepdims=True) for tl, th in time_indices])
counts_var = np.vstack(
[np.sum(counts_var[tl : th + 1, ...], axis=0, keepdims=True) for tl, th in time_indices]
)
t_norm = dt
if sum_all_times and len(new_times) > 1:
counts = np.sum(counts, axis=0, keepdims=True)
counts_var = np.sum(counts_var, axis=0, keepdims=True)
t_norm = np.sum(dt)
t_norm = t_norm.to("s")
if e_norm.size != 1:
e_norm = e_norm.reshape(1, 1, 1, -1)
if t_norm.size != 1:
t_norm = t_norm.reshape(-1, 1, 1, 1).to("s")
if vtype == "c":
norm = 1
elif vtype == "cr":
norm = 1 / t_norm
elif vtype == "dcr":
norm = 1 / (e_norm * t_norm)
else:
raise ValueError("vtype must be one of 'c', 'cr', 'dcr'.")
counts_err = np.sqrt(counts * u.ct + counts_var) * norm
counts = counts * norm
return counts, counts_err, times, t_norm, energies
[docs]
def concatenate(self, others):
"""
Concatenate two or more science products.
Parameters
----------
others: `list` [`stixpy.science.ScienceData`]
The other/s science products to concatenate
Returns
-------
`stixpy.science.ScienceData`
The concatenated science products
"""
others = others if isinstance(others, list) else [others]
if all([isinstance(o, type(self)) for o in others]):
control = self.control[:]
data = self.data[:]
for other in others:
self_control_ind_max = data["control_index"].max() + 1
other.control["index"] = other.control["index"] + self_control_ind_max
other.data["control_index"] = other.data["control_index"] + self_control_ind_max
try:
[
(table.meta.pop("DATASUM"), table.meta.pop("CHECKSUM"))
for table in [control, other.control, data, other.data]
]
except KeyError:
pass
control = vstack([control, other.control])
data = vstack([data, other.data])
return type(self)(
meta=self.meta, control=control, data=data, energies=self.energies, idb_version=self.idb_versions
)
def __repr__(self):
return (
f"{self.__class__.__name__}"
f"{self.time_range}"
f" {self.detector_masks}\n"
f" {self.pixel_masks}\n"
f" {self.energy_masks}"
)
[docs]
class RawPixelData(ScienceData, PixelPlotMixin, TimesSeriesPlotMixin, SpectrogramPlotMixin):
"""
Uncompressed or raw count data from selected pixels, detectors and energies.
Examples
--------
>>> from stixpy.data import test
>>> from stixpy.product import Product
>>> raw_pd = Product("http://dataarchive.stix.i4ds.net/fits/L1/2020/05/05/SCI/"
... "solo_L1_stix-sci-xray-rpd_20200505T235959-20200506T000019_V02_0087031808-50882.fits") # doctest: +REMOTE_DATA
>>> raw_pd # doctest: +REMOTE_DATA
RawPixelData <sunpy.time.timerange.TimeRange object at ...
Start: 2020-05-05 23:59:59
End: 2020-05-06 00:00:19
Center:2020-05-06 00:00:09
Duration:0.00023148148148144365 days or
0.005555555555554648 hours or
0.33333333333327886 minutes or
19.99999999999673 seconds
DetectorMasks
[0...4]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]
<BLANKLINE>
PixelMasks
[0...4]: [['1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1']]
<BLANKLINE>
EnergyEdgeMasks
[0]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]
<BLANKLINE>
"""
[docs]
@classmethod
def is_datasource_for(cls, *, meta, **kwargs):
"""Determines if meta data meach Raw Pixel Data"""
service_subservice_ssid = tuple(meta[name] for name in ["STYPE", "SSTYPE", "SSID"])
level = meta["level"]
if service_subservice_ssid == (21, 6, 20) and level == "L1":
return True
[docs]
class CompressedPixelData(ScienceData, PixelPlotMixin, TimesSeriesPlotMixin, SpectrogramPlotMixin):
"""
Compressed count data from selected pixels, detectors and energies.
Examples
--------
>>> from stixpy.data import test
>>> from stixpy.product import Product
>>> compressed_pd = Product("http://dataarchive.stix.i4ds.net/fits/L1/2020/05/05/SCI/"
... "solo_L1_stix-sci-xray-cpd_20200505T235959-20200506T000019_V02_0087031809-50883.fits") # doctest: +REMOTE_DATA
>>> compressed_pd # doctest: +REMOTE_DATA
CompressedPixelData <sunpy.time.timerange.TimeRange object at ...
Start: 2020-05-05 23:59:59
End: 2020-05-06 00:00:19
Center:2020-05-06 00:00:09
Duration:0.00023148148148144365 days or
0.005555555555554648 hours or
0.33333333333327886 minutes or
19.99999999999673 seconds
DetectorMasks
[0...4]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]
<BLANKLINE>
PixelMasks
[0...4]: [['1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1']]
<BLANKLINE>
EnergyEdgeMasks
[0]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]
"""
[docs]
@classmethod
def is_datasource_for(cls, *, meta, **kwargs):
"""Determines if meta data meach Raw Pixel Data"""
service_subservice_ssid = tuple(meta[name] for name in ["STYPE", "SSTYPE", "SSID"])
level = meta["level"]
if service_subservice_ssid == (21, 6, 21) and level == "L1":
return True
[docs]
class SummedCompressedPixelData(ScienceData, PixelPlotMixin, TimesSeriesPlotMixin, SpectrogramPlotMixin):
"""
Compressed and Summed count data from selected pixels, detectors and energies.
Examples
--------
>>> from stixpy.data import test
>>> from stixpy.product import Product
>>> summed_pd = Product("http://dataarchive.stix.i4ds.net/fits/L1/2020/05/05/SCI/"
... "solo_L1_stix-sci-xray-scpd_20200505T235959-20200506T000019_V02_0087031810-50884.fits") # doctest: +REMOTE_DATA
>>> summed_pd # doctest: +REMOTE_DATA
SummedCompressedPixelData <sunpy.time.timerange.TimeRange object at ...
Start: 2020-05-05 23:59:59
End: 2020-05-06 00:00:19
Center:2020-05-06 00:00:09
Duration:0.00023148148148144365 days or
0.005555555555554648 hours or
0.33333333333327886 minutes or
19.99999999999673 seconds
DetectorMasks
[0...4]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]
<BLANKLINE>
PixelMasks
[0...4]: [[['0' '0' '0' '1' '0' '0' '0' '1' '0' '0' '0' '1']
['0' '0' '1' '0' '0' '0' '1' '0' '0' '0' '1' '0']
['0' '1' '0' '0' '0' '1' '0' '0' '0' '1' '0' '0']
['1' '0' '0' '0' '1' '0' '0' '0' '1' '0' '0' '0']]]
<BLANKLINE>
EnergyEdgeMasks
[0]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]
"""
pass
[docs]
@classmethod
def is_datasource_for(cls, *, meta, **kwargs):
"""Determines if meta data meach Raw Pixel Data"""
service_subservice_ssid = tuple(meta[name] for name in ["STYPE", "SSTYPE", "SSID"])
level = meta["level"]
if service_subservice_ssid == (21, 6, 22) and level == "L1":
return True
[docs]
class Visibility(ScienceData):
"""
Compressed visibilities from selected pixels, detectors and energies.
Examples
--------
# >>> from stixpy.data import test
# >>> from stixpy.science import ScienceData
# >>> visibility = ScienceData.from_fits(test.STIX_SCI_XRAY_VIZ)
# >>> visibility
# Visibility <sunpy.time.timerange.TimeRange object at ...>
# Start: 2020-05-07 23:59:58
# End: 2020-05-08 00:00:14
# Center:2020-05-08 00:00:06
# Duration:0.00018518518518517713 days or
# 0.004444444444444251 hours or
# 0.26666666666665506 minutes or
# 15.999999999999304 seconds
# DetectorMasks
# [0...4]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]
# <BLANKLINE>
# PixelMasks
# [0]: [[['1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0']]]
# [1]: [[['1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0']]]
# [2]: [[['1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0']]]
# [3]: [[['1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0']]]
# [4]: [[['1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0' '0.0']
# ['0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '0.0' '1.0']]]
# <BLANKLINE>
# EnergyEdgeMasks
# [0]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]
# <BLANKLINE>
"""
def __init__(self, *, header, control, data, energies):
super().__init__(meta=header, control=control, data=data, energies=energies)
self.pixel_masks = PixelMasks(self.pixels)
@property
def pixels(self):
return np.vstack([self.data[f"pixel_mask{i}"][0] for i in range(1, 6)])
[docs]
@classmethod
def is_datasource_for(cls, *, meta, **kwargs):
"""Determines if meta data meach Raw Pixel Data"""
service_subservice_ssid = tuple(meta[name] for name in ["STYPE", "SSTYPE", "SSID"])
level = meta["level"]
if service_subservice_ssid == (21, 6, 23) and level == "L1":
return True
[docs]
class Spectrogram(ScienceData, TimesSeriesPlotMixin, SpectrogramPlotMixin):
"""
Spectrogram from selected pixels, detectors and energies.
Parameters
----------
meta : `astropy.fits.Header`
control : `astropy.table.QTable`
data : `astropy.table.QTable`
energies : `astropy.table.QTable`
Examples
--------
>>> from stixpy.data import test
>>> from stixpy.product import Product
>>> spectogram = Product("http://dataarchive.stix.i4ds.net/fits/L1/2020/05/05/SCI/"
... "solo_L1_stix-sci-xray-spec_20200505T235959-20200506T000019_V02_0087031812-50886.fits") # doctest: +REMOTE_DATA
>>> spectogram # doctest: +REMOTE_DATA
Spectrogram <sunpy.time.timerange.TimeRange ...
Start: 2020-05-05 23:59:59
End: 2020-05-06 00:00:19
Center:2020-05-06 00:00:09
Duration:0.00023148148148144365 days or
0.005555555555554648 hours or
0.33333333333327886 minutes or
19.99999999999673 seconds
DetectorMasks
[0]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]
<BLANKLINE>
PixelMasks
[0...4]: [['0' '0' '0' '0' '0' '0' '0' '0' '0' '0' '0' '0']]
<BLANKLINE>
EnergyEdgeMasks
[0]: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]
<BLANKLINE>
"""
def __init__(self, *, meta, control, data, energies, idb_versions):
"""
Parameters
----------
meta : astropy.fits.Header
control : astropy.table.QTable
data : astropy.table.QTable
energies : astropy.table.QTable
"""
super().__init__(meta=meta, control=control, data=data, energies=energies, idb_versions=idb_versions)
self.count_type = "rate"
self.detector_masks = DetectorMasks(self.control["detector_masks"])
self.pixel_masks = PixelMasks(self.data["pixel_masks"])
# self.energy_masks = EnergyEdgeMasks(self.control['energy_bin_edge_mask'])
# self.dE = (energies['e_high'] - energies['e_low'])[self.energy_masks.masks[0] == 1]
# self.dE = np.hstack([[1], np.diff(energies['e_low'][1:]).value, [1]]) * u.keV
[docs]
@classmethod
def is_datasource_for(cls, *, meta, **kwargs):
"""Determines if meta data meach Raw Pixel Data"""
service_subservice_ssid = tuple(meta[name] for name in ["STYPE", "SSTYPE", "SSID"])
level = meta["level"]
if service_subservice_ssid == (21, 6, 24) and level == "L1":
return True
class SliderCustomValue(Slider):
"""
A slider with a customisable formatter
"""
def __init__(self, *args, format_func=None, **kwargs):
if format_func is not None:
self._format = format_func
super().__init__(*args, **kwargs)