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/file_loaders.py
CHANGED
|
@@ -1,159 +1,392 @@
|
|
|
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
|
-
|
|
1
|
+
"""
|
|
2
|
+
ISGRI Data File Loaders
|
|
3
|
+
========================
|
|
4
|
+
|
|
5
|
+
Load INTEGRAL/ISGRI event files and detector response (PIF) files.
|
|
6
|
+
|
|
7
|
+
Functions
|
|
8
|
+
---------
|
|
9
|
+
load_isgri_events : Load photon events from FITS file
|
|
10
|
+
load_isgri_pif : Load and apply detector response (PIF) file
|
|
11
|
+
verify_events_path : Validate and resolve events file path
|
|
12
|
+
default_pif_metadata : Create default PIF metadata
|
|
13
|
+
merge_metadata : Combine events and PIF metadata
|
|
14
|
+
|
|
15
|
+
Examples
|
|
16
|
+
--------
|
|
17
|
+
>>> from isgri.utils import load_isgri_events, load_isgri_pif
|
|
18
|
+
>>>
|
|
19
|
+
>>> # Load events
|
|
20
|
+
>>> events, gtis, metadata = load_isgri_events("isgri_events.fits")
|
|
21
|
+
>>> print(f"Loaded {len(events)} events")
|
|
22
|
+
>>>
|
|
23
|
+
>>> # Apply PIF weighting
|
|
24
|
+
>>> filtered_events, pif_weights, pif_meta = load_isgri_pif(
|
|
25
|
+
... "pif_model.fits",
|
|
26
|
+
... events,
|
|
27
|
+
... pif_threshold=0.5
|
|
28
|
+
... )
|
|
29
|
+
>>> print(f"Kept {len(filtered_events)}/{len(events)} events")
|
|
30
|
+
>>> print(f"Coding fraction: {pif_meta['cod']:.2%}")
|
|
31
|
+
|
|
32
|
+
Notes
|
|
33
|
+
-----
|
|
34
|
+
- Events files contain photon arrival times, energies, and detector positions
|
|
35
|
+
- PIF files contain detector response maps for specific source positions
|
|
36
|
+
- PIF weighting corrects for shadowing by the coded mask
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from astropy.io import fits
|
|
40
|
+
from astropy.table import Table
|
|
41
|
+
import numpy as np
|
|
42
|
+
from numpy.typing import NDArray
|
|
43
|
+
from typing import Tuple, Dict, Optional, Union
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
import os
|
|
46
|
+
from .pif import apply_pif_mask, coding_fraction, estimate_active_modules
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def verify_events_path(path: Union[str, Path]) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Verify and resolve the events file path.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
path : str or Path
|
|
56
|
+
File path or directory path containing events file.
|
|
57
|
+
If directory, searches for files containing 'isgri_events'.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
resolved_path : str
|
|
62
|
+
Resolved absolute path to valid events file.
|
|
63
|
+
|
|
64
|
+
Raises
|
|
65
|
+
------
|
|
66
|
+
FileNotFoundError
|
|
67
|
+
If path doesn't exist, no events file found, or multiple
|
|
68
|
+
candidates found in directory.
|
|
69
|
+
ValueError
|
|
70
|
+
If ISGR-EVTS-ALL extension not found in file.
|
|
71
|
+
|
|
72
|
+
Examples
|
|
73
|
+
--------
|
|
74
|
+
>>> # Direct file path
|
|
75
|
+
>>> path = verify_events_path("isgri_events.fits")
|
|
76
|
+
|
|
77
|
+
>>> # Directory with single events file
|
|
78
|
+
>>> path = verify_events_path("/data/scw/0234/001200340010.001")
|
|
79
|
+
|
|
80
|
+
>>> # Will raise error if multiple files
|
|
81
|
+
>>> verify_events_path("/data/scw/") # Multiple SCWs present
|
|
82
|
+
FileNotFoundError: Multiple isgri_events files found...
|
|
83
|
+
"""
|
|
84
|
+
path = Path(path)
|
|
85
|
+
|
|
86
|
+
if path.is_file():
|
|
87
|
+
resolved_path = str(path)
|
|
88
|
+
elif path.is_dir():
|
|
89
|
+
candidate_files = [f for f in os.listdir(path) if "isgri_events" in f]
|
|
90
|
+
|
|
91
|
+
if len(candidate_files) == 0:
|
|
92
|
+
raise FileNotFoundError(f"No isgri_events file found in directory: {path}")
|
|
93
|
+
elif len(candidate_files) > 1:
|
|
94
|
+
raise FileNotFoundError(
|
|
95
|
+
f"Multiple isgri_events files found in directory: {path}\n"
|
|
96
|
+
f"Found: {candidate_files}\n"
|
|
97
|
+
"Please specify the exact file path."
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
resolved_path = str(path / candidate_files[0])
|
|
101
|
+
else:
|
|
102
|
+
raise FileNotFoundError(f"Path does not exist: {path}")
|
|
103
|
+
|
|
104
|
+
# Verify FITS structure
|
|
105
|
+
try:
|
|
106
|
+
with fits.open(resolved_path) as hdu:
|
|
107
|
+
if "ISGR-EVTS-ALL" not in hdu:
|
|
108
|
+
raise ValueError(f"Invalid events file: ISGR-EVTS-ALL extension not found in {resolved_path}")
|
|
109
|
+
except Exception as e:
|
|
110
|
+
if isinstance(e, (FileNotFoundError, ValueError)):
|
|
111
|
+
raise
|
|
112
|
+
raise ValueError(f"Cannot open FITS file {resolved_path}: {e}")
|
|
113
|
+
|
|
114
|
+
return resolved_path
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def load_isgri_events(events_path: Union[str, Path]) -> Tuple[Table, NDArray[np.float64], Dict]:
|
|
118
|
+
"""
|
|
119
|
+
Load ISGRI photon events from FITS file.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
events_path : str or Path
|
|
124
|
+
Path to events FITS file or directory containing it.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
events : Table
|
|
129
|
+
Astropy Table with columns:
|
|
130
|
+
- TIME : Event time in IJD
|
|
131
|
+
- ISGRI_ENERGY : Energy in keV
|
|
132
|
+
- DETY : Y detector coordinate (0-129)
|
|
133
|
+
- DETZ : Z detector coordinate (0-133)
|
|
134
|
+
gtis : ndarray, shape (N, 2) or None
|
|
135
|
+
Good Time Intervals [start, stop] pairs in IJD.
|
|
136
|
+
If no GTI extension found, returns None.
|
|
137
|
+
metadata : dict
|
|
138
|
+
Header metadata with keys:
|
|
139
|
+
- REVOL : Revolution number
|
|
140
|
+
- SWID : Science Window ID
|
|
141
|
+
- TSTART, TSTOP : Start/stop times (IJD)
|
|
142
|
+
- RA_SCX, DEC_SCX : Pointing axis coordinates
|
|
143
|
+
- RA_SCZ, DEC_SCZ : Z-axis coordinates
|
|
144
|
+
- NoEVTS : Number of events in file
|
|
145
|
+
|
|
146
|
+
Raises
|
|
147
|
+
------
|
|
148
|
+
FileNotFoundError
|
|
149
|
+
If events file not found or invalid.
|
|
150
|
+
ValueError
|
|
151
|
+
If required FITS extension missing.
|
|
152
|
+
|
|
153
|
+
Examples
|
|
154
|
+
--------
|
|
155
|
+
>>> events, gtis, meta = load_isgri_events("isgri_events.fits")
|
|
156
|
+
>>> print(f"Loaded {len(events)} events")
|
|
157
|
+
>>> print(f"Time range: {meta['TSTART']:.1f} - {meta['TSTOP']:.1f} IJD")
|
|
158
|
+
>>> print(f"GTIs: {len(gtis)} intervals")
|
|
159
|
+
|
|
160
|
+
>>> # Check energy range
|
|
161
|
+
>>> print(f"Energy: {events['ISGRI_ENERGY'].min():.1f} - "
|
|
162
|
+
... f"{events['ISGRI_ENERGY'].max():.1f} keV")
|
|
163
|
+
|
|
164
|
+
See Also
|
|
165
|
+
--------
|
|
166
|
+
load_isgri_pif : Apply detector response weighting
|
|
167
|
+
"""
|
|
168
|
+
confirmed_path = verify_events_path(events_path)
|
|
169
|
+
|
|
170
|
+
with fits.open(confirmed_path) as hdu:
|
|
171
|
+
# Load events
|
|
172
|
+
events_data = hdu["ISGR-EVTS-ALL"].data
|
|
173
|
+
header = hdu["ISGR-EVTS-ALL"].header
|
|
174
|
+
|
|
175
|
+
# Extract metadata
|
|
176
|
+
metadata = {
|
|
177
|
+
"REVOL": header.get("REVOL"),
|
|
178
|
+
"SWID": header.get("SWID"),
|
|
179
|
+
"TSTART": header.get("TSTART"),
|
|
180
|
+
"TSTOP": header.get("TSTOP"),
|
|
181
|
+
"TELAPSE": header.get("TELAPSE"),
|
|
182
|
+
"OBT_TSTART": header.get("OBTSTART"),
|
|
183
|
+
"OBT_TSTOP": header.get("OBTEND"),
|
|
184
|
+
"RA_SCX": header.get("RA_SCX"),
|
|
185
|
+
"DEC_SCX": header.get("DEC_SCX"),
|
|
186
|
+
"RA_SCZ": header.get("RA_SCZ"),
|
|
187
|
+
"DEC_SCZ": header.get("DEC_SCZ"),
|
|
188
|
+
"NoEVTS": header.get("NAXIS2"),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Load GTIs
|
|
192
|
+
try:
|
|
193
|
+
gti_data = hdu["IBIS-GNRL-GTI"].data
|
|
194
|
+
gtis = np.column_stack([gti_data["START"], gti_data["STOP"]])
|
|
195
|
+
except (KeyError, IndexError):
|
|
196
|
+
# No GTI extension - return empty GTI
|
|
197
|
+
# t_start = events_data["TIME"][0]
|
|
198
|
+
# t_stop = events_data["TIME"][-1]
|
|
199
|
+
# gtis = np.array([[t_start, t_stop]])
|
|
200
|
+
gtis = None
|
|
201
|
+
|
|
202
|
+
# Filter bad events (SELECT_FLAG != 0)
|
|
203
|
+
good_mask = events_data["SELECT_FLAG"] == 0
|
|
204
|
+
events = Table(events_data[good_mask])
|
|
205
|
+
|
|
206
|
+
return events, gtis, metadata
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def default_pif_metadata() -> Dict:
|
|
210
|
+
"""
|
|
211
|
+
Create default PIF metadata for cases without PIF file.
|
|
212
|
+
|
|
213
|
+
Used when no PIF weighting is applied. All modules are
|
|
214
|
+
considered active with unit weight.
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
metadata : dict
|
|
219
|
+
Default PIF metadata with:
|
|
220
|
+
- SWID : None
|
|
221
|
+
- SRC_RA, SRC_DEC : None (no source position)
|
|
222
|
+
- Source_Name : None
|
|
223
|
+
- cod : None (no coding fraction)
|
|
224
|
+
- No_Modules : [True]*8 (all modules active)
|
|
225
|
+
|
|
226
|
+
Examples
|
|
227
|
+
--------
|
|
228
|
+
>>> meta = default_pif_metadata()
|
|
229
|
+
>>> print(meta['No_Modules'])
|
|
230
|
+
[True, True, True, True, True, True, True, True]
|
|
231
|
+
"""
|
|
232
|
+
return {
|
|
233
|
+
"SWID": None,
|
|
234
|
+
"SRC_RA": None,
|
|
235
|
+
"SRC_DEC": None,
|
|
236
|
+
"Source_Name": None,
|
|
237
|
+
"cod": None,
|
|
238
|
+
"No_Modules": [True] * 8, # All modules active
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def merge_metadata(events_metadata: Dict, pif_metadata: Dict) -> Dict:
|
|
243
|
+
"""
|
|
244
|
+
Merge events and PIF metadata dictionaries.
|
|
245
|
+
|
|
246
|
+
PIF metadata takes precedence except for SWID, which is
|
|
247
|
+
preserved from events metadata.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
events_metadata : dict
|
|
252
|
+
Metadata from events file.
|
|
253
|
+
pif_metadata : dict
|
|
254
|
+
Metadata from PIF file.
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
merged : dict
|
|
259
|
+
Combined metadata with PIF values overwriting events values
|
|
260
|
+
(except SWID).
|
|
261
|
+
|
|
262
|
+
Examples
|
|
263
|
+
--------
|
|
264
|
+
>>> events_meta = {'SWID': '023400100010', 'TSTART': 3000.0}
|
|
265
|
+
>>> pif_meta = {'SWID': '999999999999', 'SRC_RA': 83.63, 'SRC_DEC': 22.01}
|
|
266
|
+
>>> merged = merge_metadata(events_meta, pif_meta)
|
|
267
|
+
>>> print(merged['SWID']) # Preserved from events
|
|
268
|
+
023400100010
|
|
269
|
+
>>> print(merged['SRC_RA']) # From PIF
|
|
270
|
+
83.63
|
|
271
|
+
"""
|
|
272
|
+
merged = events_metadata.copy()
|
|
273
|
+
|
|
274
|
+
for key, value in pif_metadata.items():
|
|
275
|
+
if key != "SWID": # Preserve SWID from events
|
|
276
|
+
merged[key] = value
|
|
277
|
+
|
|
278
|
+
return merged
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def load_isgri_pif(
|
|
282
|
+
pif_path: Union[str, Path],
|
|
283
|
+
events: Table,
|
|
284
|
+
pif_threshold: float = 0.5,
|
|
285
|
+
pif_extension: int = -1,
|
|
286
|
+
) -> Tuple[Table, NDArray[np.float64], Dict]:
|
|
287
|
+
"""
|
|
288
|
+
Load PIF (Pixel Illumination Fraction) file and apply to events.
|
|
289
|
+
|
|
290
|
+
Filters events by PIF threshold and returns PIF weights for
|
|
291
|
+
detector response correction.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
pif_path : str or Path
|
|
296
|
+
Path to PIF FITS file.
|
|
297
|
+
events : Table
|
|
298
|
+
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
|
+
pif_extension : int, default -1
|
|
303
|
+
FITS extension index containing PIF data.
|
|
304
|
+
-1 = last extension (typical).
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
filtered_events : Table
|
|
309
|
+
Events with PIF >= threshold.
|
|
310
|
+
pif_weights : ndarray
|
|
311
|
+
PIF value for each filtered event (for response correction).
|
|
312
|
+
pif_metadata : dict
|
|
313
|
+
Metadata with keys:
|
|
314
|
+
- SWID : Science Window ID
|
|
315
|
+
- Source_ID, Source_Name : Source identifiers
|
|
316
|
+
- SRC_RA, SRC_DEC : Source position (degrees)
|
|
317
|
+
- cod : Coding fraction (0.0-1.0)
|
|
318
|
+
- No_Modules : Array of active module flags
|
|
319
|
+
|
|
320
|
+
Raises
|
|
321
|
+
------
|
|
322
|
+
ValueError
|
|
323
|
+
If PIF file has invalid shape (must be 134×130).
|
|
324
|
+
FileNotFoundError
|
|
325
|
+
If PIF file not found.
|
|
326
|
+
|
|
327
|
+
Examples
|
|
328
|
+
--------
|
|
329
|
+
>>> events, gtis, meta = load_isgri_events("events.fits")
|
|
330
|
+
>>> filtered, weights, pif_meta = load_isgri_pif(
|
|
331
|
+
... "pif_model.fits",
|
|
332
|
+
... events,
|
|
333
|
+
... pif_threshold=0.5
|
|
334
|
+
... )
|
|
335
|
+
>>>
|
|
336
|
+
>>> print(f"Filtered: {len(filtered)}/{len(events)} events")
|
|
337
|
+
>>> print(f"Coding fraction: {pif_meta['cod']:.2%}")
|
|
338
|
+
>>> print(f"Active modules: {np.sum(pif_meta['No_Modules'])}/8")
|
|
339
|
+
|
|
340
|
+
>>> # Use for light curve with response correction
|
|
341
|
+
>>> from isgri.utils import LightCurve
|
|
342
|
+
>>> lc = LightCurve(
|
|
343
|
+
... time=filtered['TIME'],
|
|
344
|
+
... energies=filtered['ISGRI_ENERGY'],
|
|
345
|
+
... dety=filtered['DETY'],
|
|
346
|
+
... detz=filtered['DETZ'],
|
|
347
|
+
... weights=weights,
|
|
348
|
+
... gtis=gtis,
|
|
349
|
+
... metadata={**meta, **pif_meta}
|
|
350
|
+
... )
|
|
351
|
+
|
|
352
|
+
See Also
|
|
353
|
+
--------
|
|
354
|
+
load_isgri_events : Load event data
|
|
355
|
+
apply_pif_mask : Apply PIF filtering
|
|
356
|
+
coding_fraction : Calculate coded fraction
|
|
357
|
+
"""
|
|
358
|
+
if not (0 <= pif_threshold <= 1):
|
|
359
|
+
raise ValueError(f"pif_threshold must be in [0, 1], got {pif_threshold}")
|
|
360
|
+
|
|
361
|
+
pif_path = Path(pif_path)
|
|
362
|
+
if not pif_path.exists():
|
|
363
|
+
raise FileNotFoundError(f"PIF file not found: {pif_path}")
|
|
364
|
+
|
|
365
|
+
# Load PIF file
|
|
366
|
+
with fits.open(pif_path) as hdu:
|
|
367
|
+
pif_file = np.array(hdu[pif_extension].data)
|
|
368
|
+
header = hdu[pif_extension].header
|
|
369
|
+
|
|
370
|
+
# Validate shape
|
|
371
|
+
if pif_file.shape != (134, 130):
|
|
372
|
+
raise ValueError(
|
|
373
|
+
f"Invalid PIF file shape: expected (134, 130), got {pif_file.shape}. " "File may be empty or corrupted."
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Extract metadata
|
|
377
|
+
pif_metadata = {
|
|
378
|
+
"SWID": header.get("SWID"),
|
|
379
|
+
"Source_ID": header.get("SOURCEID"),
|
|
380
|
+
"Source_Name": header.get("NAME"),
|
|
381
|
+
"SRC_RA": header.get("RA_OBJ"),
|
|
382
|
+
"SRC_DEC": header.get("DEC_OBJ"),
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
# Compute quality metrics
|
|
386
|
+
pif_metadata["cod"] = coding_fraction(pif_file, events)
|
|
387
|
+
pif_metadata["No_Modules"] = estimate_active_modules(pif_file)
|
|
388
|
+
|
|
389
|
+
# Apply PIF mask
|
|
390
|
+
filtered_events, pif_weights = apply_pif_mask(pif_file, events, pif_threshold)
|
|
391
|
+
|
|
392
|
+
return filtered_events, pif_weights, pif_metadata
|