gwpy 3.0.7__py3-none-any.whl → 3.0.9__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/qtransform.py +1 -1
- gwpy/detector/tests/test_units.py +3 -0
- gwpy/detector/units.py +12 -3
- gwpy/frequencyseries/frequencyseries.py +13 -4
- gwpy/io/cache.py +24 -1
- gwpy/io/datafind.py +1 -0
- gwpy/io/ffldatafind.py +27 -16
- gwpy/io/tests/test_ffldatafind.py +10 -3
- gwpy/plot/gps.py +7 -2
- gwpy/plot/tests/test_gps.py +1 -0
- gwpy/plot/tests/test_tex.py +3 -0
- gwpy/plot/tex.py +8 -6
- gwpy/segments/flag.py +14 -3
- gwpy/segments/tests/test_flag.py +53 -0
- gwpy/signal/filter_design.py +4 -3
- gwpy/signal/spectral/_lal.py +1 -1
- gwpy/signal/tests/test_coherence.py +9 -9
- gwpy/signal/tests/test_filter_design.py +3 -3
- gwpy/spectrogram/tests/test_spectrogram.py +2 -2
- gwpy/testing/fixtures.py +2 -19
- gwpy/testing/utils.py +43 -19
- gwpy/time/_tconvert.py +19 -33
- gwpy/time/tests/test_time.py +4 -45
- gwpy/timeseries/core.py +4 -2
- gwpy/timeseries/io/cache.py +47 -29
- gwpy/timeseries/io/gwf/framecpp.py +11 -2
- gwpy/timeseries/io/gwf/lalframe.py +21 -0
- gwpy/timeseries/io/losc.py +8 -2
- gwpy/timeseries/tests/test_io_cache.py +74 -0
- gwpy/timeseries/tests/test_io_gwf_lalframe.py +17 -0
- gwpy/timeseries/tests/test_timeseries.py +78 -36
- gwpy/types/array.py +16 -3
- gwpy/types/index.py +7 -5
- gwpy/types/sliceutils.py +5 -1
- gwpy/types/tests/test_array2d.py +4 -0
- gwpy/types/tests/test_series.py +26 -0
- gwpy/utils/shell.py +2 -2
- gwpy/utils/sphinx/zenodo.py +118 -61
- gwpy/utils/tests/test_shell.py +2 -2
- gwpy/utils/tests/test_sphinx_zenodo.py +175 -0
- {gwpy-3.0.7.dist-info → gwpy-3.0.9.dist-info}/METADATA +8 -7
- {gwpy-3.0.7.dist-info → gwpy-3.0.9.dist-info}/RECORD +49 -48
- {gwpy-3.0.7.dist-info → gwpy-3.0.9.dist-info}/WHEEL +1 -1
- gwpy/utils/sphinx/epydoc.py +0 -104
- {gwpy-3.0.7.dist-info → gwpy-3.0.9.dist-info}/LICENSE +0 -0
- {gwpy-3.0.7.dist-info → gwpy-3.0.9.dist-info}/entry_points.txt +0 -0
- {gwpy-3.0.7.dist-info → gwpy-3.0.9.dist-info}/top_level.txt +0 -0
|
@@ -175,11 +175,11 @@ class TestSpectrogram(_TestArray2D):
|
|
|
175
175
|
|
|
176
176
|
# test simple filter
|
|
177
177
|
a2 = array.filter(*zpk)
|
|
178
|
-
utils.
|
|
178
|
+
utils.assert_quantity_sub_equal(array * fresp, a2)
|
|
179
179
|
|
|
180
180
|
# test inplace filtering
|
|
181
181
|
array.filter(lti, inplace=True)
|
|
182
|
-
utils.
|
|
182
|
+
utils.assert_quantity_sub_equal(array, a2)
|
|
183
183
|
|
|
184
184
|
# test errors
|
|
185
185
|
with pytest.raises(TypeError):
|
gwpy/testing/fixtures.py
CHANGED
|
@@ -30,7 +30,7 @@ import numpy
|
|
|
30
30
|
|
|
31
31
|
from matplotlib import rc_context
|
|
32
32
|
|
|
33
|
-
from ..plot.tex import
|
|
33
|
+
from ..plot.tex import has_tex
|
|
34
34
|
from ..timeseries import TimeSeries
|
|
35
35
|
from ..utils.decorators import deprecated_function
|
|
36
36
|
from .utils import TemporaryFilename
|
|
@@ -52,25 +52,8 @@ def tmpfile():
|
|
|
52
52
|
|
|
53
53
|
# -- plotting -----------------------------------------------------------------
|
|
54
54
|
|
|
55
|
-
def _test_usetex():
|
|
56
|
-
"""Return `True` if we can render figures using `text.usetex`.
|
|
57
|
-
"""
|
|
58
|
-
from matplotlib import pyplot
|
|
59
|
-
with rc_context(rc={'text.usetex': True}):
|
|
60
|
-
fig = pyplot.figure()
|
|
61
|
-
fig.gca()
|
|
62
|
-
try:
|
|
63
|
-
fig.canvas.draw()
|
|
64
|
-
except RuntimeError:
|
|
65
|
-
return False
|
|
66
|
-
else:
|
|
67
|
-
return True
|
|
68
|
-
finally:
|
|
69
|
-
pyplot.close(fig)
|
|
70
|
-
|
|
71
|
-
|
|
72
55
|
SKIP_TEX = pytest.mark.skipif(
|
|
73
|
-
not
|
|
56
|
+
not has_tex(),
|
|
74
57
|
reason='TeX is not available',
|
|
75
58
|
)
|
|
76
59
|
|
gwpy/testing/utils.py
CHANGED
|
@@ -23,7 +23,6 @@ import os.path
|
|
|
23
23
|
import subprocess
|
|
24
24
|
import tempfile
|
|
25
25
|
from contextlib import contextmanager
|
|
26
|
-
from distutils.version import LooseVersion
|
|
27
26
|
from importlib import import_module
|
|
28
27
|
from itertools import zip_longest
|
|
29
28
|
from pathlib import Path
|
|
@@ -35,12 +34,14 @@ from numpy.testing import (assert_array_equal, assert_allclose)
|
|
|
35
34
|
|
|
36
35
|
from astropy.time import Time
|
|
37
36
|
|
|
38
|
-
from
|
|
37
|
+
from gwpy.io.cache import file_segment
|
|
38
|
+
from gwpy.utils.decorators import deprecated_function
|
|
39
39
|
|
|
40
40
|
# -- useful constants ---------------------------------------------------------
|
|
41
41
|
|
|
42
42
|
TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
|
|
43
43
|
TEST_GWF_FILE = os.path.join(TEST_DATA_DIR, 'HLV-HW100916-968654552-1.gwf')
|
|
44
|
+
TEST_GWF_SPAN = file_segment(TEST_GWF_FILE)
|
|
44
45
|
TEST_HDF5_FILE = os.path.join(TEST_DATA_DIR, 'HLV-HW100916-968654552-1.hdf')
|
|
45
46
|
|
|
46
47
|
|
|
@@ -83,8 +84,9 @@ def skip_missing_dependency(module): # pragma: no cover
|
|
|
83
84
|
"be removed in GWpy 3.1.0",
|
|
84
85
|
))
|
|
85
86
|
def module_older_than(module, minversion): # pragma: no cover
|
|
87
|
+
from packaging.version import Version
|
|
86
88
|
mod = import_module(module)
|
|
87
|
-
return
|
|
89
|
+
return Version(mod.__version__) < Version(minversion)
|
|
88
90
|
|
|
89
91
|
|
|
90
92
|
@deprecated_function(message=(
|
|
@@ -148,26 +150,42 @@ def _assert_quantity(q1, q2, array_assertion=assert_array_equal):
|
|
|
148
150
|
array_assertion(q1.value, q2.value)
|
|
149
151
|
|
|
150
152
|
|
|
151
|
-
def assert_quantity_sub_equal(
|
|
152
|
-
|
|
153
|
+
def assert_quantity_sub_equal(
|
|
154
|
+
a,
|
|
155
|
+
b,
|
|
156
|
+
*attrs,
|
|
157
|
+
almost_equal=False,
|
|
158
|
+
exclude=None,
|
|
159
|
+
**kwargs,
|
|
160
|
+
):
|
|
161
|
+
"""Assert that two `~gwpy.types.Array` objects are the same (or almost).
|
|
153
162
|
|
|
154
163
|
Parameters
|
|
155
164
|
----------
|
|
156
165
|
a, b : `~gwpy.types.Array`
|
|
157
|
-
|
|
166
|
+
The arrays to be tested (can be subclasses).
|
|
158
167
|
|
|
159
168
|
*attrs
|
|
160
|
-
|
|
169
|
+
The list of attributes to test, defaults to all.
|
|
161
170
|
|
|
162
171
|
almost_equal : `bool`, optional
|
|
163
|
-
|
|
164
|
-
i.e. require exact matches
|
|
172
|
+
Allow the numpy array's to be 'almost' equal, default: `False`,
|
|
173
|
+
i.e. require exact matches.
|
|
165
174
|
|
|
166
175
|
exclude : `list`, optional
|
|
167
|
-
|
|
176
|
+
A list of attributes to exclude from the test.
|
|
177
|
+
|
|
178
|
+
kwargs
|
|
179
|
+
Other keyword arguments are passed to the array comparison operator
|
|
180
|
+
`numpy.testing.assert_array_equal` or `numpy.testing.assert_allclose`.
|
|
181
|
+
|
|
182
|
+
See also
|
|
183
|
+
--------
|
|
184
|
+
numpy.testing.assert_array_equal
|
|
185
|
+
numpy.testing.assert_allclose
|
|
168
186
|
"""
|
|
169
187
|
# get value test method
|
|
170
|
-
if
|
|
188
|
+
if almost_equal:
|
|
171
189
|
assert_array = assert_allclose
|
|
172
190
|
else:
|
|
173
191
|
assert_array = assert_array_equal
|
|
@@ -175,8 +193,7 @@ def assert_quantity_sub_equal(a, b, *attrs, **kwargs):
|
|
|
175
193
|
# parse attributes to be tested
|
|
176
194
|
if not attrs:
|
|
177
195
|
attrs = a._metadata_slots
|
|
178
|
-
|
|
179
|
-
attrs = [attr for attr in attrs if attr not in exclude]
|
|
196
|
+
attrs = [attr for attr in attrs if attr not in (exclude or [])]
|
|
180
197
|
|
|
181
198
|
# don't assert indexes that don't exist for both
|
|
182
199
|
def _check_index(dim):
|
|
@@ -193,7 +210,7 @@ def assert_quantity_sub_equal(a, b, *attrs, **kwargs):
|
|
|
193
210
|
|
|
194
211
|
# test data
|
|
195
212
|
assert_attributes(a, b, *attrs)
|
|
196
|
-
assert_array(a.value, b.value)
|
|
213
|
+
assert_array(a.value, b.value, **kwargs)
|
|
197
214
|
|
|
198
215
|
|
|
199
216
|
def assert_attributes(a, b, *attrs):
|
|
@@ -313,11 +330,18 @@ def TemporaryFilename(*args, **kwargs): # pylint: disable=invalid-name
|
|
|
313
330
|
os.remove(name)
|
|
314
331
|
|
|
315
332
|
|
|
316
|
-
def test_read_write(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
def test_read_write(
|
|
334
|
+
data,
|
|
335
|
+
format,
|
|
336
|
+
extension=None,
|
|
337
|
+
autoidentify=True,
|
|
338
|
+
read_args=[],
|
|
339
|
+
read_kw={},
|
|
340
|
+
write_args=[],
|
|
341
|
+
write_kw={},
|
|
342
|
+
assert_equal=assert_quantity_sub_equal,
|
|
343
|
+
assert_kw={},
|
|
344
|
+
):
|
|
321
345
|
"""Test that data can be written to and read from a file in some format
|
|
322
346
|
|
|
323
347
|
Parameters
|
gwpy/time/_tconvert.py
CHANGED
|
@@ -23,14 +23,13 @@ Peter Shawhan.
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import datetime
|
|
26
|
-
import warnings
|
|
27
26
|
from decimal import Decimal
|
|
28
27
|
from numbers import Number
|
|
29
28
|
|
|
30
|
-
from dateutil.parser import parse as parse_datestr
|
|
31
|
-
|
|
32
29
|
from astropy.units import Quantity
|
|
33
30
|
|
|
31
|
+
from dateparser import parse as dateparser_parse
|
|
32
|
+
|
|
34
33
|
from . import (Time, LIGOTimeGPS)
|
|
35
34
|
|
|
36
35
|
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>'
|
|
@@ -224,7 +223,11 @@ def from_gps(gps):
|
|
|
224
223
|
# special case strings
|
|
225
224
|
|
|
226
225
|
def _now():
|
|
227
|
-
|
|
226
|
+
try:
|
|
227
|
+
now = datetime.datetime.now(datetime.UTC)
|
|
228
|
+
except AttributeError: # python < 3.11
|
|
229
|
+
now = datetime.datetime.utcnow()
|
|
230
|
+
return now.replace(microsecond=0)
|
|
228
231
|
|
|
229
232
|
|
|
230
233
|
def _today():
|
|
@@ -260,35 +263,18 @@ def _str_to_datetime(datestr):
|
|
|
260
263
|
except KeyError: # any other string
|
|
261
264
|
pass
|
|
262
265
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
with warnings.catch_warnings():
|
|
276
|
-
# don't allow lazy passing of time-zones
|
|
277
|
-
warnings.simplefilter("error", RuntimeWarning)
|
|
278
|
-
try:
|
|
279
|
-
return parse_datestr(datestr)
|
|
280
|
-
except RuntimeWarning as exc:
|
|
281
|
-
if MAYA_ERROR:
|
|
282
|
-
raise exc from MAYA_ERROR
|
|
283
|
-
exc.args = (
|
|
284
|
-
f"{str(exc).rstrip('.')}. Try installing 'maya' which can "
|
|
285
|
-
"handle more complex date strings.",
|
|
286
|
-
)
|
|
287
|
-
raise
|
|
288
|
-
except (ValueError, TypeError) as exc: # improve error reporting
|
|
289
|
-
exc.args = ("Cannot parse date string {0!r}: {1}".format(
|
|
290
|
-
datestr, exc.args[0]),)
|
|
291
|
-
raise
|
|
266
|
+
result = dateparser_parse(
|
|
267
|
+
datestr,
|
|
268
|
+
settings={
|
|
269
|
+
"TIMEZONE": "UTC",
|
|
270
|
+
"RETURN_AS_TIMEZONE_AWARE": True,
|
|
271
|
+
"TO_TIMEZONE": "UTC",
|
|
272
|
+
"PREFER_DATES_FROM": "current_period",
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
if result is None:
|
|
276
|
+
raise ValueError("failed to parse '{datestr}' as datetime")
|
|
277
|
+
return result
|
|
292
278
|
|
|
293
279
|
|
|
294
280
|
def _datetime_to_time(dtm):
|
gwpy/time/tests/test_time.py
CHANGED
|
@@ -19,11 +19,9 @@
|
|
|
19
19
|
"""Tests for :mod:`gwpy.time`
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from contextlib import nullcontext
|
|
23
22
|
from datetime import datetime
|
|
24
23
|
from decimal import Decimal
|
|
25
24
|
from operator import attrgetter
|
|
26
|
-
from unittest import mock
|
|
27
25
|
|
|
28
26
|
import pytest
|
|
29
27
|
|
|
@@ -55,10 +53,6 @@ YESTERDAY = 1126137617
|
|
|
55
53
|
|
|
56
54
|
|
|
57
55
|
@pytest.mark.freeze_time(FREEZE)
|
|
58
|
-
@pytest.mark.parametrize("use_maya", (
|
|
59
|
-
False,
|
|
60
|
-
pytest.param(True, marks=pytest.mark.requires("maya")),
|
|
61
|
-
))
|
|
62
56
|
@pytest.mark.parametrize(("in_", "out"), [
|
|
63
57
|
(1126259462, int(GW150914)),
|
|
64
58
|
(1235635623.7500002, LIGOTimeGPS(1235635623, 750000200)),
|
|
@@ -66,6 +60,7 @@ YESTERDAY = 1126137617
|
|
|
66
60
|
('0', 0),
|
|
67
61
|
('Jan 1 2017', 1167264018),
|
|
68
62
|
('Sep 14 2015 09:50:45.391', GW150914),
|
|
63
|
+
('Oct 30 2016 12:34 CST', 1161887657),
|
|
69
64
|
((2017, 1, 1), 1167264018),
|
|
70
65
|
(datetime(2017, 1, 1), 1167264018),
|
|
71
66
|
(Time(57754, format='mjd'), 1167264018),
|
|
@@ -83,30 +78,15 @@ YESTERDAY = 1126137617
|
|
|
83
78
|
('tomorrow', TOMORROW),
|
|
84
79
|
('yesterday', YESTERDAY),
|
|
85
80
|
])
|
|
86
|
-
def test_to_gps(in_, out
|
|
87
|
-
"""Test that :func:`to_gps` works
|
|
88
|
-
"""
|
|
89
|
-
if use_maya: # do nothing
|
|
90
|
-
ctx = nullcontext()
|
|
91
|
-
else: # force 'install maya' to error to use dateutil
|
|
92
|
-
ctx = mock.patch.dict("sys.modules", {"maya": None})
|
|
93
|
-
with ctx:
|
|
94
|
-
assert time.to_gps(in_) == out
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@pytest.mark.requires("maya")
|
|
98
|
-
@pytest.mark.parametrize(("in_", "out"), [
|
|
99
|
-
('Oct 30 2016 12:34 CST', 1161887657),
|
|
100
|
-
])
|
|
101
|
-
def test_to_gps_maya(in_, out):
|
|
102
|
-
"""Test that :func:`gwpy.time.to_gps` works with maya.
|
|
81
|
+
def test_to_gps(in_, out):
|
|
82
|
+
"""Test that :func:`to_gps` works.
|
|
103
83
|
"""
|
|
104
84
|
assert time.to_gps(in_) == out
|
|
105
85
|
|
|
106
86
|
|
|
107
87
|
@pytest.mark.parametrize(("in_", "err"), [
|
|
108
88
|
(Quantity(1, 'm'), UnitConversionError),
|
|
109
|
-
('random string',
|
|
89
|
+
('random string', ValueError),
|
|
110
90
|
])
|
|
111
91
|
def test_to_gps_error(in_, err):
|
|
112
92
|
"""Test that :func:`gwpy.time.to_gps` errors when it should.
|
|
@@ -115,27 +95,6 @@ def test_to_gps_error(in_, err):
|
|
|
115
95
|
time.to_gps(in_)
|
|
116
96
|
|
|
117
97
|
|
|
118
|
-
@mock.patch.dict("sys.modules", {"maya": None})
|
|
119
|
-
def test_to_gps_dateparser_error_propagation_nomaya():
|
|
120
|
-
with pytest.raises(
|
|
121
|
-
RuntimeWarning,
|
|
122
|
-
match=(
|
|
123
|
-
"tzname CST identified but not understood.(.*) "
|
|
124
|
-
"Try installing 'maya' (.*)"
|
|
125
|
-
),
|
|
126
|
-
):
|
|
127
|
-
time.to_gps("Oct 30 2016 12:34 CST")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@pytest.mark.requires("maya")
|
|
131
|
-
@mock.patch("maya.when", side_effect=ValueError("test chain"))
|
|
132
|
-
def test_to_gps_dateparser_error_propagation_maya(_):
|
|
133
|
-
with pytest.raises(RuntimeWarning) as exc:
|
|
134
|
-
time.to_gps("Oct 30 2016 12:34 CST")
|
|
135
|
-
# validate that the maya exception was chained to the dateutil one
|
|
136
|
-
assert str(exc.value.__cause__) == "test chain"
|
|
137
|
-
|
|
138
|
-
|
|
139
98
|
@pytest.mark.parametrize('in_, out', [
|
|
140
99
|
(1167264018, datetime(2017, 1, 1)),
|
|
141
100
|
('1167264018', datetime(2017, 1, 1)),
|
gwpy/timeseries/core.py
CHANGED
|
@@ -1288,16 +1288,18 @@ class TimeSeriesBaseDict(OrderedDict):
|
|
|
1288
1288
|
# find observatory for this group
|
|
1289
1289
|
if observatory is None:
|
|
1290
1290
|
try:
|
|
1291
|
-
|
|
1291
|
+
obs = ''.join(
|
|
1292
1292
|
sorted(set(c.ifo[0] for c in channellist)))
|
|
1293
1293
|
except TypeError as exc:
|
|
1294
1294
|
raise ValueError(
|
|
1295
1295
|
"Cannot parse list of IFOs from channel names",
|
|
1296
1296
|
) from exc
|
|
1297
1297
|
|
|
1298
|
+
else:
|
|
1299
|
+
obs = observatory
|
|
1298
1300
|
# find frames
|
|
1299
1301
|
cache = io_datafind.find_urls(
|
|
1300
|
-
|
|
1302
|
+
obs,
|
|
1301
1303
|
frametype,
|
|
1302
1304
|
start,
|
|
1303
1305
|
end,
|
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
|
+
)
|
|
@@ -401,6 +401,7 @@ def read_frvect(vect, epoch, start, end, name=None, series_class=TimeSeries):
|
|
|
401
401
|
dim = vect.GetDim(0)
|
|
402
402
|
dx = dim.dx
|
|
403
403
|
x0 = dim.startX
|
|
404
|
+
xunit = dim.GetUnitX() or None
|
|
404
405
|
|
|
405
406
|
# start and end GPS times of this FrVect
|
|
406
407
|
dimstart = epoch + x0
|
|
@@ -428,8 +429,16 @@ def read_frvect(vect, epoch, start, end, name=None, series_class=TimeSeries):
|
|
|
428
429
|
unit = vect.GetUnitY() or None
|
|
429
430
|
|
|
430
431
|
# create array
|
|
431
|
-
series = series_class(
|
|
432
|
-
|
|
432
|
+
series = series_class(
|
|
433
|
+
arr,
|
|
434
|
+
t0=dimstart+nxstart*dx,
|
|
435
|
+
dt=dx,
|
|
436
|
+
name=name,
|
|
437
|
+
channel=name,
|
|
438
|
+
xunit=xunit,
|
|
439
|
+
unit=unit,
|
|
440
|
+
copy=False,
|
|
441
|
+
)
|
|
433
442
|
|
|
434
443
|
# add information to channel
|
|
435
444
|
series.channel.sample_rate = series.sample_rate.value
|
|
@@ -149,12 +149,33 @@ 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()
|
|
155
163
|
for name in channels:
|
|
156
164
|
ts = _read_channel(stream, str(name), start=start, duration=duration)
|
|
157
165
|
out[name] = series_class.from_lal(ts, copy=False)
|
|
166
|
+
tsend = ts.epoch + getattr(ts.data.data, "size", 0) * ts.deltaT
|
|
167
|
+
if (end - tsend) >= ts.deltaT: # one (probably) sample missing
|
|
168
|
+
# if the available data simply didn't go all the way to the end
|
|
169
|
+
# lalframe would have errored (and we protect against that above),
|
|
170
|
+
# so we know there is data missing.
|
|
171
|
+
# see https://git.ligo.org/lscsoft/lalsuite/-/issues/710
|
|
172
|
+
ts = _read_channel(
|
|
173
|
+
stream,
|
|
174
|
+
str(name),
|
|
175
|
+
start=tsend,
|
|
176
|
+
duration=float(end - tsend),
|
|
177
|
+
)
|
|
178
|
+
out[name] = out[name].append(ts.data.data, inplace=False)
|
|
158
179
|
lalframe.FrStreamSeek(stream, epoch)
|
|
159
180
|
return out
|
|
160
181
|
|
gwpy/timeseries/io/losc.py
CHANGED
|
@@ -35,7 +35,10 @@ from gwosc.locate import get_urls
|
|
|
35
35
|
|
|
36
36
|
from .. import (StateVector, TimeSeries)
|
|
37
37
|
from ...io import (gwf as io_gwf, hdf5 as io_hdf5)
|
|
38
|
-
from ...io.cache import
|
|
38
|
+
from ...io.cache import (
|
|
39
|
+
file_segment,
|
|
40
|
+
sieve as sieve_cache,
|
|
41
|
+
)
|
|
39
42
|
from ...io.utils import file_path
|
|
40
43
|
from ...detector.units import parse_unit
|
|
41
44
|
from ...segments import Segment
|
|
@@ -149,7 +152,10 @@ def fetch_gwosc_data(detector, start, end, cls=TimeSeries, **kwargs):
|
|
|
149
152
|
key in kwargs}
|
|
150
153
|
if 'sample_rate' in url_kw: # format as Hertz
|
|
151
154
|
url_kw['sample_rate'] = Quantity(url_kw['sample_rate'], 'Hz').value
|
|
152
|
-
cache =
|
|
155
|
+
cache = sieve_cache(
|
|
156
|
+
get_urls(detector, int(start), int(ceil(end)), **url_kw),
|
|
157
|
+
segment=span,
|
|
158
|
+
)
|
|
153
159
|
# if event dataset, pick shortest file that covers the request
|
|
154
160
|
# -- this is a bit hacky, and presumes that only an event dataset
|
|
155
161
|
# -- would be produced with overlapping files.
|
|
@@ -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]
|
|
@@ -28,6 +28,7 @@ import numpy
|
|
|
28
28
|
from gwdatafind.utils import file_segment
|
|
29
29
|
|
|
30
30
|
from ...io.cache import write_cache
|
|
31
|
+
from ...segments import Segment
|
|
31
32
|
from ...testing.utils import (
|
|
32
33
|
assert_dict_equal,
|
|
33
34
|
assert_quantity_sub_equal,
|
|
@@ -184,3 +185,19 @@ def test_write_no_ifo(tmp_path, data):
|
|
|
184
185
|
{None: data},
|
|
185
186
|
tmp
|
|
186
187
|
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_read_missing_sample():
|
|
191
|
+
"""Check that giving non-sampled start time doesn't result in missing data.
|
|
192
|
+
|
|
193
|
+
See https://git.ligo.org/computing/helpdesk/-/issues/4774 and
|
|
194
|
+
https://git.ligo.org/lscsoft/lalsuite/-/issues/710.
|
|
195
|
+
"""
|
|
196
|
+
data = TimeSeries.read(
|
|
197
|
+
TEST_GWF_PATH,
|
|
198
|
+
"H1:LDAS-STRAIN",
|
|
199
|
+
start=968654552.6709,
|
|
200
|
+
end=968654553,
|
|
201
|
+
format="gwf.lalframe",
|
|
202
|
+
)
|
|
203
|
+
assert data.span == Segment(968654552.670898432, 968654553.0)
|