isgri 0.6.1__py3-none-any.whl → 0.7.1__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/__version__.py +1 -1
- isgri/catalog/__init__.py +2 -1
- isgri/catalog/builder.py +370 -16
- isgri/catalog/scwquery.py +5 -2
- isgri/cli/__init__.py +1 -1
- isgri/cli/builder.py +102 -0
- isgri/cli/main.py +188 -168
- isgri/cli/query.py +210 -164
- isgri/config.py +37 -9
- isgri/utils/file_loaders.py +171 -30
- isgri/utils/lightcurve.py +103 -13
- isgri/utils/pif.py +14 -0
- isgri/utils/quality.py +4 -3
- isgri-0.7.1.dist-info/METADATA +266 -0
- isgri-0.7.1.dist-info/RECORD +22 -0
- isgri-0.6.1.dist-info/METADATA +0 -147
- isgri-0.6.1.dist-info/RECORD +0 -21
- {isgri-0.6.1.dist-info → isgri-0.7.1.dist-info}/WHEEL +0 -0
- {isgri-0.6.1.dist-info → isgri-0.7.1.dist-info}/entry_points.txt +0 -0
- {isgri-0.6.1.dist-info → isgri-0.7.1.dist-info}/licenses/LICENSE +0 -0
isgri/utils/file_loaders.py
CHANGED
|
@@ -43,18 +43,103 @@ from numpy.typing import NDArray
|
|
|
43
43
|
from typing import Tuple, Dict, Optional, Union
|
|
44
44
|
from pathlib import Path
|
|
45
45
|
import os
|
|
46
|
-
from .pif import
|
|
46
|
+
from .pif import coding_fraction, estimate_active_modules
|
|
47
|
+
from ..config import Config
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
def
|
|
50
|
+
def resolve_pif_path(
|
|
51
|
+
pif_path: Optional[Union[str, Path]] = None,
|
|
52
|
+
source: Optional[str] = None,
|
|
53
|
+
swid: Optional[str] = None,
|
|
54
|
+
config: Optional[Config] = None,
|
|
55
|
+
) -> str:
|
|
56
|
+
"""
|
|
57
|
+
Resolve PIF file path based on input or source name.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
pif_path : str or Path, optional
|
|
62
|
+
Direct path to PIF file.
|
|
63
|
+
source : str, optional
|
|
64
|
+
Source name to construct default PIF path.
|
|
65
|
+
swid : str, optional
|
|
66
|
+
Science Window ID
|
|
67
|
+
config : Config, optional
|
|
68
|
+
Configuration object containing pif_path.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
resolved_path : str
|
|
73
|
+
Resolved absolute path to PIF file.
|
|
74
|
+
|
|
75
|
+
Raises
|
|
76
|
+
------
|
|
77
|
+
FileNotFoundError
|
|
78
|
+
If PIF file not found.
|
|
79
|
+
|
|
80
|
+
Notes
|
|
81
|
+
-----
|
|
82
|
+
- If both pif_path and source are provided, pif_path takes precedence.
|
|
83
|
+
- If neither is provided, raises ValueError.
|
|
84
|
+
"""
|
|
85
|
+
if pif_path is not None:
|
|
86
|
+
path = Path(pif_path)
|
|
87
|
+
if path.is_file():
|
|
88
|
+
return str(path)
|
|
89
|
+
if not path.is_dir():
|
|
90
|
+
raise FileNotFoundError(f"PIF path is not a directory: {path}")
|
|
91
|
+
|
|
92
|
+
elif config is not None and config.pif_path is not None:
|
|
93
|
+
path = Path(config.pif_path)
|
|
94
|
+
if not path.is_dir():
|
|
95
|
+
raise FileNotFoundError(f"PIF path from config is not a directory: {path}")
|
|
96
|
+
else:
|
|
97
|
+
raise ValueError("Either pif_path must be provided here or in config.")
|
|
98
|
+
|
|
99
|
+
if swid is None:
|
|
100
|
+
raise ValueError("swid must be provided to resolve PIF file name.")
|
|
101
|
+
|
|
102
|
+
if source is not None:
|
|
103
|
+
candidate_path = path / source
|
|
104
|
+
if candidate_path.is_dir():
|
|
105
|
+
path = candidate_path
|
|
106
|
+
|
|
107
|
+
revol = swid[:4]
|
|
108
|
+
revol_dir = path / revol
|
|
109
|
+
if revol_dir.is_dir():
|
|
110
|
+
path = revol_dir
|
|
111
|
+
|
|
112
|
+
candidate_files = [f for f in os.listdir(path) if f.startswith(swid)]
|
|
113
|
+
if len(candidate_files) == 0:
|
|
114
|
+
raise FileNotFoundError(f"No PIF file found for SWID {swid} in directory: {path}")
|
|
115
|
+
elif len(candidate_files) > 1:
|
|
116
|
+
raise FileNotFoundError(
|
|
117
|
+
f"Multiple PIF files found for SWID {swid} in directory: {path}\n"
|
|
118
|
+
f"Found: {candidate_files}\n"
|
|
119
|
+
"Please specify the exact file path."
|
|
120
|
+
)
|
|
121
|
+
resolved_path = path / candidate_files[0]
|
|
122
|
+
if not resolved_path.is_file():
|
|
123
|
+
raise FileNotFoundError(f"PIF file not found: {resolved_path}")
|
|
124
|
+
|
|
125
|
+
return str(resolved_path)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def resolve_event_path(
|
|
129
|
+
events_path: Optional[Union[str, Path]] = None, swid: Optional[str] = None, config: Optional[Config] = None
|
|
130
|
+
) -> str:
|
|
50
131
|
"""
|
|
51
132
|
Verify and resolve the events file path.
|
|
52
133
|
|
|
53
134
|
Parameters
|
|
54
135
|
----------
|
|
55
|
-
|
|
136
|
+
events_path : str or Path, optional
|
|
56
137
|
File path or directory path containing events file.
|
|
57
138
|
If directory, searches for files containing 'isgri_events'.
|
|
139
|
+
swid : str, optional
|
|
140
|
+
Science Window ID to construct default event path. Requires events_path to be None and defined archive_path in Config.
|
|
141
|
+
config : Config, optional
|
|
142
|
+
Configuration object containing archive_path.
|
|
58
143
|
|
|
59
144
|
Returns
|
|
60
145
|
-------
|
|
@@ -80,16 +165,34 @@ def verify_events_path(path: Union[str, Path]) -> str:
|
|
|
80
165
|
>>> # Will raise error if multiple files
|
|
81
166
|
>>> verify_events_path("/data/scw/") # Multiple SCWs present
|
|
82
167
|
FileNotFoundError: Multiple isgri_events files found...
|
|
168
|
+
|
|
169
|
+
Notes
|
|
170
|
+
-----
|
|
171
|
+
- If both events_path and swid are provided, events_path takes precedence.
|
|
172
|
+
- If neither is provided, raises ValueError.
|
|
173
|
+
- Using swid requires archive_path in Config and constructs path as:
|
|
174
|
+
{archive_path}/{revol}/{swid}/isgri_events.fits.gz
|
|
83
175
|
"""
|
|
84
|
-
|
|
176
|
+
if events_path is not None:
|
|
177
|
+
path = Path(events_path)
|
|
178
|
+
if path.is_file():
|
|
179
|
+
return verify_events_file(path)
|
|
180
|
+
elif not path.is_dir():
|
|
181
|
+
raise FileNotFoundError(f"Events path does not exist: {path}")
|
|
182
|
+
elif config is not None and swid is not None:
|
|
183
|
+
if config.archive_path is None:
|
|
184
|
+
raise ValueError("archive_path must be defined in config to resolve events path using swid.")
|
|
185
|
+
path = Path(config.archive_path)
|
|
186
|
+
if not path.is_dir():
|
|
187
|
+
raise FileNotFoundError(f"Archive path from config is not a directory: {path}")
|
|
188
|
+
else:
|
|
189
|
+
raise ValueError("Either events_path must be provided here or in config along with swid.")
|
|
85
190
|
|
|
86
|
-
if
|
|
87
|
-
resolved_path = str(path)
|
|
88
|
-
elif path.is_dir():
|
|
191
|
+
if swid is None:
|
|
89
192
|
candidate_files = [f for f in os.listdir(path) if "isgri_events" in f]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
193
|
+
if len(candidate_files) == 1:
|
|
194
|
+
resolved_path = path / candidate_files[0]
|
|
195
|
+
return verify_events_file(resolved_path)
|
|
93
196
|
elif len(candidate_files) > 1:
|
|
94
197
|
raise FileNotFoundError(
|
|
95
198
|
f"Multiple isgri_events files found in directory: {path}\n"
|
|
@@ -97,21 +200,68 @@ def verify_events_path(path: Union[str, Path]) -> str:
|
|
|
97
200
|
"Please specify the exact file path."
|
|
98
201
|
)
|
|
99
202
|
else:
|
|
100
|
-
|
|
203
|
+
raise FileNotFoundError(f"No isgri_events file found in directory: {path}")
|
|
204
|
+
|
|
205
|
+
revol = swid[:4]
|
|
206
|
+
revol_dir = path / revol
|
|
207
|
+
if revol_dir.is_dir():
|
|
208
|
+
path = revol_dir
|
|
209
|
+
candidate_dirs = [d for d in os.listdir(path) if d.startswith(swid) and (path / d).is_dir()]
|
|
210
|
+
if len(candidate_dirs) == 1:
|
|
211
|
+
path = path / candidate_dirs[0]
|
|
212
|
+
elif len(candidate_dirs) > 1:
|
|
213
|
+
raise FileNotFoundError(
|
|
214
|
+
f"Multiple directories found for SWID {swid} in directory: {path}\n"
|
|
215
|
+
f"Found: {candidate_dirs}\n"
|
|
216
|
+
"Please specify the exact file path."
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
raise FileNotFoundError(f"No directory found for SWID {swid} in directory: {path}")
|
|
220
|
+
|
|
221
|
+
candidate_files = [f for f in os.listdir(path) if "isgri_events" in f]
|
|
222
|
+
|
|
223
|
+
if len(candidate_files) == 0:
|
|
224
|
+
raise FileNotFoundError(f"No isgri_events file found in directory: {path}")
|
|
225
|
+
elif len(candidate_files) > 1:
|
|
226
|
+
raise FileNotFoundError(
|
|
227
|
+
f"Multiple isgri_events files found in directory: {path}\n"
|
|
228
|
+
f"Found: {candidate_files}\n"
|
|
229
|
+
"Please specify the exact file path."
|
|
230
|
+
)
|
|
101
231
|
else:
|
|
102
|
-
|
|
232
|
+
resolved_path = str(path / candidate_files[0])
|
|
233
|
+
|
|
234
|
+
return verify_events_file(resolved_path)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def verify_events_file(events_path: Union[str, Path]) -> str:
|
|
238
|
+
"""
|
|
239
|
+
Verify that the provided events file path is valid.
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
events_path : str or Path
|
|
243
|
+
Path to events FITS file.
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
str
|
|
247
|
+
Validated absolute path to events FITS file.
|
|
248
|
+
Raises
|
|
249
|
+
------
|
|
250
|
+
FileNotFoundError
|
|
251
|
+
If events file not found.
|
|
252
|
+
ValueError
|
|
253
|
+
If required FITS extension missing.
|
|
254
|
+
"""
|
|
103
255
|
|
|
104
|
-
# Verify FITS structure
|
|
105
256
|
try:
|
|
106
|
-
with fits.open(
|
|
257
|
+
with fits.open(events_path) as hdu:
|
|
107
258
|
if "ISGR-EVTS-ALL" not in hdu:
|
|
108
|
-
raise ValueError(f"Invalid events file: ISGR-EVTS-ALL extension not found in {
|
|
259
|
+
raise ValueError(f"Invalid events file: ISGR-EVTS-ALL extension not found in {events_path}")
|
|
109
260
|
except Exception as e:
|
|
110
261
|
if isinstance(e, (FileNotFoundError, ValueError)):
|
|
111
262
|
raise
|
|
112
|
-
raise ValueError(f"Cannot open FITS file {
|
|
113
|
-
|
|
114
|
-
return resolved_path
|
|
263
|
+
raise ValueError(f"Cannot open FITS file {events_path}: {e}")
|
|
264
|
+
return str(events_path)
|
|
115
265
|
|
|
116
266
|
|
|
117
267
|
def load_isgri_events(events_path: Union[str, Path]) -> Tuple[Table, NDArray[np.float64], Dict]:
|
|
@@ -165,9 +315,8 @@ def load_isgri_events(events_path: Union[str, Path]) -> Tuple[Table, NDArray[np.
|
|
|
165
315
|
--------
|
|
166
316
|
load_isgri_pif : Apply detector response weighting
|
|
167
317
|
"""
|
|
168
|
-
confirmed_path = verify_events_path(events_path)
|
|
169
318
|
|
|
170
|
-
with fits.open(
|
|
319
|
+
with fits.open(events_path) as hdu:
|
|
171
320
|
# Load events
|
|
172
321
|
events_data = hdu["ISGR-EVTS-ALL"].data
|
|
173
322
|
header = hdu["ISGR-EVTS-ALL"].header
|
|
@@ -281,7 +430,6 @@ def merge_metadata(events_metadata: Dict, pif_metadata: Dict) -> Dict:
|
|
|
281
430
|
def load_isgri_pif(
|
|
282
431
|
pif_path: Union[str, Path],
|
|
283
432
|
events: Table,
|
|
284
|
-
pif_threshold: float = 0.5,
|
|
285
433
|
pif_extension: int = -1,
|
|
286
434
|
) -> Tuple[Table, NDArray[np.float64], Dict]:
|
|
287
435
|
"""
|
|
@@ -296,9 +444,6 @@ def load_isgri_pif(
|
|
|
296
444
|
Path to PIF FITS file.
|
|
297
445
|
events : Table
|
|
298
446
|
Events table from load_isgri_events() with DETZ, DETY columns.
|
|
299
|
-
pif_threshold : float, default 0.5
|
|
300
|
-
Minimum PIF value to keep event (0.0-1.0).
|
|
301
|
-
Higher values = only well-illuminated pixels.
|
|
302
447
|
pif_extension : int, default -1
|
|
303
448
|
FITS extension index containing PIF data.
|
|
304
449
|
-1 = last extension (typical).
|
|
@@ -352,12 +497,8 @@ def load_isgri_pif(
|
|
|
352
497
|
See Also
|
|
353
498
|
--------
|
|
354
499
|
load_isgri_events : Load event data
|
|
355
|
-
apply_pif_mask : Apply PIF filtering
|
|
356
500
|
coding_fraction : Calculate coded fraction
|
|
357
501
|
"""
|
|
358
|
-
if not (0 <= pif_threshold <= 1):
|
|
359
|
-
raise ValueError(f"pif_threshold must be in [0, 1], got {pif_threshold}")
|
|
360
|
-
|
|
361
502
|
pif_path = Path(pif_path)
|
|
362
503
|
if not pif_path.exists():
|
|
363
504
|
raise FileNotFoundError(f"PIF file not found: {pif_path}")
|
|
@@ -387,6 +528,6 @@ def load_isgri_pif(
|
|
|
387
528
|
pif_metadata["No_Modules"] = estimate_active_modules(pif_file)
|
|
388
529
|
|
|
389
530
|
# Apply PIF mask
|
|
390
|
-
|
|
531
|
+
pif_weights = pif_file[events["DETZ"], events["DETY"]]
|
|
391
532
|
|
|
392
|
-
return
|
|
533
|
+
return events, pif_weights, pif_metadata
|
isgri/utils/lightcurve.py
CHANGED
|
@@ -32,8 +32,16 @@ from numpy.typing import NDArray
|
|
|
32
32
|
from typing import Optional, Union, Tuple, List
|
|
33
33
|
from pathlib import Path
|
|
34
34
|
import os
|
|
35
|
-
from .file_loaders import
|
|
35
|
+
from .file_loaders import (
|
|
36
|
+
resolve_event_path,
|
|
37
|
+
load_isgri_events,
|
|
38
|
+
resolve_pif_path,
|
|
39
|
+
load_isgri_pif,
|
|
40
|
+
default_pif_metadata,
|
|
41
|
+
merge_metadata,
|
|
42
|
+
)
|
|
36
43
|
from .pif import DETZ_BOUNDS, DETY_BOUNDS
|
|
44
|
+
from ..config import Config
|
|
37
45
|
|
|
38
46
|
|
|
39
47
|
class LightCurve:
|
|
@@ -103,7 +111,9 @@ class LightCurve:
|
|
|
103
111
|
detz: NDArray[np.float64],
|
|
104
112
|
weights: NDArray[np.float64],
|
|
105
113
|
metadata: dict,
|
|
106
|
-
|
|
114
|
+
use_pif: bool = True,
|
|
115
|
+
pif_threshold: float = 0.5,
|
|
116
|
+
):
|
|
107
117
|
"""
|
|
108
118
|
Initialize LightCurve instance.
|
|
109
119
|
|
|
@@ -133,14 +143,19 @@ class LightCurve:
|
|
|
133
143
|
self.detz = detz
|
|
134
144
|
self.weights = weights
|
|
135
145
|
self.metadata = metadata
|
|
146
|
+
self.use_pif = bool(use_pif and np.any(weights != 1.0))
|
|
147
|
+
self.pif_threshold = pif_threshold
|
|
148
|
+
self._cached_weights = None
|
|
149
|
+
self._cached_pif_settings = (self.use_pif, self.pif_threshold)
|
|
136
150
|
|
|
137
151
|
@classmethod
|
|
138
152
|
def load_data(
|
|
139
153
|
cls,
|
|
140
154
|
events_path: Optional[Union[str, Path]] = None,
|
|
141
155
|
pif_path: Optional[Union[str, Path]] = None,
|
|
142
|
-
|
|
156
|
+
swid: Optional[str] = None,
|
|
143
157
|
source: Optional[str] = None,
|
|
158
|
+
use_pif: bool = True,
|
|
144
159
|
pif_threshold: float = 0.5,
|
|
145
160
|
pif_extension: int = -1,
|
|
146
161
|
) -> "LightCurve":
|
|
@@ -150,7 +165,7 @@ class LightCurve:
|
|
|
150
165
|
Args:
|
|
151
166
|
events_path (str): The path to the events file or directory.
|
|
152
167
|
pif_path (str, optional): The path to the PIF file. Defaults to None.
|
|
153
|
-
|
|
168
|
+
swid (str, optional): Science Window ID for auto-path resolution. Defaults to None.
|
|
154
169
|
source (str, optional): Source name for auto-path resolution. Defaults to None.
|
|
155
170
|
pif_threshold (float, optional): The PIF threshold value. Defaults to 0.5.
|
|
156
171
|
pif_extension (int, optional): PIF file extension index. Defaults to -1.
|
|
@@ -158,12 +173,18 @@ class LightCurve:
|
|
|
158
173
|
Returns:
|
|
159
174
|
LightCurve: An instance of the LightCurve class.
|
|
160
175
|
"""
|
|
161
|
-
|
|
162
|
-
|
|
176
|
+
if swid is not None or source is not None:
|
|
177
|
+
cfg = Config()
|
|
178
|
+
else:
|
|
179
|
+
cfg = None
|
|
180
|
+
|
|
181
|
+
confirmed_events_path = resolve_event_path(events_path, swid, cfg)
|
|
182
|
+
events, gtis, metadata = load_isgri_events(confirmed_events_path)
|
|
183
|
+
if pif_path is not None or source is not None:
|
|
163
184
|
if pif_threshold < 0 or pif_threshold > 1:
|
|
164
185
|
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,
|
|
186
|
+
pif_path = resolve_pif_path(pif_path, source, metadata["SWID"], cfg)
|
|
187
|
+
events, weights, metadata_pif = load_isgri_pif(pif_path, events, pif_extension)
|
|
167
188
|
else:
|
|
168
189
|
weights = np.ones(len(events))
|
|
169
190
|
metadata_pif = default_pif_metadata()
|
|
@@ -172,7 +193,7 @@ class LightCurve:
|
|
|
172
193
|
time = events["TIME"]
|
|
173
194
|
energies = events["ISGRI_ENERGY"]
|
|
174
195
|
dety, detz = events["DETY"], events["DETZ"]
|
|
175
|
-
return cls(time, energies, gtis, dety, detz, weights, metadata)
|
|
196
|
+
return cls(time, energies, gtis, dety, detz, weights, metadata, use_pif, pif_threshold)
|
|
176
197
|
|
|
177
198
|
def rebin(
|
|
178
199
|
self,
|
|
@@ -181,6 +202,8 @@ class LightCurve:
|
|
|
181
202
|
emax: float,
|
|
182
203
|
local_time: bool = True,
|
|
183
204
|
custom_mask: Optional[NDArray[np.bool_]] = None,
|
|
205
|
+
use_pif: Optional[bool] = None,
|
|
206
|
+
pif_threshold: Optional[float] = None,
|
|
184
207
|
) -> Tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
185
208
|
"""
|
|
186
209
|
Rebins the events with the specified bin size and energy range.
|
|
@@ -224,10 +247,13 @@ class LightCurve:
|
|
|
224
247
|
# Create bins
|
|
225
248
|
bins, binsize_actual = self._create_bins(binsize, time, t0, local_time)
|
|
226
249
|
|
|
250
|
+
# Get weights
|
|
251
|
+
weights = self._get_weights(use_pif, pif_threshold)
|
|
252
|
+
|
|
227
253
|
# Apply filters
|
|
228
254
|
mask = self._create_event_mask(emin, emax, custom_mask)
|
|
229
255
|
time_filtered = time[mask]
|
|
230
|
-
weights_filtered =
|
|
256
|
+
weights_filtered = weights[mask]
|
|
231
257
|
|
|
232
258
|
# Histogram
|
|
233
259
|
counts, bin_edges = np.histogram(time_filtered, bins=bins, weights=weights_filtered)
|
|
@@ -265,6 +291,42 @@ class LightCurve:
|
|
|
265
291
|
|
|
266
292
|
return bins, binsize_actual
|
|
267
293
|
|
|
294
|
+
def _get_weights(
|
|
295
|
+
self, use_pif: Optional[bool] = None, pif_threshold: Optional[float] = None
|
|
296
|
+
) -> NDArray[np.float64]:
|
|
297
|
+
"""
|
|
298
|
+
Get event weights based on PIF settings.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
use_pif : bool, optional
|
|
303
|
+
Override instance use_pif setting
|
|
304
|
+
pif_threshold : float, optional
|
|
305
|
+
Override instance pif_threshold setting
|
|
306
|
+
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
weights : ndarray
|
|
310
|
+
Event weights (0 if below threshold, PIF value otherwise, or 1.0 if no PIF)
|
|
311
|
+
"""
|
|
312
|
+
if use_pif is None:
|
|
313
|
+
use_pif = self.use_pif
|
|
314
|
+
if pif_threshold is None:
|
|
315
|
+
pif_threshold = self.pif_threshold
|
|
316
|
+
|
|
317
|
+
cache_key = (use_pif, pif_threshold)
|
|
318
|
+
if cache_key == self._cached_pif_settings and self._cached_weights is not None:
|
|
319
|
+
return self._cached_weights
|
|
320
|
+
|
|
321
|
+
if not use_pif:
|
|
322
|
+
weights = np.ones_like(self.weights)
|
|
323
|
+
else:
|
|
324
|
+
weights = np.where(self.weights > pif_threshold, self.weights, 0.0)
|
|
325
|
+
|
|
326
|
+
self._cached_pif_settings = cache_key
|
|
327
|
+
self._cached_weights = weights
|
|
328
|
+
return weights
|
|
329
|
+
|
|
268
330
|
def _create_event_mask(
|
|
269
331
|
self,
|
|
270
332
|
emin: float,
|
|
@@ -298,6 +360,8 @@ class LightCurve:
|
|
|
298
360
|
emax: float,
|
|
299
361
|
local_time: bool = True,
|
|
300
362
|
custom_mask: Optional[NDArray[np.bool_]] = None,
|
|
363
|
+
use_pif: Optional[bool] = None,
|
|
364
|
+
pif_threshold: Optional[float] = None,
|
|
301
365
|
) -> Tuple[NDArray[np.float64], List[NDArray[np.float64]]]:
|
|
302
366
|
"""
|
|
303
367
|
Rebins the events by all 8 detector modules with the specified bin size and energy range.
|
|
@@ -337,7 +401,9 @@ class LightCurve:
|
|
|
337
401
|
time_filtered = time[energy_mask]
|
|
338
402
|
dety_filtered = self.dety[energy_mask]
|
|
339
403
|
detz_filtered = self.detz[energy_mask]
|
|
340
|
-
|
|
404
|
+
|
|
405
|
+
weights = self._get_weights(use_pif, pif_threshold)
|
|
406
|
+
weights_filtered = weights[energy_mask]
|
|
341
407
|
|
|
342
408
|
# Compute module indices using digitize
|
|
343
409
|
dety_bin = np.digitize(dety_filtered, DETY_BOUNDS) - 1 # 0 or 1
|
|
@@ -358,6 +424,8 @@ class LightCurve:
|
|
|
358
424
|
emin: float,
|
|
359
425
|
emax: float,
|
|
360
426
|
local_time: bool = True,
|
|
427
|
+
use_pif: Optional[bool] = None,
|
|
428
|
+
pif_threshold: Optional[float] = None,
|
|
361
429
|
) -> float:
|
|
362
430
|
"""
|
|
363
431
|
Calculates the counts in the specified time and energy range.
|
|
@@ -373,7 +441,29 @@ class LightCurve:
|
|
|
373
441
|
float: The total counts in the specified range.
|
|
374
442
|
"""
|
|
375
443
|
time = self.local_time if local_time else self.time
|
|
376
|
-
|
|
444
|
+
weights = self._get_weights(use_pif, pif_threshold)
|
|
445
|
+
return np.sum(weights[(time >= t1) & (time < t2) & (self.energies >= emin) & (self.energies < emax)])
|
|
446
|
+
|
|
447
|
+
def get(
|
|
448
|
+
self, use_pif: Optional[bool] = None, pif_threshold: Optional[float] = None
|
|
449
|
+
) -> Tuple[
|
|
450
|
+
NDArray[np.float64], NDArray[np.float64], NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]
|
|
451
|
+
]:
|
|
452
|
+
"""
|
|
453
|
+
Returns the time, energies, dety, detz, weights arrays with filtering above PIF threshold applied.
|
|
454
|
+
Args:
|
|
455
|
+
use_pif (bool, optional): Override instance use_pif setting
|
|
456
|
+
pif_threshold (float, optional): Override instance pif_threshold setting
|
|
457
|
+
"""
|
|
458
|
+
weights = self._get_weights(use_pif, pif_threshold)
|
|
459
|
+
mask = weights > 0.0
|
|
460
|
+
return (
|
|
461
|
+
self.time[mask],
|
|
462
|
+
self.energies[mask],
|
|
463
|
+
self.dety[mask],
|
|
464
|
+
self.detz[mask],
|
|
465
|
+
weights[mask],
|
|
466
|
+
)
|
|
377
467
|
|
|
378
468
|
def ijd2loc(self, ijd_time: Union[float, NDArray[np.float64]]) -> Union[float, NDArray[np.float64]]:
|
|
379
469
|
"""
|
|
@@ -405,5 +495,5 @@ class LightCurve:
|
|
|
405
495
|
f"LightCurve(n_events={len(self.time)}, "
|
|
406
496
|
f"time_range=({self.time[0]:.3f}, {self.time[-1]:.3f}) IJD, "
|
|
407
497
|
f"energy_range=({self.energies.min():.1f}, {self.energies.max():.1f}) keV, "
|
|
408
|
-
f"scw={self.metadata.get('SWID', 'Unknown')})"
|
|
498
|
+
f"scw={self.metadata.get('SWID', 'Unknown')}), use_pif={self.use_pif}, pif_threshold={self.pif_threshold})"
|
|
409
499
|
)
|
isgri/utils/pif.py
CHANGED
|
@@ -36,6 +36,7 @@ import numpy as np
|
|
|
36
36
|
from numpy.typing import NDArray
|
|
37
37
|
from typing import Tuple
|
|
38
38
|
from astropy.table import Table
|
|
39
|
+
import warnings
|
|
39
40
|
|
|
40
41
|
# ISGRI detector module boundaries (mm coordinates)
|
|
41
42
|
# 8 modules total: 4 rows × 2 columns
|
|
@@ -99,9 +100,16 @@ def apply_pif_mask(
|
|
|
99
100
|
events: Table,
|
|
100
101
|
pif_threshold: float = 0.5,
|
|
101
102
|
) -> Tuple[Table, NDArray[np.float64]]:
|
|
103
|
+
|
|
102
104
|
"""
|
|
103
105
|
Filter events by PIF threshold and return PIF weights.
|
|
104
106
|
|
|
107
|
+
.. deprecated::
|
|
108
|
+
This function is deprecated. PIF filtering is now handled
|
|
109
|
+
automatically by LightCurve methods using the `use_pif` and
|
|
110
|
+
`pif_threshold` attributes. Use `LightCurve.load_data()` instead.
|
|
111
|
+
|
|
112
|
+
|
|
105
113
|
Events with PIF < threshold are removed. Remaining events are
|
|
106
114
|
weighted by their PIF values for response correction.
|
|
107
115
|
|
|
@@ -138,6 +146,12 @@ def apply_pif_mask(
|
|
|
138
146
|
>>> print(f"Kept {len(filtered)}/{len(events)} events")
|
|
139
147
|
>>> print(f"Mean weight: {weights.mean():.3f}")
|
|
140
148
|
"""
|
|
149
|
+
warnings.warn(
|
|
150
|
+
"apply_pif_mask() is deprecated. Use LightCurve.load_data() with "
|
|
151
|
+
"use_pif and pif_threshold parameters instead.",
|
|
152
|
+
DeprecationWarning,
|
|
153
|
+
stacklevel=2,
|
|
154
|
+
)
|
|
141
155
|
# Validate inputs
|
|
142
156
|
if not (0 <= pif_threshold <= 1):
|
|
143
157
|
raise ValueError(f"pif_threshold must be in [0, 1], got {pif_threshold}")
|
isgri/utils/quality.py
CHANGED
|
@@ -366,9 +366,10 @@ class QualityMetrics:
|
|
|
366
366
|
time, counts = data["time"], data["counts"]
|
|
367
367
|
|
|
368
368
|
if gtis is None:
|
|
369
|
-
if self.lightcurve is None:
|
|
370
|
-
|
|
371
|
-
|
|
369
|
+
if self.lightcurve is not None:
|
|
370
|
+
gtis = self.lightcurve.gtis
|
|
371
|
+
if gtis is None or len(gtis) == 0:
|
|
372
|
+
raise ValueError("GTIs must be provided or available in lightcurve")
|
|
372
373
|
|
|
373
374
|
# Check for overlap
|
|
374
375
|
if gtis[0, 0] > time[-1] or gtis[-1, 1] < time[0]:
|