isgri 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- isgri/__init__.py +1 -0
- isgri/__version__.py +1 -0
- isgri/catalog/__init__.py +3 -0
- isgri/catalog/builder.py +90 -0
- isgri/catalog/scwquery.py +524 -0
- isgri/catalog/wcs.py +190 -0
- isgri/cli.py +224 -0
- isgri/config.py +151 -0
- isgri/utils/file_loaders.py +392 -159
- isgri/utils/lightcurve.py +409 -265
- isgri/utils/pif.py +286 -41
- isgri/utils/quality.py +389 -182
- isgri/utils/time_conversion.py +210 -39
- isgri-0.5.0.dist-info/METADATA +164 -0
- isgri-0.5.0.dist-info/RECORD +19 -0
- isgri-0.5.0.dist-info/entry_points.txt +2 -0
- isgri-0.3.0.dist-info/METADATA +0 -66
- isgri-0.3.0.dist-info/RECORD +0 -11
- {isgri-0.3.0.dist-info → isgri-0.5.0.dist-info}/WHEEL +0 -0
- {isgri-0.3.0.dist-info → isgri-0.5.0.dist-info}/licenses/LICENSE +0 -0
isgri/utils/lightcurve.py
CHANGED
|
@@ -1,265 +1,409 @@
|
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
""
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
float
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"""
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
1
|
+
"""
|
|
2
|
+
ISGRI Light Curve Analysis
|
|
3
|
+
===========================
|
|
4
|
+
|
|
5
|
+
Tools for working with INTEGRAL/ISGRI event data and creating light curves.
|
|
6
|
+
|
|
7
|
+
Classes
|
|
8
|
+
-------
|
|
9
|
+
LightCurve : Main light curve class
|
|
10
|
+
|
|
11
|
+
Examples
|
|
12
|
+
--------
|
|
13
|
+
>>> from isgri.utils import LightCurve
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Load events with PIF weighting
|
|
16
|
+
>>> lc = LightCurve.load_data(
|
|
17
|
+
... events_path="events.fits",
|
|
18
|
+
... pif_path="model.fits",
|
|
19
|
+
... pif_threshold=0.5
|
|
20
|
+
... )
|
|
21
|
+
>>>
|
|
22
|
+
>>> # Create 1-second binned light curve
|
|
23
|
+
>>> time, counts = lc.rebin(binsize=1.0, emin=20, emax=100)
|
|
24
|
+
>>>
|
|
25
|
+
>>> # Analyze by detector module
|
|
26
|
+
>>> times, module_lcs = lc.rebin_by_modules(1.0, 20, 100)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from astropy.io import fits
|
|
30
|
+
import numpy as np
|
|
31
|
+
from numpy.typing import NDArray
|
|
32
|
+
from typing import Optional, Union, Tuple, List
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
import os
|
|
35
|
+
from .file_loaders import load_isgri_events, load_isgri_pif, default_pif_metadata, merge_metadata
|
|
36
|
+
from .pif import DETZ_BOUNDS, DETY_BOUNDS
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class LightCurve:
|
|
40
|
+
"""
|
|
41
|
+
ISGRI light curve analysis class.
|
|
42
|
+
|
|
43
|
+
Handles event data with optional detector response (PIF) weighting.
|
|
44
|
+
Provides rebinning, module-level analysis, and time conversions.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
time : ndarray
|
|
49
|
+
IJD time values of events
|
|
50
|
+
energies : ndarray
|
|
51
|
+
Energy values in keV
|
|
52
|
+
gtis : ndarray
|
|
53
|
+
Good Time Intervals (start, stop) in IJD
|
|
54
|
+
dety : ndarray
|
|
55
|
+
Y detector coordinates (mm)
|
|
56
|
+
detz : ndarray
|
|
57
|
+
Z detector coordinates (mm)
|
|
58
|
+
weights : ndarray
|
|
59
|
+
PIF weight for each event (1.0 if no PIF)
|
|
60
|
+
metadata : dict
|
|
61
|
+
Event metadata (SWID, source info, etc.)
|
|
62
|
+
|
|
63
|
+
Attributes
|
|
64
|
+
----------
|
|
65
|
+
t0 : float
|
|
66
|
+
Reference time (first event time in IJD)
|
|
67
|
+
local_time : ndarray
|
|
68
|
+
Time relative to t0 in seconds
|
|
69
|
+
|
|
70
|
+
Examples
|
|
71
|
+
--------
|
|
72
|
+
>>> lc = LightCurve.load_data("events.fits", pif_path="model.fits")
|
|
73
|
+
>>> time, counts = lc.rebin(binsize=1.0, emin=20, emax=100)
|
|
74
|
+
>>> print(f"Total counts: {counts.sum()}")
|
|
75
|
+
|
|
76
|
+
>>> # Module-by-module analysis
|
|
77
|
+
>>> times, module_counts = lc.rebin_by_modules(1.0, 20, 100)
|
|
78
|
+
>>> print(f"Module 3 counts: {module_counts[3].sum()}")
|
|
79
|
+
|
|
80
|
+
See Also
|
|
81
|
+
--------
|
|
82
|
+
load_data : Load events from FITS files
|
|
83
|
+
rebin : Rebin light curve
|
|
84
|
+
rebin_by_modules : Rebin by detector module
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
time: NDArray[np.float64]
|
|
88
|
+
energies: NDArray[np.float64]
|
|
89
|
+
gtis: NDArray[np.float64]
|
|
90
|
+
t0: float
|
|
91
|
+
local_time: NDArray[np.float64]
|
|
92
|
+
dety: NDArray[np.float64]
|
|
93
|
+
detz: NDArray[np.float64]
|
|
94
|
+
weights: NDArray[np.float64]
|
|
95
|
+
metadata: dict
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
time: NDArray[np.float64],
|
|
100
|
+
energies: NDArray[np.float64],
|
|
101
|
+
gtis: NDArray[np.float64],
|
|
102
|
+
dety: NDArray[np.float64],
|
|
103
|
+
detz: NDArray[np.float64],
|
|
104
|
+
weights: NDArray[np.float64],
|
|
105
|
+
metadata: dict,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Initialize LightCurve instance.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
time : ndarray
|
|
113
|
+
IJD time values
|
|
114
|
+
energies : ndarray
|
|
115
|
+
Energy values in keV
|
|
116
|
+
gtis : ndarray
|
|
117
|
+
Good Time Intervals
|
|
118
|
+
dety : ndarray
|
|
119
|
+
Y detector coordinates
|
|
120
|
+
detz : ndarray
|
|
121
|
+
Z detector coordinates
|
|
122
|
+
weights : ndarray
|
|
123
|
+
PIF weights for each event
|
|
124
|
+
metadata : dict
|
|
125
|
+
Event metadata
|
|
126
|
+
"""
|
|
127
|
+
self.time = time
|
|
128
|
+
self.energies = energies
|
|
129
|
+
self.gtis = gtis
|
|
130
|
+
self.t0 = time[0]
|
|
131
|
+
self.local_time = (time - self.t0) * 86400
|
|
132
|
+
self.dety = dety
|
|
133
|
+
self.detz = detz
|
|
134
|
+
self.weights = weights
|
|
135
|
+
self.metadata = metadata
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def load_data(
|
|
139
|
+
cls,
|
|
140
|
+
events_path: Optional[Union[str, Path]] = None,
|
|
141
|
+
pif_path: Optional[Union[str, Path]] = None,
|
|
142
|
+
scw: Optional[str] = None,
|
|
143
|
+
source: Optional[str] = None,
|
|
144
|
+
pif_threshold: float = 0.5,
|
|
145
|
+
pif_extension: int = -1,
|
|
146
|
+
) -> "LightCurve":
|
|
147
|
+
"""
|
|
148
|
+
Loads the events from the given events file and PIF file (optional).
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
events_path (str): The path to the events file or directory.
|
|
152
|
+
pif_path (str, optional): The path to the PIF file. Defaults to None.
|
|
153
|
+
scw (str, optional): SCW identifier for auto-path resolution. Defaults to None.
|
|
154
|
+
source (str, optional): Source name for auto-path resolution. Defaults to None.
|
|
155
|
+
pif_threshold (float, optional): The PIF threshold value. Defaults to 0.5.
|
|
156
|
+
pif_extension (int, optional): PIF file extension index. Defaults to -1.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
LightCurve: An instance of the LightCurve class.
|
|
160
|
+
"""
|
|
161
|
+
events, gtis, metadata = load_isgri_events(events_path)
|
|
162
|
+
if pif_path:
|
|
163
|
+
if pif_threshold < 0 or pif_threshold > 1:
|
|
164
|
+
raise ValueError(f"pif_threshold must be in [0, 1], got {pif_threshold}")
|
|
165
|
+
|
|
166
|
+
events, weights, metadata_pif = load_isgri_pif(pif_path, events, pif_threshold, pif_extension)
|
|
167
|
+
else:
|
|
168
|
+
weights = np.ones(len(events))
|
|
169
|
+
metadata_pif = default_pif_metadata()
|
|
170
|
+
|
|
171
|
+
metadata = merge_metadata(metadata, metadata_pif)
|
|
172
|
+
time = events["TIME"]
|
|
173
|
+
energies = events["ISGRI_ENERGY"]
|
|
174
|
+
dety, detz = events["DETY"], events["DETZ"]
|
|
175
|
+
return cls(time, energies, gtis, dety, detz, weights, metadata)
|
|
176
|
+
|
|
177
|
+
def rebin(
|
|
178
|
+
self,
|
|
179
|
+
binsize: Union[float, NDArray[np.float64], List[float]],
|
|
180
|
+
emin: float,
|
|
181
|
+
emax: float,
|
|
182
|
+
local_time: bool = True,
|
|
183
|
+
custom_mask: Optional[NDArray[np.bool_]] = None,
|
|
184
|
+
) -> Tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
185
|
+
"""
|
|
186
|
+
Rebins the events with the specified bin size and energy range.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
binsize (float or array): The bin size in seconds, or array of bin edges.
|
|
190
|
+
emin (float): The minimum energy value in keV.
|
|
191
|
+
emax (float): The maximum energy value in keV.
|
|
192
|
+
local_time (bool, optional): If True, returns local time. If False, returns IJD time. Defaults to True.
|
|
193
|
+
custom_mask (ndarray, optional): Additional boolean mask to apply. Defaults to None.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
tuple: (time, counts) arrays.
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
ValueError: If emin >= emax.
|
|
200
|
+
|
|
201
|
+
Examples:
|
|
202
|
+
>>> time, counts = lc.rebin(binsize=1.0, emin=30, emax=300)
|
|
203
|
+
>>> time, counts = lc.rebin(binsize=[0, 1, 2, 5, 10], emin=50, emax=200)
|
|
204
|
+
"""
|
|
205
|
+
# Validate inputs
|
|
206
|
+
if emin >= emax:
|
|
207
|
+
raise ValueError(f"emin ({emin}) must be less than emax ({emax})")
|
|
208
|
+
|
|
209
|
+
if emin < 0:
|
|
210
|
+
raise ValueError(f"emin must be non-negative, got {emin}")
|
|
211
|
+
|
|
212
|
+
if isinstance(binsize, (int, float)) and binsize <= 0:
|
|
213
|
+
raise ValueError(f"binsize must be positive, got {binsize}")
|
|
214
|
+
|
|
215
|
+
if custom_mask is not None and len(custom_mask) != len(self.time):
|
|
216
|
+
raise ValueError(
|
|
217
|
+
f"custom_mask length ({len(custom_mask)}) must match " f"number of events ({len(self.time)})"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Select time axis
|
|
221
|
+
time = self.local_time if local_time else self.time
|
|
222
|
+
t0 = 0 if local_time else self.t0
|
|
223
|
+
|
|
224
|
+
# Create bins
|
|
225
|
+
bins, binsize_actual = self._create_bins(binsize, time, t0, local_time)
|
|
226
|
+
|
|
227
|
+
# Apply filters
|
|
228
|
+
mask = self._create_event_mask(emin, emax, custom_mask)
|
|
229
|
+
time_filtered = time[mask]
|
|
230
|
+
weights_filtered = self.weights[mask]
|
|
231
|
+
|
|
232
|
+
# Histogram
|
|
233
|
+
counts, bin_edges = np.histogram(time_filtered, bins=bins, weights=weights_filtered)
|
|
234
|
+
time_centers = bin_edges[:-1] + 0.5 * binsize_actual
|
|
235
|
+
|
|
236
|
+
return time_centers, counts
|
|
237
|
+
|
|
238
|
+
def _create_bins(
|
|
239
|
+
self,
|
|
240
|
+
binsize: Union[float, NDArray[np.float64], List[float]],
|
|
241
|
+
time: NDArray[np.float64],
|
|
242
|
+
t0: float,
|
|
243
|
+
local_time: bool,
|
|
244
|
+
) -> Tuple[NDArray[np.float64], float]:
|
|
245
|
+
"""
|
|
246
|
+
Create time bins for rebinning.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
binsize (float or array): Bin size or custom bin edges.
|
|
250
|
+
time (ndarray): Time array.
|
|
251
|
+
t0 (float): Start time.
|
|
252
|
+
local_time (bool): Whether using local time.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
tuple: (bins array, actual binsize).
|
|
256
|
+
"""
|
|
257
|
+
if isinstance(binsize, (list, np.ndarray)):
|
|
258
|
+
# Custom bin edges provided
|
|
259
|
+
bins = np.array(binsize)
|
|
260
|
+
binsize_actual = np.mean(np.diff(bins))
|
|
261
|
+
else:
|
|
262
|
+
# Uniform binning
|
|
263
|
+
binsize_actual = binsize if local_time else binsize / 86400
|
|
264
|
+
bins = np.arange(t0, time[-1] + binsize_actual, binsize_actual)
|
|
265
|
+
|
|
266
|
+
return bins, binsize_actual
|
|
267
|
+
|
|
268
|
+
def _create_event_mask(
|
|
269
|
+
self,
|
|
270
|
+
emin: float,
|
|
271
|
+
emax: float,
|
|
272
|
+
custom_mask: Optional[NDArray[np.bool_]] = None,
|
|
273
|
+
) -> NDArray[np.bool_]:
|
|
274
|
+
"""
|
|
275
|
+
Create combined event filter mask.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
emin (float): Minimum energy in keV.
|
|
279
|
+
emax (float): Maximum energy in keV.
|
|
280
|
+
custom_mask (ndarray, optional): Additional mask to apply.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
ndarray: Boolean mask for events.
|
|
284
|
+
"""
|
|
285
|
+
# Energy filter
|
|
286
|
+
mask = (self.energies >= emin) & (self.energies < emax)
|
|
287
|
+
|
|
288
|
+
# Custom filter (optional)
|
|
289
|
+
if custom_mask is not None:
|
|
290
|
+
mask &= custom_mask
|
|
291
|
+
|
|
292
|
+
return mask
|
|
293
|
+
|
|
294
|
+
def rebin_by_modules(
|
|
295
|
+
self,
|
|
296
|
+
binsize: float,
|
|
297
|
+
emin: float,
|
|
298
|
+
emax: float,
|
|
299
|
+
local_time: bool = True,
|
|
300
|
+
custom_mask: Optional[NDArray[np.bool_]] = None,
|
|
301
|
+
) -> Tuple[NDArray[np.float64], List[NDArray[np.float64]]]:
|
|
302
|
+
"""
|
|
303
|
+
Rebins the events by all 8 detector modules with the specified bin size and energy range.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
binsize (float): The bin size in seconds.
|
|
307
|
+
emin (float): The minimum energy value in keV.
|
|
308
|
+
emax (float): The maximum energy value in keV.
|
|
309
|
+
local_time (bool, optional): If True, returns local time. Defaults to True.
|
|
310
|
+
custom_mask (ndarray, optional): A custom mask to apply. Defaults to None.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
tuple: (times, counts) where:
|
|
314
|
+
- times: array of time bin centers
|
|
315
|
+
- counts: list of 8 arrays, one for each module
|
|
316
|
+
|
|
317
|
+
Raises:
|
|
318
|
+
ValueError: If emin >= emax.
|
|
319
|
+
|
|
320
|
+
Examples:
|
|
321
|
+
>>> times, counts = lc.rebin_by_modules(binsize=1.0, emin=30, emax=300)
|
|
322
|
+
>>> module_3_lc = counts[3] # Get lightcurve for module 3
|
|
323
|
+
"""
|
|
324
|
+
if emin >= emax:
|
|
325
|
+
raise ValueError("emin must be less than emax")
|
|
326
|
+
|
|
327
|
+
time = self.local_time if local_time else self.time
|
|
328
|
+
t0 = 0 if local_time else self.t0
|
|
329
|
+
binsize_adj = binsize if local_time else binsize / 86400
|
|
330
|
+
bins = np.arange(t0, time[-1] + binsize_adj, binsize_adj)
|
|
331
|
+
times = bins[:-1] + 0.5 * binsize_adj
|
|
332
|
+
|
|
333
|
+
energy_mask = (self.energies >= emin) & (self.energies < emax)
|
|
334
|
+
if custom_mask is not None:
|
|
335
|
+
energy_mask &= custom_mask
|
|
336
|
+
|
|
337
|
+
time_filtered = time[energy_mask]
|
|
338
|
+
dety_filtered = self.dety[energy_mask]
|
|
339
|
+
detz_filtered = self.detz[energy_mask]
|
|
340
|
+
weights_filtered = self.weights[energy_mask]
|
|
341
|
+
|
|
342
|
+
# Compute module indices using digitize
|
|
343
|
+
dety_bin = np.digitize(dety_filtered, DETY_BOUNDS) - 1 # 0 or 1
|
|
344
|
+
detz_bin = np.digitize(detz_filtered, DETZ_BOUNDS) - 1 # 0, 1, 2, or 3
|
|
345
|
+
module_idx = dety_bin + detz_bin * 2 # Flat index: 0-7
|
|
346
|
+
|
|
347
|
+
counts = []
|
|
348
|
+
for i in range(8):
|
|
349
|
+
mask = module_idx == i
|
|
350
|
+
counts.append(np.histogram(time_filtered[mask], bins=bins, weights=weights_filtered[mask])[0])
|
|
351
|
+
|
|
352
|
+
return times, counts
|
|
353
|
+
|
|
354
|
+
def cts(
|
|
355
|
+
self,
|
|
356
|
+
t1: float,
|
|
357
|
+
t2: float,
|
|
358
|
+
emin: float,
|
|
359
|
+
emax: float,
|
|
360
|
+
local_time: bool = True,
|
|
361
|
+
) -> float:
|
|
362
|
+
"""
|
|
363
|
+
Calculates the counts in the specified time and energy range.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
t1 (float): The start time (seconds or IJD depending on local_time).
|
|
367
|
+
t2 (float): The end time (seconds or IJD depending on local_time).
|
|
368
|
+
emin (float): The minimum energy value in keV.
|
|
369
|
+
emax (float): The maximum energy value in keV.
|
|
370
|
+
local_time (bool, optional): If True, uses local time. Defaults to True.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
float: The total counts in the specified range.
|
|
374
|
+
"""
|
|
375
|
+
time = self.local_time if local_time else self.time
|
|
376
|
+
return np.sum(self.weights[(time >= t1) & (time < t2) & (self.energies >= emin) & (self.energies < emax)])
|
|
377
|
+
|
|
378
|
+
def ijd2loc(self, ijd_time: Union[float, NDArray[np.float64]]) -> Union[float, NDArray[np.float64]]:
|
|
379
|
+
"""
|
|
380
|
+
Converts IJD (INTEGRAL Julian Date) time to local time.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
ijd_time (float or ndarray): The IJD time value(s).
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
float or ndarray: The local time in seconds from t0.
|
|
387
|
+
"""
|
|
388
|
+
return (ijd_time - self.t0) * 86400
|
|
389
|
+
|
|
390
|
+
def loc2ijd(self, evt_time: Union[float, NDArray[np.float64]]) -> Union[float, NDArray[np.float64]]:
|
|
391
|
+
"""
|
|
392
|
+
Converts local time to IJD (INTEGRAL Julian Date) time.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
evt_time (float or ndarray): The local time in seconds from t0.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
float or ndarray: The IJD time value(s).
|
|
399
|
+
"""
|
|
400
|
+
return evt_time / 86400 + self.t0
|
|
401
|
+
|
|
402
|
+
def __repr__(self) -> str:
|
|
403
|
+
"""String representation."""
|
|
404
|
+
return (
|
|
405
|
+
f"LightCurve(n_events={len(self.time)}, "
|
|
406
|
+
f"time_range=({self.time[0]:.3f}, {self.time[-1]:.3f}) IJD, "
|
|
407
|
+
f"energy_range=({self.energies.min():.1f}, {self.energies.max():.1f}) keV, "
|
|
408
|
+
f"scw={self.metadata.get('SWID', 'Unknown')})"
|
|
409
|
+
)
|