gwpy 3.0.8__py3-none-any.whl → 3.0.10__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.
Potentially problematic release.
This version of gwpy might be problematic. Click here for more details.
- gwpy/_version.py +2 -2
- gwpy/astro/range.py +3 -3
- gwpy/astro/tests/test_range.py +4 -5
- gwpy/cli/gwpy_plot.py +1 -1
- gwpy/cli/qtransform.py +1 -1
- gwpy/cli/tests/base.py +11 -1
- gwpy/detector/units.py +2 -1
- gwpy/frequencyseries/tests/test_hist.py +4 -3
- gwpy/io/datafind.py +1 -0
- gwpy/io/ffldatafind.py +27 -16
- gwpy/io/tests/test_ffldatafind.py +10 -3
- gwpy/plot/plot.py +12 -2
- gwpy/plot/tests/test_segments.py +1 -1
- gwpy/plot/tex.py +6 -4
- gwpy/segments/flag.py +13 -11
- gwpy/signal/filter_design.py +5 -4
- gwpy/signal/tests/test_coherence.py +31 -10
- gwpy/signal/tests/test_filter_design.py +3 -3
- gwpy/spectrogram/tests/test_spectrogram.py +2 -2
- gwpy/table/io/ligolw.py +1 -1
- gwpy/table/tests/test_io_ligolw.py +1 -1
- gwpy/testing/errors.py +1 -0
- gwpy/testing/fixtures.py +1 -18
- gwpy/testing/utils.py +15 -6
- gwpy/timeseries/core.py +0 -1
- gwpy/timeseries/io/cache.py +47 -29
- gwpy/timeseries/io/gwf/lalframe.py +8 -0
- gwpy/timeseries/statevector.py +4 -2
- gwpy/timeseries/tests/test_core.py +7 -13
- gwpy/timeseries/tests/test_io_cache.py +74 -0
- gwpy/timeseries/tests/test_io_gwf_framecpp.py +1 -1
- gwpy/timeseries/tests/test_statevector.py +29 -17
- gwpy/timeseries/tests/test_timeseries.py +37 -34
- gwpy/types/array.py +16 -3
- gwpy/types/index.py +7 -5
- gwpy/types/tests/test_array.py +12 -10
- gwpy/types/tests/test_array2d.py +5 -9
- gwpy/types/tests/test_series.py +5 -5
- gwpy/utils/sphinx/zenodo.py +5 -1
- gwpy/utils/tests/test_sphinx_zenodo.py +23 -0
- gwpy-3.0.10.dist-info/METADATA +125 -0
- {gwpy-3.0.8.dist-info → gwpy-3.0.10.dist-info}/RECORD +46 -45
- {gwpy-3.0.8.dist-info → gwpy-3.0.10.dist-info}/WHEEL +1 -1
- gwpy-3.0.8.dist-info/METADATA +0 -124
- {gwpy-3.0.8.dist-info → gwpy-3.0.10.dist-info}/LICENSE +0 -0
- {gwpy-3.0.8.dist-info → gwpy-3.0.10.dist-info}/entry_points.txt +0 -0
- {gwpy-3.0.8.dist-info → gwpy-3.0.10.dist-info}/top_level.txt +0 -0
gwpy/timeseries/io/cache.py
CHANGED
|
@@ -19,22 +19,34 @@
|
|
|
19
19
|
"""I/O utilities for reading `TimeSeries` from a `list` of file paths.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from
|
|
22
|
+
from io import BytesIO
|
|
23
|
+
from math import inf
|
|
24
|
+
from os import PathLike
|
|
25
|
+
|
|
26
|
+
from ...io.cache import (
|
|
27
|
+
FILE_LIKE,
|
|
28
|
+
file_segment,
|
|
29
|
+
read_cache,
|
|
30
|
+
write_cache,
|
|
31
|
+
)
|
|
23
32
|
from ...segments import Segment
|
|
24
33
|
|
|
25
34
|
__author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
|
|
26
35
|
|
|
27
36
|
|
|
28
|
-
def preformat_cache(cache, start=None, end=None):
|
|
37
|
+
def preformat_cache(cache, start=None, end=None, sort=file_segment):
|
|
29
38
|
"""Preprocess a `list` of file paths for reading.
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
This function does the following:
|
|
41
|
+
|
|
42
|
+
- read the list of paths cache file (if necessary),
|
|
43
|
+
- sort the cache in time order (if possible),
|
|
44
|
+
- sieve the cache to only include data we need.
|
|
33
45
|
|
|
34
46
|
Parameters
|
|
35
47
|
----------
|
|
36
|
-
cache : `list`, `str`
|
|
37
|
-
List of file paths, or path to a
|
|
48
|
+
cache : `list`, `str`, `pathlib.Path`
|
|
49
|
+
List of file paths, or path to a cache file.
|
|
38
50
|
|
|
39
51
|
start : `~gwpy.time.LIGOTimeGPS`, `float`, `str`, optional
|
|
40
52
|
GPS start time of required data, defaults to start of data found;
|
|
@@ -44,31 +56,37 @@ def preformat_cache(cache, start=None, end=None):
|
|
|
44
56
|
GPS end time of required data, defaults to end of data found;
|
|
45
57
|
any input parseable by `~gwpy.time.to_gps` is fine.
|
|
46
58
|
|
|
59
|
+
sort : `callable`, optional
|
|
60
|
+
A callable key function by which to sort the file paths.
|
|
61
|
+
|
|
47
62
|
Returns
|
|
48
63
|
-------
|
|
49
64
|
modcache : `list`
|
|
50
65
|
A parsed, sieved list of paths based on the input arguments.
|
|
66
|
+
|
|
67
|
+
See also
|
|
68
|
+
--------
|
|
69
|
+
gwpy.io.cache.read_cache
|
|
70
|
+
For details of how the sorting and sieving is implemented
|
|
51
71
|
"""
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
start
|
|
72
|
-
|
|
73
|
-
end = file_segment(cache[-1])[-1]
|
|
74
|
-
return sieve(cache, segment=Segment(start, end))
|
|
72
|
+
# if given a list of paths, write it to a file-like structure
|
|
73
|
+
# so that we can use read_cache to do all the work
|
|
74
|
+
if not isinstance(cache, (str, PathLike) + FILE_LIKE):
|
|
75
|
+
cachef = BytesIO()
|
|
76
|
+
write_cache(cache, cachef)
|
|
77
|
+
cachef.seek(0)
|
|
78
|
+
cache = cachef
|
|
79
|
+
|
|
80
|
+
# need start and end times to sieve the cache
|
|
81
|
+
if start is None:
|
|
82
|
+
start = -inf
|
|
83
|
+
if end is None:
|
|
84
|
+
end = +inf
|
|
85
|
+
|
|
86
|
+
# read the cache
|
|
87
|
+
return read_cache(
|
|
88
|
+
cache,
|
|
89
|
+
coltype=type(start),
|
|
90
|
+
sort=sort,
|
|
91
|
+
segment=Segment(start, end),
|
|
92
|
+
)
|
|
@@ -149,6 +149,14 @@ def read(source, channels, start=None, end=None, series_class=TimeSeries,
|
|
|
149
149
|
end = epoch + streamdur
|
|
150
150
|
end = min(epoch + streamdur, lalutils.to_lal_ligotimegps(end))
|
|
151
151
|
duration = float(end - start)
|
|
152
|
+
if start >= (epoch + streamdur):
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"cannot read data starting after stream ends",
|
|
155
|
+
)
|
|
156
|
+
if duration < 0:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
"cannot read data with negative duration",
|
|
159
|
+
)
|
|
152
160
|
|
|
153
161
|
# read data
|
|
154
162
|
out = series_class.DictClass()
|
gwpy/timeseries/statevector.py
CHANGED
|
@@ -184,8 +184,10 @@ class StateTimeSeries(TimeSeriesBase):
|
|
|
184
184
|
def unit(self):
|
|
185
185
|
return units.dimensionless_unscaled
|
|
186
186
|
|
|
187
|
-
def override_unit(self,
|
|
188
|
-
|
|
187
|
+
def override_unit(self, *args, **kwargs):
|
|
188
|
+
raise NotImplementedError(
|
|
189
|
+
f"overriding units is not supported for {type(self).__name__}",
|
|
190
|
+
)
|
|
189
191
|
|
|
190
192
|
def _to_own_unit(self, value, check_precision=True):
|
|
191
193
|
if isinstance(value, units.Quantity) and value.unit != self.unit:
|
|
@@ -53,7 +53,8 @@ class TestTimeSeriesBase(_TestSeries):
|
|
|
53
53
|
def test_new(self):
|
|
54
54
|
"""Test `gwpy.timeseries.TimeSeriesBase` constructor
|
|
55
55
|
"""
|
|
56
|
-
array =
|
|
56
|
+
array = self.create()
|
|
57
|
+
super().test_new()
|
|
57
58
|
|
|
58
59
|
# check time-domain metadata
|
|
59
60
|
assert array.epoch == GPS_EPOCH
|
|
@@ -145,7 +146,7 @@ class TestTimeSeriesBase(_TestSeries):
|
|
|
145
146
|
def test_sample_rate_ghz(self, array):
|
|
146
147
|
"""Test that very large sample rates don't get rounded to dt=0.
|
|
147
148
|
|
|
148
|
-
Regression: https://
|
|
149
|
+
Regression: https://gitlab.com/gwpy/gwpy/-/issues/1646
|
|
149
150
|
"""
|
|
150
151
|
array.sample_rate = 1e9
|
|
151
152
|
assert array.dt.value > 0.
|
|
@@ -400,20 +401,13 @@ class TestTimeSeriesBaseDict(object):
|
|
|
400
401
|
|
|
401
402
|
def test_resample(self, instance):
|
|
402
403
|
if self.ENTRY_CLASS is TimeSeriesBase: # currently only for subclasses
|
|
403
|
-
|
|
404
|
+
pytest.skip(f"not implemented for {type(instance).__name__}")
|
|
405
|
+
|
|
406
|
+
# for all subclasses
|
|
404
407
|
a = instance.resample(.5)
|
|
405
408
|
for key in a:
|
|
406
409
|
assert a[key].dx == 1/.5 * a[key].xunit
|
|
407
410
|
|
|
408
|
-
def test_fetch(self):
|
|
409
|
-
return NotImplemented
|
|
410
|
-
|
|
411
|
-
def test_find(self):
|
|
412
|
-
return NotImplemented
|
|
413
|
-
|
|
414
|
-
def test_get(self):
|
|
415
|
-
return NotImplemented
|
|
416
|
-
|
|
417
411
|
@pytest.mark.requires("nds2")
|
|
418
412
|
def test_from_nds2_buffers(self):
|
|
419
413
|
buffers = [
|
|
@@ -445,7 +439,7 @@ class TestTimeSeriesBaseDict(object):
|
|
|
445
439
|
def test_plot_separate(self, instance):
|
|
446
440
|
"""Test plotting `TimeSeriesDict` on separate axes.
|
|
447
441
|
|
|
448
|
-
See https://
|
|
442
|
+
See https://gitlab.com/gwpy/gwpy/-/issues/1609
|
|
449
443
|
"""
|
|
450
444
|
with rc_context(rc={'text.usetex': False}):
|
|
451
445
|
plot = instance.plot(separate=True)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) Cardiff University 2023
|
|
3
|
+
#
|
|
4
|
+
# This file is part of GWpy.
|
|
5
|
+
#
|
|
6
|
+
# GWpy is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# GWpy is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU General Public License
|
|
17
|
+
# along with GWpy. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
|
|
19
|
+
"""Tests for :mod:`gwpy.timeseries.io.cache`
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
24
|
+
from ...io.cache import write_cache
|
|
25
|
+
from ..io import cache as ts_io_cache
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def cache():
|
|
30
|
+
"""List of files over which to test sorting/sieving.
|
|
31
|
+
"""
|
|
32
|
+
return [
|
|
33
|
+
"/tmp/A-TEST-0-10.tmp",
|
|
34
|
+
"/tmp/A-TEST-10-10.tmp",
|
|
35
|
+
"/tmp/A-TEST-20-10.tmp",
|
|
36
|
+
"/tmp/A-TEST-30-5.tmp",
|
|
37
|
+
"/tmp/A-TEST-35-15.tmp",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def cache_file(tmp_path, cache):
|
|
43
|
+
"""File version of `cache()`.
|
|
44
|
+
"""
|
|
45
|
+
path = tmp_path / "cache.txt"
|
|
46
|
+
write_cache(cache, path)
|
|
47
|
+
return path
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.parametrize("source", ("cache", "cache_file"))
|
|
51
|
+
@pytest.mark.parametrize(("start", "end", "idx"), [
|
|
52
|
+
# use everything in the cache
|
|
53
|
+
(None, None, slice(None)),
|
|
54
|
+
# use only GPS time '25' onwards, which is cache[2:]
|
|
55
|
+
(25, None, slice(2, None)),
|
|
56
|
+
# use only up to GPS time '25', which is cache[:3]
|
|
57
|
+
(None, 25, slice(None, 3)),
|
|
58
|
+
# use interval [10, 35), which needs cache[1:4]
|
|
59
|
+
(10, 35, slice(1, 4)),
|
|
60
|
+
])
|
|
61
|
+
def test_preformat_cache(request, cache, source, start, end, idx):
|
|
62
|
+
"""Test that `gwpy.timeseries.io.cache.preformat_cache` works properly.
|
|
63
|
+
|
|
64
|
+
Here `[start, end)` is a GPS segment, and `idx` the corresponding slice
|
|
65
|
+
needed to restrict the cache object.
|
|
66
|
+
|
|
67
|
+
Loops over a variety of input arguments, using `request` to dynamically
|
|
68
|
+
loop over `cache` or `cache_file` as the input.
|
|
69
|
+
"""
|
|
70
|
+
assert ts_io_cache.preformat_cache(
|
|
71
|
+
request.getfixturevalue(source), # cache or cache_file
|
|
72
|
+
start=start,
|
|
73
|
+
end=end,
|
|
74
|
+
) == cache[idx]
|
|
@@ -63,7 +63,7 @@ def test_read_scaled_type_change(int32ts, tmp_path):
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def test_read_write_frvect_name(tmp_path):
|
|
66
|
-
"""Test against regression of https://
|
|
66
|
+
"""Test against regression of https://gitlab.com/gwpy/gwpy/-/issues/1206
|
|
67
67
|
"""
|
|
68
68
|
data = TimeSeries(
|
|
69
69
|
numpy.random.random(10),
|
|
@@ -144,29 +144,39 @@ class TestStateTimeSeries(_TestTimeSeriesBase):
|
|
|
144
144
|
assert flag.label == 'Label'
|
|
145
145
|
assert flag.description == 'Description'
|
|
146
146
|
|
|
147
|
-
def test_override_unit(self):
|
|
148
|
-
|
|
147
|
+
def test_override_unit(self, array):
|
|
148
|
+
with pytest.raises(NotImplementedError):
|
|
149
|
+
super().test_override_unit(array)
|
|
149
150
|
|
|
150
151
|
def test_is_compatible_error_unit(self):
|
|
151
|
-
|
|
152
|
+
pytest.skip(f"not implemented for {self.TEST_CLASS.__name__}")
|
|
152
153
|
|
|
153
154
|
def test_to_from_pycbc(self):
|
|
154
|
-
|
|
155
|
+
pytest.skip(f"not implemented for {self.TEST_CLASS.__name__}")
|
|
155
156
|
|
|
156
|
-
def test_to_from_lal(self):
|
|
157
|
-
|
|
157
|
+
def test_to_from_lal(self, array):
|
|
158
|
+
with pytest.raises(NotImplementedError):
|
|
159
|
+
super().test_to_from_lal(array)
|
|
158
160
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
@pytest.mark.parametrize("copy", (False, True))
|
|
162
|
+
def test_to_from_lal_no_copy(self, array, copy):
|
|
163
|
+
with pytest.raises(NotImplementedError):
|
|
164
|
+
super().test_to_from_lal_no_copy(array, copy)
|
|
161
165
|
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
@pytest.mark.requires("lal")
|
|
167
|
+
def test_to_from_lal_pow10_units(self, array):
|
|
168
|
+
with pytest.raises(NotImplementedError):
|
|
169
|
+
super().test_to_from_lal_pow10_units(array)
|
|
164
170
|
|
|
165
|
-
|
|
166
|
-
|
|
171
|
+
@pytest.mark.requires("lal")
|
|
172
|
+
def test_to_from_lal_scaled_units(self, array):
|
|
173
|
+
with pytest.raises(NotImplementedError):
|
|
174
|
+
super().test_to_from_lal_scaled_units(array)
|
|
167
175
|
|
|
168
|
-
|
|
169
|
-
|
|
176
|
+
@pytest.mark.requires("lal")
|
|
177
|
+
def test_to_from_lal_unrecognised_units(self, array):
|
|
178
|
+
with pytest.raises(NotImplementedError):
|
|
179
|
+
super().test_to_from_lal_unrecognised_units(array)
|
|
170
180
|
|
|
171
181
|
|
|
172
182
|
# -- StateTimeSeriesDict ------------------------------------------------------
|
|
@@ -176,8 +186,8 @@ class TestStateTimeSeriesDict(_TestTimeSeriesBaseDict):
|
|
|
176
186
|
ENTRY_CLASS = StateTimeSeries
|
|
177
187
|
DTYPE = 'bool'
|
|
178
188
|
|
|
179
|
-
def test_resample(self):
|
|
180
|
-
|
|
189
|
+
def test_resample(self, instance):
|
|
190
|
+
pytest.skip(f"not implemented for {type(instance).__name__}")
|
|
181
191
|
|
|
182
192
|
|
|
183
193
|
# -- Bits ---------------------------------------------------------------------
|
|
@@ -326,7 +336,9 @@ class TestStateVector(_TestTimeSeriesBase):
|
|
|
326
336
|
array.resample(array.sample_rate * 1.5)
|
|
327
337
|
|
|
328
338
|
def test_to_from_lal_scaled_units(self):
|
|
329
|
-
|
|
339
|
+
pytest.skip(
|
|
340
|
+
f"not implemented for {self.TEST_CLASS.__name__}",
|
|
341
|
+
)
|
|
330
342
|
|
|
331
343
|
# -- data access ----------------------------
|
|
332
344
|
|
|
@@ -201,7 +201,7 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
201
201
|
def test_read_ascii_header(self, tmpdir):
|
|
202
202
|
"""Check that ASCII files with headers are read without extra options
|
|
203
203
|
|
|
204
|
-
[regression: https://
|
|
204
|
+
[regression: https://gitlab.com/gwpy/gwpy/-/issues/1473]
|
|
205
205
|
"""
|
|
206
206
|
txt = tmpdir / "text.txt"
|
|
207
207
|
txt.write_text(
|
|
@@ -253,28 +253,29 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
253
253
|
exclude=['channel'])
|
|
254
254
|
|
|
255
255
|
@pytest.mark.parametrize('api', GWF_APIS)
|
|
256
|
-
def
|
|
256
|
+
def test_read_gwf_end_error(self, api):
|
|
257
|
+
"""Test that reading past the end of available data fails.
|
|
258
|
+
"""
|
|
257
259
|
fmt = "gwf" if api is None else "gwf." + api
|
|
258
|
-
|
|
259
|
-
tmp = tmp_path / "test.gwf"
|
|
260
|
-
array.write(tmp, format=fmt)
|
|
261
|
-
|
|
262
|
-
# check that reading past the end of the array fails
|
|
263
|
-
with pytest.raises((ValueError, RuntimeError)):
|
|
260
|
+
with pytest.raises(ValueError):
|
|
264
261
|
self.TEST_CLASS.read(
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
utils.TEST_GWF_FILE,
|
|
263
|
+
"L1:LDAS-STRAIN",
|
|
267
264
|
format=fmt,
|
|
268
|
-
start=
|
|
265
|
+
start=utils.TEST_GWF_SPAN[1],
|
|
269
266
|
)
|
|
270
267
|
|
|
271
|
-
|
|
272
|
-
|
|
268
|
+
@pytest.mark.parametrize('api', GWF_APIS)
|
|
269
|
+
def test_read_gwf_negative_duration_error(self, api):
|
|
270
|
+
"""Test that reading a negative duration fails.
|
|
271
|
+
"""
|
|
272
|
+
fmt = "gwf" if api is None else "gwf." + api
|
|
273
|
+
with pytest.raises(ValueError):
|
|
273
274
|
self.TEST_CLASS.read(
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
utils.TEST_GWF_FILE,
|
|
276
|
+
"L1:LDAS-STRAIN",
|
|
276
277
|
format=fmt,
|
|
277
|
-
end=
|
|
278
|
+
end=utils.TEST_GWF_SPAN[0]-1,
|
|
278
279
|
)
|
|
279
280
|
|
|
280
281
|
@pytest.mark.parametrize('api', GWF_APIS)
|
|
@@ -283,7 +284,7 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
283
284
|
"""Check that each GWF API can read a series of files, either in
|
|
284
285
|
a single process, or in multiple processes
|
|
285
286
|
|
|
286
|
-
Regression: https://
|
|
287
|
+
Regression: https://gitlab.com/gwpy/gwpy/-/issues/1486
|
|
287
288
|
"""
|
|
288
289
|
fmt = "gwf" if api is None else "gwf." + api
|
|
289
290
|
a1 = self.create(name='TEST')
|
|
@@ -511,7 +512,7 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
511
512
|
"""Check that `TimeSeries.read` with `gap='raise'` actually
|
|
512
513
|
raises appropriately.
|
|
513
514
|
|
|
514
|
-
[regression: https://
|
|
515
|
+
[regression: https://gitlab.com/gwpy/gwpy/-/issues/1211]
|
|
515
516
|
"""
|
|
516
517
|
from gwpy.io.cache import file_segment
|
|
517
518
|
span = file_segment(utils.TEST_HDF5_FILE)
|
|
@@ -845,9 +846,7 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
845
846
|
gw150914.psd(abs(gw150914.span), method='lal_median_mean')
|
|
846
847
|
|
|
847
848
|
# odd number of segments should warn
|
|
848
|
-
|
|
849
|
-
# triggered here, for some reason
|
|
850
|
-
with pytest.warns(UserWarning):
|
|
849
|
+
with pytest.warns(UserWarning), pytest.deprecated_call():
|
|
851
850
|
gw150914.psd(1, .5, method='lal_median_mean')
|
|
852
851
|
|
|
853
852
|
@pytest.mark.parametrize('method', ('welch', 'bartlett', 'median'))
|
|
@@ -1021,18 +1020,16 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
1021
1020
|
pytest.param('pycbc', marks=pytest.mark.requires("pycbc.psd")),
|
|
1022
1021
|
])
|
|
1023
1022
|
def test_spectrogram_median_mean(self, gw150914, library):
|
|
1024
|
-
method =
|
|
1023
|
+
method = f"{library}-median-mean"
|
|
1025
1024
|
|
|
1026
|
-
# median-mean
|
|
1027
|
-
# even number of FFTs.
|
|
1028
|
-
# pytest only asserts a single warning, and UserWarning will take
|
|
1029
|
-
# precedence apparently, so check that for lal
|
|
1025
|
+
# the LAL implementation of median-mean warns if not given the
|
|
1026
|
+
# correct amount of data for an even number of FFTs.
|
|
1030
1027
|
if library == 'lal':
|
|
1031
|
-
|
|
1028
|
+
lal_warn_ctx = pytest.warns(UserWarning)
|
|
1032
1029
|
else:
|
|
1033
|
-
|
|
1030
|
+
lal_warn_ctx = nullcontext()
|
|
1034
1031
|
|
|
1035
|
-
with
|
|
1032
|
+
with pytest.deprecated_call(), lal_warn_ctx:
|
|
1036
1033
|
sg = gw150914.spectrogram(
|
|
1037
1034
|
1.5,
|
|
1038
1035
|
fftlength=.5,
|
|
@@ -1073,18 +1070,22 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
1073
1070
|
fgram = gw150914.fftgram(1)
|
|
1074
1071
|
fs = int(gw150914.sample_rate.value)
|
|
1075
1072
|
f, t, sxx = signal.spectrogram(
|
|
1076
|
-
gw150914,
|
|
1073
|
+
gw150914,
|
|
1074
|
+
fs,
|
|
1077
1075
|
window='hann',
|
|
1078
1076
|
nperseg=fs,
|
|
1079
1077
|
mode='complex',
|
|
1080
1078
|
)
|
|
1081
1079
|
utils.assert_array_equal(gw150914.t0.value + t, fgram.xindex.value)
|
|
1082
1080
|
utils.assert_array_equal(f, fgram.yindex.value)
|
|
1083
|
-
utils.assert_array_equal(sxx.T, fgram)
|
|
1081
|
+
utils.assert_array_equal(sxx.T, fgram.value)
|
|
1084
1082
|
|
|
1083
|
+
def test_fftgram_overlap(self, gw150914):
|
|
1085
1084
|
fgram = gw150914.fftgram(1, overlap=0.5)
|
|
1085
|
+
fs = int(gw150914.sample_rate.value)
|
|
1086
1086
|
f, t, sxx = signal.spectrogram(
|
|
1087
|
-
gw150914,
|
|
1087
|
+
gw150914,
|
|
1088
|
+
fs,
|
|
1088
1089
|
window='hann',
|
|
1089
1090
|
nperseg=fs,
|
|
1090
1091
|
noverlap=fs//2,
|
|
@@ -1092,7 +1093,7 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
1092
1093
|
)
|
|
1093
1094
|
utils.assert_array_equal(gw150914.t0.value + t, fgram.xindex.value)
|
|
1094
1095
|
utils.assert_array_equal(f, fgram.yindex.value)
|
|
1095
|
-
utils.assert_array_equal(sxx.T, fgram)
|
|
1096
|
+
utils.assert_array_equal(sxx.T, fgram.value)
|
|
1096
1097
|
|
|
1097
1098
|
def test_spectral_variance(self, gw150914):
|
|
1098
1099
|
variance = gw150914.spectral_variance(.5, method="median")
|
|
@@ -1381,7 +1382,9 @@ class TestTimeSeries(_TestTimeSeriesBase):
|
|
|
1381
1382
|
|
|
1382
1383
|
def test_convolve(self):
|
|
1383
1384
|
data = self.TEST_CLASS(
|
|
1384
|
-
signal.hann
|
|
1385
|
+
signal.get_window("hann", 1024),
|
|
1386
|
+
sample_rate=512,
|
|
1387
|
+
epoch=-1,
|
|
1385
1388
|
)
|
|
1386
1389
|
filt = numpy.array([1, 0])
|
|
1387
1390
|
|
gwpy/types/array.py
CHANGED
|
@@ -34,6 +34,11 @@ from math import modf
|
|
|
34
34
|
import numpy
|
|
35
35
|
|
|
36
36
|
from astropy.units import Quantity
|
|
37
|
+
try:
|
|
38
|
+
from astropy.utils.compat.numpycompat import COPY_IF_NEEDED
|
|
39
|
+
except ImportError: # astropy < 6.1
|
|
40
|
+
from astropy.utils import minversion
|
|
41
|
+
COPY_IF_NEEDED = None if minversion(numpy, "2.0.0.dev") else False
|
|
37
42
|
|
|
38
43
|
from ..detector import Channel
|
|
39
44
|
from ..detector.units import parse_unit
|
|
@@ -119,8 +124,16 @@ class Array(Quantity):
|
|
|
119
124
|
unit = parse_unit(unit, parse_strict='warn')
|
|
120
125
|
|
|
121
126
|
# create new array
|
|
122
|
-
new = super().__new__(
|
|
123
|
-
|
|
127
|
+
new = super().__new__(
|
|
128
|
+
cls,
|
|
129
|
+
value,
|
|
130
|
+
unit=unit,
|
|
131
|
+
dtype=dtype,
|
|
132
|
+
copy=COPY_IF_NEEDED,
|
|
133
|
+
order=order,
|
|
134
|
+
subok=subok,
|
|
135
|
+
ndmin=ndmin,
|
|
136
|
+
)
|
|
124
137
|
|
|
125
138
|
# explicitly copy here to get ownership of the data,
|
|
126
139
|
# see (astropy/astropy#7244)
|
|
@@ -397,7 +410,7 @@ class Array(Quantity):
|
|
|
397
410
|
out = super().__array_ufunc__(function, method, *inputs, **kwargs)
|
|
398
411
|
# if a ufunc returns a scalar, return a Quantity
|
|
399
412
|
if not out.ndim:
|
|
400
|
-
return Quantity(out, copy=
|
|
413
|
+
return Quantity(out, copy=COPY_IF_NEEDED)
|
|
401
414
|
# otherwise return an array
|
|
402
415
|
return out
|
|
403
416
|
|
gwpy/types/index.py
CHANGED
|
@@ -23,6 +23,8 @@ import numpy
|
|
|
23
23
|
|
|
24
24
|
from astropy.units import Quantity
|
|
25
25
|
|
|
26
|
+
from .array import COPY_IF_NEEDED
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
class Index(Quantity):
|
|
28
30
|
"""1-D `~astropy.units.Quantity` array for indexing a `Series`
|
|
@@ -57,11 +59,11 @@ class Index(Quantity):
|
|
|
57
59
|
"""
|
|
58
60
|
if dtype is None:
|
|
59
61
|
dtype = max(
|
|
60
|
-
numpy.array(start, subok=True, copy=
|
|
61
|
-
numpy.array(step, subok=True, copy=
|
|
62
|
+
numpy.array(start, subok=True, copy=COPY_IF_NEEDED).dtype,
|
|
63
|
+
numpy.array(step, subok=True, copy=COPY_IF_NEEDED).dtype,
|
|
62
64
|
)
|
|
63
|
-
start = Quantity(start, dtype=dtype, copy=
|
|
64
|
-
step = Quantity(step, dtype=dtype, copy=
|
|
65
|
+
start = Quantity(start, dtype=dtype, copy=COPY_IF_NEEDED)
|
|
66
|
+
step = Quantity(step, dtype=dtype, copy=COPY_IF_NEEDED).to(start.unit)
|
|
65
67
|
stop = start + step * num
|
|
66
68
|
return cls(
|
|
67
69
|
numpy.arange(
|
|
@@ -71,7 +73,7 @@ class Index(Quantity):
|
|
|
71
73
|
dtype=dtype,
|
|
72
74
|
)[:num],
|
|
73
75
|
unit=start.unit,
|
|
74
|
-
copy=
|
|
76
|
+
copy=COPY_IF_NEEDED,
|
|
75
77
|
)
|
|
76
78
|
|
|
77
79
|
@property
|
gwpy/types/tests/test_array.py
CHANGED
|
@@ -88,23 +88,25 @@ class TestArray(object):
|
|
|
88
88
|
|
|
89
89
|
# -- test basic construction ----------------
|
|
90
90
|
|
|
91
|
-
def
|
|
92
|
-
"""
|
|
91
|
+
def assert_new(self, array):
|
|
92
|
+
"""Run basic assertions for a new instance of the type under test.
|
|
93
93
|
"""
|
|
94
|
-
# test basic empty contructor
|
|
95
|
-
with pytest.raises(TypeError):
|
|
96
|
-
self.TEST_CLASS()
|
|
97
|
-
|
|
98
|
-
# test with some data
|
|
99
|
-
array = self.create()
|
|
100
94
|
utils.assert_array_equal(array.value, self.data)
|
|
101
95
|
|
|
102
96
|
# test that copy=True ensures owndata
|
|
103
97
|
assert self.create(copy=False).flags.owndata is False
|
|
104
98
|
assert self.create(copy=True).flags.owndata is True
|
|
105
99
|
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
def test_new_empty(self):
|
|
101
|
+
"""Test that `Array()` raises an exception.
|
|
102
|
+
"""
|
|
103
|
+
with pytest.raises(TypeError):
|
|
104
|
+
self.TEST_CLASS()
|
|
105
|
+
|
|
106
|
+
def test_new(self):
|
|
107
|
+
"""Test Array creation.
|
|
108
|
+
"""
|
|
109
|
+
self.assert_new(self.create())
|
|
108
110
|
|
|
109
111
|
def test_unit(self, array):
|
|
110
112
|
# test default unit is dimensionless
|
gwpy/types/tests/test_array2d.py
CHANGED
|
@@ -198,7 +198,7 @@ class TestArray2D(_TestSeries):
|
|
|
198
198
|
|
|
199
199
|
But still represent the output as an `Array2D` with `Index` arrays.
|
|
200
200
|
|
|
201
|
-
This tests regression of https://
|
|
201
|
+
This tests regression of https://gitlab.com/gwpy/gwpy/-/issues/1504.
|
|
202
202
|
"""
|
|
203
203
|
# create an array with indices
|
|
204
204
|
a = self.create()
|
|
@@ -239,18 +239,14 @@ class TestArray2D(_TestSeries):
|
|
|
239
239
|
with pytest.raises(IndexError):
|
|
240
240
|
array.value_at(1.6, 4.8)
|
|
241
241
|
|
|
242
|
-
@pytest.mark.skip("not implemented for >1D arrays")
|
|
243
242
|
def test_pad(self):
|
|
244
|
-
|
|
243
|
+
pytest.skip("not implemented for >1D arrays")
|
|
245
244
|
|
|
246
|
-
@pytest.mark.skip("not implemented for >1D arrays")
|
|
247
245
|
def test_pad_index(self):
|
|
248
|
-
|
|
246
|
+
pytest.skip("not implemented for >1D arrays")
|
|
249
247
|
|
|
250
|
-
@pytest.mark.skip("not implemented for >1D arrays")
|
|
251
248
|
def test_pad_asymmetric(self):
|
|
252
|
-
|
|
249
|
+
pytest.skip("not implemented for >1D arrays")
|
|
253
250
|
|
|
254
|
-
@pytest.mark.skip("not applicable for >1D arrays")
|
|
255
251
|
def test_single_getitem_not_created(self):
|
|
256
|
-
|
|
252
|
+
pytest.skip("not implemented for >1D arrays")
|