isgri 0.2.0__tar.gz → 0.3.0__tar.gz
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-0.3.0/.python-version +1 -0
- {isgri-0.2.0 → isgri-0.3.0}/PKG-INFO +3 -3
- {isgri-0.2.0 → isgri-0.3.0}/demo/lightcurve_walkthrough.ipynb +8 -7
- {isgri-0.2.0 → isgri-0.3.0}/pyproject.toml +3 -3
- {isgri-0.2.0 → isgri-0.3.0}/src/isgri/utils/quality.py +23 -7
- {isgri-0.2.0 → isgri-0.3.0}/tests/utils/test_lightcurve.py +67 -0
- isgri-0.3.0/tests/utils/test_quality.py +268 -0
- isgri-0.2.0/tests/utils/test_quality.py +0 -39
- {isgri-0.2.0 → isgri-0.3.0}/.gitignore +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/LICENSE +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/README.md +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/demo/data/255900280010_isgri_model.fits.gz +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/demo/data/isgri_events.fits.gz +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/src/isgri/__init__.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/src/isgri/utils/__init__.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/src/isgri/utils/file_loaders.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/src/isgri/utils/lightcurve.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/src/isgri/utils/pif.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/src/isgri/utils/time_conversion.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/tests/__init__.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/tests/utils/__init__.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/tests/utils/test_file_loaders.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/tests/utils/test_pif.py +0 -0
- {isgri-0.2.0 → isgri-0.3.0}/tests/utils/test_time_conversion.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: isgri
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python package for INTEGRAL IBIS/ISGRI lightcurve analysis
|
|
5
5
|
Author: Dominik Patryk Pacholski
|
|
6
6
|
License: MIT
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
|
-
Requires-Dist: astropy
|
|
10
|
-
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: astropy
|
|
10
|
+
Requires-Dist: numpy
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
|
|
13
13
|
# isgri
|
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
},
|
|
244
244
|
{
|
|
245
245
|
"cell_type": "code",
|
|
246
|
-
"execution_count":
|
|
246
|
+
"execution_count": 3,
|
|
247
247
|
"id": "69106f8b",
|
|
248
248
|
"metadata": {},
|
|
249
249
|
"outputs": [
|
|
@@ -251,11 +251,12 @@
|
|
|
251
251
|
"name": "stdout",
|
|
252
252
|
"output_type": "stream",
|
|
253
253
|
"text": [
|
|
254
|
-
"Raw chisq reduced:
|
|
255
|
-
"Clipped chisq reduced:
|
|
256
|
-
"GTI chisq reduced:
|
|
257
|
-
"Module variability: [
|
|
258
|
-
"
|
|
254
|
+
"Raw chisq reduced: 10.04\n",
|
|
255
|
+
"Clipped chisq reduced: 1.00\n",
|
|
256
|
+
"GTI chisq reduced: 10.03\n",
|
|
257
|
+
"Module variability: (array([ 6359.549, 6926.603, 35261.777, 36130.625, 38277.758, 39590.93 ,\n",
|
|
258
|
+
" 33664.773, 35566.195], dtype=float32), array([2942, 3025, 3567, 3568, 3568, 3568, 3568, 3567]), array([ 6185.0806, 6649.9795, 91052.58 , 93023.62 , 97074.28 ,\n",
|
|
259
|
+
" 96712.97 , 91139.47 , 85202.89 ], dtype=float32))\n"
|
|
259
260
|
]
|
|
260
261
|
}
|
|
261
262
|
],
|
|
@@ -389,7 +390,7 @@
|
|
|
389
390
|
"name": "python",
|
|
390
391
|
"nbconvert_exporter": "python",
|
|
391
392
|
"pygments_lexer": "ipython3",
|
|
392
|
-
"version": "3.
|
|
393
|
+
"version": "3.10.19"
|
|
393
394
|
}
|
|
394
395
|
},
|
|
395
396
|
"nbformat": 4,
|
|
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "isgri"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
authors = [{name = "Dominik Patryk Pacholski"}]
|
|
9
9
|
license = {text = "MIT"}
|
|
10
10
|
description = "Python package for INTEGRAL IBIS/ISGRI lightcurve analysis"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"numpy
|
|
15
|
-
"astropy
|
|
14
|
+
"numpy",
|
|
15
|
+
"astropy",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[tool.setuptools.packages.find]
|
|
@@ -62,7 +62,7 @@ class QualityMetrics:
|
|
|
62
62
|
time, counts = self.lightcurve.rebin_by_modules(
|
|
63
63
|
binsize=self.binsize, emin=self.emin, emax=self.emax, local_time=self.local_time
|
|
64
64
|
)
|
|
65
|
-
module_data = {"time": time, "counts": counts}
|
|
65
|
+
module_data = {"time": time, "counts": np.asarray(counts)}
|
|
66
66
|
self.module_data = module_data
|
|
67
67
|
return module_data
|
|
68
68
|
|
|
@@ -71,20 +71,36 @@ class QualityMetrics:
|
|
|
71
71
|
Compute reduced chi-squared for count data.
|
|
72
72
|
|
|
73
73
|
Args:
|
|
74
|
-
counts (ndarray): Count array(s) to analyze.
|
|
75
|
-
return_all (bool, optional): If True, returns
|
|
74
|
+
counts (ndarray): Count array(s) to analyze. Shape: (n_modules, n_bins) or (n_bins,)
|
|
75
|
+
return_all (bool, optional): If True, returns detailed results. If False, returns weighted mean. Defaults to False.
|
|
76
76
|
|
|
77
77
|
Returns:
|
|
78
|
-
|
|
78
|
+
If return_all=False:
|
|
79
|
+
float: Weighted mean chi-squared (weighted by total counts per module)
|
|
80
|
+
If return_all=True:
|
|
81
|
+
tuple: (chi_squared, dof, no_counts) where:
|
|
82
|
+
- chi_squared: Raw chi-squared values per module
|
|
83
|
+
- dof: Degrees of freedom per module (n_bins - 1 excluding NaN)
|
|
84
|
+
- no_counts: Total counts per module
|
|
79
85
|
"""
|
|
80
86
|
counts = np.asarray(counts)
|
|
81
87
|
counts = np.where(counts == 0, np.nan, counts)
|
|
82
88
|
mean_counts = np.nanmean(counts, axis=-1, keepdims=True)
|
|
83
89
|
chi_squared = np.nansum((counts - mean_counts) ** 2 / mean_counts, axis=-1)
|
|
84
|
-
|
|
90
|
+
|
|
91
|
+
# DOF = number of non-empty bins minus 1
|
|
92
|
+
nan_mask = ~np.isnan(counts)
|
|
93
|
+
dof = np.sum(nan_mask, axis=-1) - 1
|
|
94
|
+
no_counts = np.nansum(counts, axis=-1)
|
|
95
|
+
|
|
85
96
|
if return_all:
|
|
86
|
-
return chi_squared
|
|
87
|
-
|
|
97
|
+
return chi_squared, dof, no_counts
|
|
98
|
+
|
|
99
|
+
if np.sum(no_counts) == 0 or np.all(dof <= 0):
|
|
100
|
+
return np.nan
|
|
101
|
+
|
|
102
|
+
# Weight by total counts per module
|
|
103
|
+
return np.average(chi_squared / dof, weights=no_counts)
|
|
88
104
|
|
|
89
105
|
def raw_chi_squared(self, counts=None, return_all=False):
|
|
90
106
|
"""
|
|
@@ -219,3 +219,70 @@ def test_lightcurve_rebin_with_custom_mask(mock_events_file):
|
|
|
219
219
|
time_full, counts_full = lc.rebin(binsize=1.0, emin=30, emax=300)
|
|
220
220
|
|
|
221
221
|
assert np.sum(counts_masked) < np.sum(counts_full)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_lightcurve_module_assignment():
|
|
225
|
+
"""Test that events are correctly assigned to detector modules."""
|
|
226
|
+
# Create synthetic events with DIFFERENT counts per module to verify assignment
|
|
227
|
+
module_counts_expected = [10, 25, 50, 75, 100, 125, 150, 200] # Different for each module
|
|
228
|
+
n_events = sum(module_counts_expected)
|
|
229
|
+
|
|
230
|
+
events = np.zeros(
|
|
231
|
+
n_events,
|
|
232
|
+
dtype=[
|
|
233
|
+
("TIME", "f8"),
|
|
234
|
+
("ISGRI_ENERGY", "f4"),
|
|
235
|
+
("DETY", "i2"),
|
|
236
|
+
("DETZ", "i2"),
|
|
237
|
+
("SELECT_FLAG", "i2"),
|
|
238
|
+
],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Module layout: 0 1
|
|
242
|
+
# 2 3
|
|
243
|
+
# 4 5
|
|
244
|
+
# 6 7
|
|
245
|
+
module_positions = [
|
|
246
|
+
(16, 32), # Module 0: DETZ [0-32), DETY [0-64)
|
|
247
|
+
(16, 96), # Module 1: DETZ [0-32), DETY [64-130)
|
|
248
|
+
(48, 32), # Module 2: DETZ [32-66), DETY [0-64)
|
|
249
|
+
(48, 96), # Module 3: DETZ [32-66), DETY [64-130)
|
|
250
|
+
(80, 32), # Module 4: DETZ [66-100), DETY [0-64)
|
|
251
|
+
(80, 96), # Module 5: DETZ [66-100), DETY [64-130)
|
|
252
|
+
(116, 32), # Module 6: DETZ [100-134), DETY [0-64)
|
|
253
|
+
(116, 96), # Module 7: DETZ [100-134), DETY [64-130)
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
idx = 0
|
|
257
|
+
for module_no, (detz, dety) in enumerate(module_positions):
|
|
258
|
+
n_events_module = module_counts_expected[module_no]
|
|
259
|
+
|
|
260
|
+
events["DETZ"][idx : idx + n_events_module] = detz
|
|
261
|
+
events["DETY"][idx : idx + n_events_module] = dety
|
|
262
|
+
events["TIME"][idx : idx + n_events_module] = np.linspace(0, 10 / 86400, n_events_module)
|
|
263
|
+
events["ISGRI_ENERGY"][idx : idx + n_events_module] = 100 # All same energy
|
|
264
|
+
events["SELECT_FLAG"][idx : idx + n_events_module] = 0
|
|
265
|
+
|
|
266
|
+
idx += n_events_module
|
|
267
|
+
|
|
268
|
+
# Create LightCurve
|
|
269
|
+
time = events["TIME"]
|
|
270
|
+
energies = events["ISGRI_ENERGY"]
|
|
271
|
+
dety = events["DETY"]
|
|
272
|
+
detz = events["DETZ"]
|
|
273
|
+
gtis = np.array([[time[0], time[-1]]])
|
|
274
|
+
weights = np.ones(n_events)
|
|
275
|
+
metadata = {}
|
|
276
|
+
|
|
277
|
+
lc = LightCurve(time, energies, gtis, dety, detz, weights, metadata)
|
|
278
|
+
|
|
279
|
+
# Rebin by modules (1 bin covering all time)
|
|
280
|
+
times, counts = lc.rebin_by_modules(binsize=20.0, emin=50, emax=200, local_time=True)
|
|
281
|
+
|
|
282
|
+
# Verify each module has the expected count
|
|
283
|
+
for module_no, expected_count in enumerate(module_counts_expected):
|
|
284
|
+
actual_count = counts[module_no][0]
|
|
285
|
+
assert actual_count == expected_count, (
|
|
286
|
+
f"Module {module_no} at position {module_positions[module_no]}: "
|
|
287
|
+
f"expected {expected_count} counts, got {actual_count}"
|
|
288
|
+
)
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import numpy as np
|
|
3
|
+
from isgri.utils.quality import QualityMetrics
|
|
4
|
+
from isgri.utils.lightcurve import LightCurve
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture
|
|
8
|
+
def mock_lightcurve():
|
|
9
|
+
"""Create a simple mock LightCurve for testing."""
|
|
10
|
+
n_events = 1000
|
|
11
|
+
time = np.linspace(0, 100 / 86400, n_events)
|
|
12
|
+
energies = np.random.uniform(30, 300, n_events)
|
|
13
|
+
gtis = np.array([[time[0], time[-1]]])
|
|
14
|
+
dety = np.random.randint(0, 128, n_events)
|
|
15
|
+
detz = np.random.randint(0, 134, n_events)
|
|
16
|
+
weights = np.ones(n_events)
|
|
17
|
+
metadata = {"SWID": "test"}
|
|
18
|
+
|
|
19
|
+
return LightCurve(time, energies, gtis, dety, detz, weights, metadata)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_quality_metrics_init():
|
|
23
|
+
"""Test QualityMetrics initialization."""
|
|
24
|
+
qm = QualityMetrics(binsize=1.0, emin=30, emax=300)
|
|
25
|
+
|
|
26
|
+
assert qm.lightcurve is None
|
|
27
|
+
assert qm.binsize == 1.0
|
|
28
|
+
assert qm.emin == 30
|
|
29
|
+
assert qm.emax == 300
|
|
30
|
+
assert qm.local_time is False
|
|
31
|
+
assert qm.module_data is None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_quality_metrics_init_with_lightcurve(mock_lightcurve):
|
|
35
|
+
"""Test QualityMetrics initialization with LightCurve."""
|
|
36
|
+
qm = QualityMetrics(mock_lightcurve, binsize=1.0, emin=30, emax=300)
|
|
37
|
+
|
|
38
|
+
assert qm.lightcurve is not None
|
|
39
|
+
assert isinstance(qm.lightcurve, LightCurve)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_quality_metrics_init_invalid_type():
|
|
43
|
+
"""Test QualityMetrics raises error for invalid lightcurve type."""
|
|
44
|
+
with pytest.raises(TypeError):
|
|
45
|
+
QualityMetrics(lightcurve="not_a_lightcurve")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_compute_counts_without_lightcurve():
|
|
49
|
+
"""Test _compute_counts raises error when lightcurve is None."""
|
|
50
|
+
qm = QualityMetrics()
|
|
51
|
+
|
|
52
|
+
with pytest.raises(ValueError, match="Lightcurve is not set"):
|
|
53
|
+
qm._compute_counts()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_compute_counts_caching(mock_lightcurve):
|
|
57
|
+
"""Test that _compute_counts caches results."""
|
|
58
|
+
qm = QualityMetrics(mock_lightcurve, binsize=1.0, emin=30, emax=300)
|
|
59
|
+
|
|
60
|
+
data1 = qm._compute_counts()
|
|
61
|
+
data2 = qm._compute_counts()
|
|
62
|
+
|
|
63
|
+
assert data1 is data2
|
|
64
|
+
assert "time" in data1
|
|
65
|
+
assert "counts" in data1
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_chi_squared_constant_lightcurve():
|
|
69
|
+
"""Test chi-squared for constant lightcurve."""
|
|
70
|
+
counts = np.ones(100) * 10.0
|
|
71
|
+
qm = QualityMetrics()
|
|
72
|
+
|
|
73
|
+
chi = qm.raw_chi_squared(counts=counts)
|
|
74
|
+
|
|
75
|
+
assert chi < 0.1
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_chi_squared_poisson_lightcurve():
|
|
79
|
+
"""Test chi-squared for Poisson-distributed data."""
|
|
80
|
+
np.random.seed(42)
|
|
81
|
+
counts = np.random.poisson(10, 1000)
|
|
82
|
+
qm = QualityMetrics()
|
|
83
|
+
|
|
84
|
+
chi = qm.raw_chi_squared(counts=counts)
|
|
85
|
+
|
|
86
|
+
assert 0.7 < chi < 1.3
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_chi_squared_variable_lightcurve():
|
|
90
|
+
"""Test chi-squared for highly variable lightcurve."""
|
|
91
|
+
counts = np.concatenate([np.ones(50) * 5, np.ones(50) * 50])
|
|
92
|
+
qm = QualityMetrics()
|
|
93
|
+
|
|
94
|
+
chi = qm.raw_chi_squared(counts=counts)
|
|
95
|
+
|
|
96
|
+
assert chi > 2.0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_chi_squared_1d_vs_2d():
|
|
100
|
+
"""Test chi-squared handles both 1D and 2D arrays."""
|
|
101
|
+
np.random.seed(42)
|
|
102
|
+
counts_1d = np.random.poisson(10, 100)
|
|
103
|
+
counts_2d = np.random.poisson(10, (8, 100))
|
|
104
|
+
|
|
105
|
+
qm = QualityMetrics()
|
|
106
|
+
chi_1d = qm.raw_chi_squared(counts=counts_1d)
|
|
107
|
+
chi_2d = qm.raw_chi_squared(counts=counts_2d)
|
|
108
|
+
|
|
109
|
+
assert isinstance(chi_1d, (float, np.floating))
|
|
110
|
+
assert isinstance(chi_2d, (float, np.floating))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_chi_squared_return_all():
|
|
114
|
+
"""Test chi-squared with return_all=True."""
|
|
115
|
+
np.random.seed(42)
|
|
116
|
+
counts = np.random.poisson(10, (8, 100))
|
|
117
|
+
qm = QualityMetrics()
|
|
118
|
+
|
|
119
|
+
chi, dof, no_counts = qm.raw_chi_squared(counts=counts, return_all=True)
|
|
120
|
+
|
|
121
|
+
assert chi.shape == (8,)
|
|
122
|
+
assert dof.shape == (8,)
|
|
123
|
+
assert no_counts.shape == (8,)
|
|
124
|
+
assert np.all(dof == 99)
|
|
125
|
+
assert np.all(no_counts > 0)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_chi_squared_with_zeros():
|
|
129
|
+
"""Test chi-squared handles zero counts."""
|
|
130
|
+
counts = np.array([10, 10, 0, 10, 10])
|
|
131
|
+
qm = QualityMetrics()
|
|
132
|
+
|
|
133
|
+
chi = qm.raw_chi_squared(counts=counts)
|
|
134
|
+
|
|
135
|
+
assert np.isfinite(chi)
|
|
136
|
+
assert chi < 0.5
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_sigma_clip_removes_outliers():
|
|
140
|
+
"""Test sigma clipping removes outliers."""
|
|
141
|
+
counts = np.ones(100) * 10.0
|
|
142
|
+
counts[50] = 100.0
|
|
143
|
+
|
|
144
|
+
qm = QualityMetrics()
|
|
145
|
+
chi_raw = qm.raw_chi_squared(counts=counts)
|
|
146
|
+
chi_clipped = qm.sigma_clip_chi_squared(counts=counts, sigma=3.0)
|
|
147
|
+
|
|
148
|
+
assert chi_clipped < chi_raw
|
|
149
|
+
assert chi_clipped < 0.5
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_sigma_clip_different_thresholds():
|
|
153
|
+
"""Test different sigma thresholds."""
|
|
154
|
+
np.random.seed(42)
|
|
155
|
+
counts = np.random.poisson(10, 100)
|
|
156
|
+
counts[50] = 100
|
|
157
|
+
|
|
158
|
+
qm = QualityMetrics()
|
|
159
|
+
chi_1sigma = qm.sigma_clip_chi_squared(counts=counts, sigma=1.0)
|
|
160
|
+
chi_3sigma = qm.sigma_clip_chi_squared(counts=counts, sigma=3.0)
|
|
161
|
+
|
|
162
|
+
assert chi_1sigma <= chi_3sigma
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_sigma_clip_2d_arrays():
|
|
166
|
+
"""Test sigma clipping works with 2D arrays."""
|
|
167
|
+
np.random.seed(42)
|
|
168
|
+
counts = np.random.poisson(10, (8, 100))
|
|
169
|
+
counts[:, 50] = 100
|
|
170
|
+
|
|
171
|
+
qm = QualityMetrics()
|
|
172
|
+
chi_raw = qm.raw_chi_squared(counts=counts)
|
|
173
|
+
chi_clipped = qm.sigma_clip_chi_squared(counts=counts, sigma=3.0)
|
|
174
|
+
|
|
175
|
+
assert chi_clipped < chi_raw
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_gti_chi_squared_filters_correctly(mock_lightcurve):
|
|
179
|
+
"""Test GTI filtering applies correctly."""
|
|
180
|
+
qm = QualityMetrics(mock_lightcurve, binsize=1.0, emin=30, emax=300, local_time=False)
|
|
181
|
+
|
|
182
|
+
chi = qm.gti_chi_squared()
|
|
183
|
+
|
|
184
|
+
assert np.isfinite(chi)
|
|
185
|
+
assert chi > 0
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_gti_chi_squared_no_overlap():
|
|
189
|
+
"""Test GTI chi-squared raises error when no overlap."""
|
|
190
|
+
np.random.seed(42)
|
|
191
|
+
time = np.linspace(0, 100, 100)
|
|
192
|
+
counts = np.random.poisson(10, (8, 100))
|
|
193
|
+
gtis = np.array([[200, 300]])
|
|
194
|
+
|
|
195
|
+
qm = QualityMetrics()
|
|
196
|
+
|
|
197
|
+
with pytest.raises(ValueError, match="No overlap"):
|
|
198
|
+
qm.gti_chi_squared(time=time, counts=counts, gtis=gtis)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_gti_chi_squared_custom_gtis():
|
|
202
|
+
"""Test GTI chi-squared with custom GTI array."""
|
|
203
|
+
np.random.seed(42)
|
|
204
|
+
time = np.linspace(0, 100, 100)
|
|
205
|
+
counts = np.random.poisson(10, (8, 100))
|
|
206
|
+
gtis = np.array([[0, 50], [75, 100]])
|
|
207
|
+
|
|
208
|
+
qm = QualityMetrics()
|
|
209
|
+
chi = qm.gti_chi_squared(time=time, counts=counts, gtis=gtis)
|
|
210
|
+
|
|
211
|
+
assert np.isfinite(chi)
|
|
212
|
+
assert chi > 0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_gti_chi_squared_return_all():
|
|
216
|
+
"""Test GTI chi-squared with return_all=True."""
|
|
217
|
+
np.random.seed(42)
|
|
218
|
+
time = np.linspace(0, 100, 100)
|
|
219
|
+
counts = np.random.poisson(10, (8, 100))
|
|
220
|
+
gtis = np.array([[0, 100]])
|
|
221
|
+
|
|
222
|
+
qm = QualityMetrics()
|
|
223
|
+
chi, dof, no_counts = qm.gti_chi_squared(time=time, counts=counts, gtis=gtis, return_all=True)
|
|
224
|
+
|
|
225
|
+
assert chi.shape == (8,)
|
|
226
|
+
assert dof.shape == (8,)
|
|
227
|
+
assert no_counts.shape == (8,)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_chi_squared_weighting():
|
|
231
|
+
"""Test chi-squared weighting by total counts."""
|
|
232
|
+
counts = np.array(
|
|
233
|
+
[
|
|
234
|
+
np.concatenate([np.ones(50) * 5, np.ones(50) * 50]),
|
|
235
|
+
np.ones(100) * 100,
|
|
236
|
+
]
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
qm = QualityMetrics()
|
|
240
|
+
chi_weighted = qm.raw_chi_squared(counts=counts)
|
|
241
|
+
chi_1, chi_2 = qm.raw_chi_squared(counts=counts, return_all=True)[0]
|
|
242
|
+
|
|
243
|
+
# Weighted average closer to high-count module
|
|
244
|
+
assert abs(chi_weighted - chi_2) < abs(chi_weighted - chi_1)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_chi_squared_all_nan():
|
|
248
|
+
"""Test chi-squared handles all-NaN arrays."""
|
|
249
|
+
counts = np.full(100, np.nan)
|
|
250
|
+
qm = QualityMetrics()
|
|
251
|
+
|
|
252
|
+
chi = qm.raw_chi_squared(counts=counts)
|
|
253
|
+
|
|
254
|
+
assert np.isnan(chi)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_integration_with_real_lightcurve(mock_lightcurve):
|
|
258
|
+
"""Test full workflow with real LightCurve."""
|
|
259
|
+
qm = QualityMetrics(mock_lightcurve, binsize=1.0, emin=30, emax=300, local_time=False)
|
|
260
|
+
|
|
261
|
+
chi_raw = qm.raw_chi_squared()
|
|
262
|
+
chi_clipped = qm.sigma_clip_chi_squared(sigma=3.0)
|
|
263
|
+
chi_gti = qm.gti_chi_squared()
|
|
264
|
+
|
|
265
|
+
assert np.isfinite(chi_raw)
|
|
266
|
+
assert np.isfinite(chi_clipped)
|
|
267
|
+
assert np.isfinite(chi_gti)
|
|
268
|
+
assert qm.module_data is not None
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
import numpy as np
|
|
3
|
-
from isgri.utils.quality import QualityMetrics
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_quality_metrics_init():
|
|
7
|
-
"""Test QualityMetrics initialization."""
|
|
8
|
-
qm = QualityMetrics(binsize=1.0, emin=30, emax=300)
|
|
9
|
-
assert qm.lightcurve is None
|
|
10
|
-
assert qm.binsize == 1.0
|
|
11
|
-
|
|
12
|
-
def test_chi_squared_raw_constant():
|
|
13
|
-
"""Test chi-squared for constant lightcurve (should be ~0)."""
|
|
14
|
-
counts = np.ones(100) * 10
|
|
15
|
-
qm = QualityMetrics()
|
|
16
|
-
chi = qm.raw_chi_squared(counts=counts)
|
|
17
|
-
assert chi < 0.1 # Should be very small
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_chi_squared_raw_poisson():
|
|
21
|
-
"""Test chi-squared for Poisson-like data."""
|
|
22
|
-
np.random.seed(42)
|
|
23
|
-
counts = np.random.poisson(10, 100)
|
|
24
|
-
qm = QualityMetrics()
|
|
25
|
-
chi = qm.raw_chi_squared(counts=counts)
|
|
26
|
-
assert 0.5 < chi < 2.0 # Should be ~1 for Poisson
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def test_chi_squared_clipped():
|
|
30
|
-
"""Test sigma-clipped chi-squared removes outliers."""
|
|
31
|
-
counts = np.ones(100) * 10
|
|
32
|
-
counts[50] = 100 # Add outlier
|
|
33
|
-
|
|
34
|
-
qm = QualityMetrics()
|
|
35
|
-
chi_raw = qm.raw_chi_squared(counts=counts)
|
|
36
|
-
chi_clipped = qm.sigma_clip_chi_squared(counts=counts, sigma=3.0)
|
|
37
|
-
|
|
38
|
-
assert chi_clipped < chi_raw # Clipped should be smaller
|
|
39
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|