From 3ba0a1e53ecb8b3deca900fcae29c355a7efc37e Mon Sep 17 00:00:00 2001 From: Shane Maloney Date: Sat, 18 May 2024 11:53:50 +0100 Subject: [PATCH 1/2] WIP --- stixpy/product/sources/science.py | 49 ++++++++++++++++++++++++++++++- stixpy/utils/time.py | 3 ++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/stixpy/product/sources/science.py b/stixpy/product/sources/science.py index b50f6b3..0bbeddb 100644 --- a/stixpy/product/sources/science.py +++ b/stixpy/product/sources/science.py @@ -31,9 +31,48 @@ "EnergyEdgeMasks", ] +from stixpy.utils.time import times_to_indices quantity_support() +# fmt: off +PIXEL_MAPPING = { + "all": slice(0, 12), # All pixels (top, bottom and small) + "big": slice(0, 8), # All big pixel top + bot + "top": slice(0, 4), # Top row pixels only + "bottom": slice(4, 8), # Bottom row pixels only + "small": slice(8, 12), # Small, middle row pixels only +} + + +DETECTOR_MAPPING = { + "all": slice(0, 32), # all detectors + "imaging": [2, 19, 21, 15, 13, 31, 20, 25, 3, 23, 7, 27, 14, 26, 30, 5, 29, 1, 24, 4, 22, 6, 28, 0,], # 30 imaging detectors (excluding BKG and CFL) + "imaging_24": [2, 19, 21, 15, 13, 31, 20, 25, 3, 23, 7, 27, 14, 26, 30, 5, 29, 1,], # 24 standard (not fine) imaging detectors + "imaging_fine": [4, 22, 6, 28, 0], # 6 finest imaging detectors + "cfl": 9, # Only CLF detector + "bkg": 8, # Only BKG detector +} +# fmt: on + + +def parse_time(times): + pass + + +def parse_energy(): + pass + + +def parse_detectors(detectors): + if isinstance(detectors, str): + pass + + +def parse_pixels(pixels): + if isinstance(pixels, str): + return PIXEL_MAPPING.get(pixels, None) + class PPrintMixin: """ @@ -523,7 +562,13 @@ def durtaion(self): return self.data["timedel"] def get_data( - self, time_indices=None, energy_indices=None, detector_indices=None, pixel_indices=None, sum_all_times=False + self, + time=None, + time_indices=None, + energy_indices=None, + detector_indices=None, + pixel_indices=None, + sum_all_times=False, ): """ Return the counts, errors, times, durations and energies for selected data. @@ -633,6 +678,8 @@ def get_data( energies = QTable(energies * u.keV, names=["e_low", "e_high"]) t_norm = self.data["timedel"] + if time is not None: + time_indices = times_to_indices(time, times) if time_indices is not None: time_indices = np.asarray(time_indices) if time_indices.ndim == 1: diff --git a/stixpy/utils/time.py b/stixpy/utils/time.py index 4848c36..1f50b91 100644 --- a/stixpy/utils/time.py +++ b/stixpy/utils/time.py @@ -1,6 +1,7 @@ import astropy.units as u import numpy as np from astropy.time import Time +from astropy.units import Quantity from numpy.testing import assert_equal @@ -22,6 +23,8 @@ def times_to_indices(in_times, obs_times, unit=u.ms, decimals=3): ------- Array of indices """ + if not isinstance(in_times, (Time, Quantity)): + return in_times unique_only = False shape = None relative_times = np.around((obs_times - obs_times[0]).to(unit), decimals=decimals) From 0b04ba77d76166d49deb19741dfdda28573db2eb Mon Sep 17 00:00:00 2001 From: Shane Maloney Date: Mon, 27 May 2024 10:27:39 +0100 Subject: [PATCH 2/2] WIP add dunder getitem --- stixpy/product/product.py | 13 ++-- stixpy/product/sources/science.py | 107 ++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 11 deletions(-) diff --git a/stixpy/product/product.py b/stixpy/product/product.py index c96dbf6..bc4dbfa 100644 --- a/stixpy/product/product.py +++ b/stixpy/product/product.py @@ -1,3 +1,4 @@ +import numpy as np from astropy.time import Time __all__ = ["BaseProduct", "GenericProduct", "LevelBinary", "L1Product", "Level2"] @@ -76,14 +77,14 @@ class L1Product(GenericProduct): def __init__(self, **kwargs): super().__init__(**kwargs) - meta = kwargs["meta"] - data = kwargs["data"] - # TODO don't change the data add new property or similar + @property + def time(self): try: - data["time"] = Time(meta["date-obs"]) + data["time"] + time = Time(self.meta["date-obs"]) + np.atleast_1d(self.data["time"]) except KeyError: - data["time"] = Time(meta["date_obs"]) + data["time"] + time = Time(self.meta["date_obs"]) + np.atleast_1d(self.data["time"]) + return time class Level2(GenericProduct): @@ -96,5 +97,5 @@ def __init__(self, **kwargs): def is_datasource_for(cls, *, meta, **kwargs): """Determines if meta data meach Raw Pixel Data""" level = meta["level"] - if level == "LB": + if level == "L2": return True diff --git a/stixpy/product/sources/science.py b/stixpy/product/sources/science.py index 0bbeddb..959b7e1 100644 --- a/stixpy/product/sources/science.py +++ b/stixpy/product/sources/science.py @@ -1,3 +1,4 @@ +from typing import Union, Callable from itertools import product from collections import defaultdict @@ -106,6 +107,7 @@ class IndexMasks(PPrintMixin): """ def __init__(self, mask_array): + mask_array = np.atleast_2d(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 @@ -514,7 +516,7 @@ def __init__(self, *, meta, control, data, energies, idb_versions=None): 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.energy_masks = EnergyEdgeMasks(self.control["energy_bin_edge_mask"].squeeze()) self.dE = energies["e_high"] - energies["e_low"] @property @@ -522,9 +524,7 @@ 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 - ) + return TimeRange(self.time[0] - self.data["timedel"][0] / 2, self.time[-1] + self.data["timedel"][-1] / 2) @property def pixels(self): @@ -555,12 +555,109 @@ def times(self): return self.data["time"] @property - def durtaion(self): + def duration(self): """ An `astropy.units.Quantiy` array giving the duration or integration time """ return self.data["timedel"] + def crop_by_value( + self, + values, + ): + r""" + Crop the data by a values e.g time, energy. + + Parameters + ---------- + values + + Returns + ------- + + Examples + -------- + >>> cpd.crop_by_value() + + """ + + def rebin(self, *indices, operator: Callable = np.sum): + r""" + Rebin the data using the given indices. + + Parameters + ---------- + operator : + indices : + + Returns + ------- + + Examples + -------- + cpd.rebin(None, None, None, [1, 5]) + cpd.rebin(None, None, None, [[1, 5], [5, 10]]) + cpd.rebin([0:-10], None, None, [[1, 5], [5, 10]]) + + + """ + + def rebin_by_vales(self, *values, operator: Callable = np.sum): + r""" + Rebin the data using the given values. + + Parameters + ---------- + values + + Returns + ------- + + Examples + -------- + cpd.rebin_by_vales('all', 'imaging', 'big', [4, 10]*u.keV) + cpd.rebin_by_vales([t1, t2]', 'imaging', 'big', [[4, 10], [10, 15]]*u.keV) + cpd.rebin_by_vales([[t1, t2], [t2, t3]]', 'imaging', 'big', [[4, 10], [10, 15]]*u.keV) + + + """ + + def __getitem__(self, item: Union[int, tuple[int, ...], slice]): + r"""Slicing""" + if isinstance(item, int): + # single + data = self.data[item : item + 1].copy() + energies = self.energies.copy() + meta = self.meta.copy() + idb = self.idb_versions.copy() + ci = data["control_index"] + control = self.control[np.argwhere(self.control["index"] == ci)].copy() + return type(self)(meta=meta, control=control, data=data, energies=energies, idb_versions=idb) + elif isinstance(item, tuple): + time_slice = item[0] + missing_dims = len(self.data["counts"].shape) - len(item) + count_slice = list(item[1:]) + [None] * missing_dims + data = self.data[time_slice].copy() + data["counts"] = data["counts"][*count_slice] + energies = self.energies.copy() + meta = self.meta.copy() + idb = self.idb_versions.copy() + ci = set(data["control_index"]) + control = self.control[np.argwhere(np.isin(self.control["index"], list(ci)))].copy() + return type(self)(meta=meta, control=control, data=data, energies=energies, idb_versions=idb) + + elif isinstance(item, slice): + # Slice + data = self.data[item].copy() + energies = self.energies.copy() + meta = self.meta.copy() + idb = self.idb_versions.copy() + ci = set(data["control_index"]) + control = self.control[np.argwhere(np.isin(self.control["index"], list(ci)))].copy() + return type(self)(meta=meta, control=control, data=data, energies=energies, idb_versions=idb) + else: + raise TypeError(f"Index must be int, not {type(item)}.") + def get_data( self, time=None,