gwpy 3.0.6__py3-none-any.whl → 3.0.7__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/io/gwf/__init__.py DELETED
@@ -1,768 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Copyright (C) Duncan Macleod (2014-2020)
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
- """I/O utilities for GWF files.
20
- """
21
-
22
- import importlib
23
- import warnings
24
- from functools import wraps
25
-
26
- import numpy
27
-
28
- from ...segments import (Segment, SegmentList)
29
- from ...time import (to_gps, LIGOTimeGPS)
30
- from ..cache import read_cache
31
- from ..utils import file_path
32
-
33
- __author__ = 'Duncan Macleod <duncan.macleod@ligo.org>'
34
-
35
- APIS = [
36
- "frameCPP",
37
- "LALFrame",
38
- "FrameL",
39
- ]
40
-
41
- # first 4 bytes of any valid GWF file (see LIGO-T970130 §4.3.1)
42
- GWF_SIGNATURE = b'IGWD'
43
-
44
-
45
- # -- library finding ----------------------------------------------------------
46
-
47
- def _import_gwf_library(library, package=__package__, prefix=""):
48
- """Utility method to import the relevant gwpy.io.gwf frame API.
49
-
50
- This is just a wrapper around :meth:`importlib.import_module` with
51
- a slightly nicer error message.
52
- """
53
- # import the frame library here to have any ImportErrors occur early
54
- try:
55
- return importlib.import_module(f".{prefix}{library}", package=package)
56
- except ImportError as exc:
57
- exc.args = (f"Cannot import {library} frame API: {exc}",)
58
- raise
59
-
60
-
61
- def get_default_gwf_api():
62
- """Return the preferred GWF API library.
63
-
64
- Examples
65
- --------
66
- If you have |LDAStools.frameCPP|_ installed:
67
-
68
- >>> from gwpy.io.gwf import get_default_gwf_api
69
- >>> get_default_gwf_api()
70
- 'frameCPP'
71
-
72
- Or, if you have |lalframe|_:
73
-
74
- >>> get_default_gwf_api()
75
- 'LALFrame'
76
-
77
- Or, if you have |FrameL|_:
78
-
79
- >>> get_default_gwf_api()
80
- 'FrameL'
81
-
82
- Otherwise:
83
-
84
- >>> get_default_gwf_api()
85
- ImportError: no GWF API available, please install a supported third-party
86
- GWF library and try again
87
- """
88
- for lib in APIS:
89
- try:
90
- _import_gwf_library(lib.lower())
91
- except ImportError:
92
- continue
93
- else:
94
- return lib
95
- raise ImportError(
96
- "no GWF API available, please install a supported third-party GWF "
97
- "library and try again",
98
- )
99
-
100
- try:
101
- DEFAULT_GWF_API = get_default_gwf_api()
102
- except ImportError:
103
- DEFAULT_GWF_API = None
104
-
105
-
106
- def _gwf_library_function(func):
107
- name = func.__name__
108
-
109
- @wraps(func)
110
- def wrapper(*args, **kwargs):
111
- api = DEFAULT_GWF_API or get_default_gwf_api()
112
- lib = _import_gwf_library(api.lower(), prefix="_")
113
- return getattr(lib, name)(*args, **kwargs)
114
-
115
- return wrapper
116
-
117
-
118
- # -- i/o ----------------------------------------------------------------------
119
-
120
- def identify_gwf(origin, filepath, fileobj, *args, **kwargs):
121
- """Identify a filename or file object as GWF
122
-
123
- This function is overloaded in that it will also identify a cache file
124
- as 'gwf' if the first entry in the cache contains a GWF file extension
125
- """
126
- # pylint: disable=unused-argument
127
-
128
- # try and read file descriptor
129
- if fileobj is not None:
130
- loc = fileobj.tell()
131
- fileobj.seek(0)
132
- try:
133
- if fileobj.read(4) == GWF_SIGNATURE:
134
- return True
135
- finally:
136
- fileobj.seek(loc)
137
- if filepath is not None:
138
- if filepath.endswith('.gwf'):
139
- return True
140
- if filepath.endswith(('.lcf', '.cache')):
141
- try:
142
- cache = read_cache(filepath)
143
- except IOError:
144
- return False
145
- else:
146
- if cache[0].path.endswith('.gwf'):
147
- return True
148
-
149
-
150
- def open_gwf(filename, mode='r'):
151
- """Open a filename for reading or writing GWF format data
152
-
153
- Parameters
154
- ----------
155
- filename : `str`
156
- the path to read from, or write to
157
-
158
- mode : `str`, optional
159
- either ``'r'`` (read) or ``'w'`` (write)
160
-
161
- Returns
162
- -------
163
- `LDAStools.frameCPP.IFrameFStream`
164
- the input frame stream (if `mode='r'`), or
165
- `LDAStools.frameCPP.IFrameFStream`
166
- the output frame stream (if `mode='w'`)
167
- """
168
- if mode not in ('r', 'w'):
169
- raise ValueError("mode must be either 'r' or 'w'")
170
- from LDAStools import frameCPP
171
- filename = file_path(filename)
172
- if mode == 'r':
173
- return frameCPP.IFrameFStream(str(filename))
174
- return frameCPP.OFrameFStream(str(filename))
175
-
176
-
177
- def write_frames(filename, frames, compression='GZIP', compression_level=None):
178
- """Write a list of frame objects to a file
179
-
180
- **Requires:** |LDAStools.frameCPP|_
181
-
182
- Parameters
183
- ----------
184
- filename : `str`
185
- path to write into
186
-
187
- frames : `list` of `LDAStools.frameCPP.FrameH`
188
- list of frames to write into file
189
-
190
- compression : `int`, `str`, optional
191
- name of compresion algorithm to use, or its endian-appropriate
192
- ID, choose from
193
-
194
- - ``'RAW'``
195
- - ``'GZIP'``
196
- - ``'DIFF_GZIP'``
197
- - ``'ZERO_SUPPRESS'``
198
- - ``'ZERO_SUPPRESS_OTHERWISE_GZIP'``
199
-
200
- compression_level : `int`, optional
201
- compression level for given method, default is ``6`` for GZIP-based
202
- methods, otherwise ``0``
203
- """
204
- from LDAStools import frameCPP
205
- from ._framecpp import (Compression, DefaultCompressionLevel)
206
-
207
- # handle compression arguments
208
- if not isinstance(compression, int):
209
- compression = Compression[compression]
210
- if compression_level is None:
211
- compression_level = DefaultCompressionLevel[compression.name]
212
-
213
- # open stream
214
- stream = open_gwf(filename, 'w')
215
-
216
- # write frames one-by-one
217
- if isinstance(frames, frameCPP.FrameH):
218
- frames = [frames]
219
- for frame in frames:
220
- stream.WriteFrame(frame, int(compression), int(compression_level))
221
- # stream auto-closes (apparently)
222
-
223
-
224
- def create_frame(time=0, duration=None, name='gwpy', run=-1, ifos=None):
225
- """Create a new :class:`~LDAStools.frameCPP.FrameH`
226
-
227
- **Requires:** |LDAStools.frameCPP|_
228
-
229
- Parameters
230
- ----------
231
- time : `float`, optional
232
- frame start time in GPS seconds
233
-
234
- duration : `float`, optional
235
- frame length in seconds
236
-
237
- name : `str`, optional
238
- name of project or other experiment description
239
-
240
- run : `int`, optional
241
- run number (number < 0 reserved for simulated data); monotonic for
242
- experimental runs
243
-
244
- ifos : `list`, optional
245
- list of interferometer prefices (e.g. ``'L1'``) associated with this
246
- frame
247
-
248
- Returns
249
- -------
250
- frame : :class:`~LDAStools.frameCPP.FrameH`
251
- the newly created frame header
252
- """
253
- from LDAStools import frameCPP
254
- from ._framecpp import DetectorLocation
255
-
256
- # create frame
257
- frame = frameCPP.FrameH()
258
-
259
- # add timing
260
- gps = to_gps(time)
261
- gps = frameCPP.GPSTime(gps.gpsSeconds, gps.gpsNanoSeconds)
262
- frame.SetGTime(gps)
263
- if duration is not None:
264
- frame.SetDt(float(duration))
265
-
266
- # add FrDetectors
267
- for prefix in ifos or []:
268
- frame.AppendFrDetector(
269
- frameCPP.GetDetector(DetectorLocation[prefix], gps),
270
- )
271
-
272
- # add descriptions
273
- frame.SetName(name)
274
- frame.SetRun(run)
275
-
276
- return frame
277
-
278
-
279
- def create_fradcdata(series, frame_epoch=0,
280
- channelgroup=0, channelid=0, nbits=16):
281
- """Create a `~frameCPP.FrAdcData` from a `~gwpy.types.Series`
282
-
283
- .. note::
284
-
285
- Currently this method is restricted to 1-dimensional arrays.
286
-
287
- Parameters
288
- ----------
289
- series : `~gwpy.types.Series`
290
- the input data array to store
291
-
292
- frame_epoch : `float`, `int`, optional
293
- the GPS start epoch of the `Frame` that will contain this
294
- data structure
295
-
296
- Returns
297
- -------
298
- frdata : `~frameCPP.FrAdcData`
299
- the newly created data structure
300
-
301
- Notes
302
- -----
303
- See Table 10 (§4.3.2.4) of LIGO-T970130 for more details
304
- """
305
- from LDAStools import frameCPP
306
-
307
- # assert correct type
308
- if not series.xunit.is_equivalent('s') or series.ndim != 1:
309
- raise TypeError("only 1-dimensional timeseries data can be "
310
- "written as FrAdcData")
311
-
312
- frdata = frameCPP.FrAdcData(
313
- _series_name(series),
314
- channelgroup,
315
- channelid,
316
- nbits,
317
- (1 / series.dx.to('s')).value
318
- )
319
- frdata.SetTimeOffset(
320
- float(to_gps(series.x0.value) - to_gps(frame_epoch)),
321
- )
322
- return frdata
323
-
324
-
325
- def _get_series_trange(series):
326
- if series.xunit.is_equivalent('s'):
327
- return abs(series.xspan)
328
- return 0
329
-
330
-
331
- def _get_series_frange(series):
332
- if series.xunit.is_equivalent('Hz'): # FrequencySeries
333
- return abs(series.xspan)
334
- elif series.ndim == 2 and series.yunit.is_equivalent('Hz'): # Spectrogram
335
- return abs(series.yspan)
336
- return 0
337
-
338
-
339
- def create_frprocdata(series, frame_epoch=0, comment=None,
340
- type=None, subtype=None, trange=None,
341
- fshift=0, phase=0, frange=None, bandwidth=0):
342
- """Create a `~frameCPP.FrAdcData` from a `~gwpy.types.Series`
343
-
344
- .. note::
345
-
346
- Currently this method is restricted to 1-dimensional arrays.
347
-
348
- Parameters
349
- ----------
350
- series : `~gwpy.types.Series`
351
- the input data array to store
352
-
353
- frame_epoch : `float`, `int`, optional
354
- the GPS start epoch of the `Frame` that will contain this
355
- data structure
356
-
357
- comment : `str`, optional
358
- comment
359
-
360
- type : `int`, `str`, optional
361
- type of data object
362
-
363
- subtype : `int`, `str`, optional
364
- subtype for f-Series
365
-
366
- trange : `float`, optional
367
- duration of sampled data
368
-
369
- fshift : `float`, optional
370
- frequency in the original data that corresponds to 0 Hz in the
371
- heterodyned series
372
-
373
- phase : `float`, optional
374
- phase of the heterodyning signal at start of dataset
375
-
376
- frange : `float`, optional
377
- frequency range
378
-
379
- bandwidth : `float, optional
380
- reoslution bandwidth
381
-
382
- Returns
383
- -------
384
- frdata : `~frameCPP.FrAdcData`
385
- the newly created data structure
386
-
387
- Notes
388
- -----
389
- See Table 17 (§4.3.2.11) of LIGO-T970130 for more details
390
- """
391
- from LDAStools import frameCPP
392
-
393
- # format auxiliary data
394
- if trange is None:
395
- trange = _get_series_trange(series)
396
- if frange is None:
397
- frange = _get_series_frange(series)
398
-
399
- return frameCPP.FrProcData(
400
- _series_name(series),
401
- str(comment or series.name),
402
- _get_frprocdata_type(series, type),
403
- _get_frprocdata_subtype(series, subtype),
404
- float(to_gps(series.x0.value) - to_gps(frame_epoch)),
405
- trange,
406
- fshift,
407
- phase,
408
- frange,
409
- bandwidth,
410
- )
411
-
412
-
413
- def create_frsimdata(series, frame_epoch=0, comment=None, fshift=0, phase=0):
414
- """Create a `~frameCPP.FrAdcData` from a `~gwpy.types.Series`
415
-
416
- .. note::
417
-
418
- Currently this method is restricted to 1-dimensional arrays.
419
-
420
- Parameters
421
- ----------
422
- series : `~gwpy.types.Series`
423
- the input data array to store
424
-
425
- frame_epoch : `float`, `int`, optional
426
- the GPS start epoch of the `Frame` that will contain this
427
- data structure
428
-
429
- fshift : `float`, optional
430
- frequency in the original data that corresponds to 0 Hz in the
431
- heterodyned series
432
-
433
- phase : `float`, optional
434
- phase of the heterodyning signal at start of dataset
435
-
436
- Returns
437
- -------
438
- frdata : `~frameCPP.FrSimData`
439
- the newly created data structure
440
-
441
- Notes
442
- -----
443
- See Table 20 (§4.3.2.14) of LIGO-T970130 for more details
444
- """
445
- from LDAStools import frameCPP
446
-
447
- # assert correct type
448
- if not series.xunit.is_equivalent('s'):
449
- raise TypeError("only timeseries data can be written as FrSimData")
450
-
451
- return frameCPP.FrSimData(
452
- _series_name(series),
453
- str(comment or series.name),
454
- (1 / series.dx.to('s')).value,
455
- float(to_gps(series.x0.value) - to_gps(frame_epoch)),
456
- fshift,
457
- phase,
458
- )
459
-
460
-
461
- def create_frvect(series):
462
- """Create a `~frameCPP.FrVect` from a `~gwpy.types.Series`
463
-
464
- .. note::
465
-
466
- Currently this method is restricted to 1-dimensional arrays.
467
-
468
- Parameters
469
- ----------
470
- series : `~gwpy.types.Series`
471
- the input data array to store
472
-
473
- Returns
474
- -------
475
- frvect : `~frameCPP.FrVect`
476
- the newly created data vector
477
- """
478
- from LDAStools import frameCPP
479
- from ._framecpp import FrVectType
480
-
481
- # create dimensions
482
- dims = frameCPP.Dimension(
483
- series.shape[0], # num elements
484
- series.dx.value, # step size
485
- str(series.dx.unit), # unit
486
- 0, # starting value
487
- )
488
-
489
- # create FrVect
490
- vect = frameCPP.FrVect(
491
- _series_name(series), # name
492
- int(FrVectType.find(series.dtype)), # data type enum
493
- series.ndim, # num dimensions
494
- dims, # dimension object
495
- str(series.unit), # unit
496
- )
497
-
498
- # populate FrVect
499
- vect.GetDataArray()[:] = numpy.require(series.value, requirements=['C'])
500
-
501
- return vect
502
-
503
-
504
- # -- utilities ----------------------------------------------------------------
505
-
506
- def num_channels(framefile):
507
- """Find the total number of channels in this framefile
508
-
509
- **Requires:** |LDAStools.frameCPP|_
510
-
511
- Parameters
512
- ----------
513
- framefile : `str`
514
- path to GWF-format file on disk
515
-
516
- Returns
517
- -------
518
- n : `int`
519
- the total number of channels found in the table of contents for this
520
- file
521
- """
522
- return len(get_channel_names(framefile))
523
-
524
-
525
- def get_channel_type(channel, framefile):
526
- """Find the channel type in a given GWF file
527
-
528
- **Requires:** |LDAStools.frameCPP|_
529
-
530
- Parameters
531
- ----------
532
- channel : `str`, `~gwpy.detector.Channel`
533
- name of data channel to find
534
-
535
- framefile : `str`
536
- path of GWF file in which to search
537
-
538
- Returns
539
- -------
540
- ctype : `str`
541
- the type of the channel ('adc', 'sim', or 'proc')
542
-
543
- Raises
544
- ------
545
- ValueError
546
- if the channel is not found in the table-of-contents
547
- """
548
- channel = str(channel)
549
- for name, type_ in _iter_channels(framefile):
550
- if channel == name:
551
- return type_
552
- raise ValueError(
553
- f"'{channel}' not found in table-of-contents for {framefile}",
554
- )
555
-
556
-
557
- def channel_in_frame(channel, framefile):
558
- """Determine whether a channel is stored in this framefile
559
-
560
- **Requires:** |LDAStools.frameCPP|_
561
-
562
- Parameters
563
- ----------
564
- channel : `str`
565
- name of channel to find
566
-
567
- framefile : `str`
568
- path of GWF file to test
569
-
570
- Returns
571
- -------
572
- inframe : `bool`
573
- whether this channel is included in the table of contents for
574
- the given framefile
575
- """
576
- return str(channel) in iter_channel_names(framefile)
577
-
578
-
579
- def iter_channel_names(framefile):
580
- """Iterate over the names of channels found in a GWF file.
581
-
582
- Parameters
583
- ----------
584
- framefile : `str`
585
- path of GWF file to read
586
-
587
- Returns
588
- -------
589
- channels : `generator`
590
- an iterator that will loop over the names of channels as read from
591
- the table of contents of the given GWF file
592
- """
593
- for name, _ in _iter_channels(framefile):
594
- yield name
595
-
596
-
597
- def get_channel_names(framefile):
598
- """Return a list of all channel names found in a GWF file
599
-
600
- This method just returns
601
-
602
- >>> list(iter_channel_names(framefile))
603
-
604
- Parameters
605
- ----------
606
- framefile : `str`
607
- path of GWF file to read
608
-
609
- Returns
610
- -------
611
- channels : `list` of `str`
612
- a `list` of channel names as read from the table of contents of
613
- the given GWF file
614
- """
615
- return list(iter_channel_names(framefile))
616
-
617
-
618
- @_gwf_library_function
619
- def iter_channels(framefile):
620
- """Yields the name and type of each channel in a GWF file.
621
-
622
- Parameters
623
- ----------
624
- framefile : `str`
625
- path of GWF file, or open file stream, to read
626
- """
627
- pass # decorator does the work
628
-
629
-
630
- def data_segments(paths, channel, warn=True):
631
- """Returns the segments containing data for a channel
632
-
633
- A frame is considered to contain data if a valid FrData structure
634
- (of any type) exists for the channel in that frame. No checks
635
- are directly made against the underlying FrVect structures.
636
-
637
- Parameters
638
- ----------
639
- paths : `list` of `str`
640
- a list of GWF file paths
641
-
642
- channel : `str`
643
- the name to check in each frame
644
-
645
- warn : `bool`, optional
646
- emit a `UserWarning` when a channel is not found in a frame
647
-
648
- Returns
649
- -------
650
- segments : `~gwpy.segments.SegmentList`
651
- the list of segments containing data
652
- """
653
- segments = SegmentList()
654
- for path in paths:
655
- segments.extend(_gwf_channel_segments(path, channel, warn=warn))
656
- return segments.coalesce()
657
-
658
-
659
- def _gwf_channel_segments(path, channel, warn=True):
660
- """Yields the segments containing data for ``channel`` in this GWF path
661
- """
662
- stream = open_gwf(path)
663
- # get segments for frames
664
- toc = stream.GetTOC()
665
- secs = toc.GetGTimeS()
666
- nano = toc.GetGTimeN()
667
- dur = toc.GetDt()
668
-
669
- readers = [getattr(stream, f"ReadFr{type_.title()}Data") for
670
- type_ in ("proc", "sim", "adc")]
671
-
672
- # for each segment, try and read the data for this channel
673
- for i, (s, ns, dt) in enumerate(zip(secs, nano, dur)):
674
- for read in readers:
675
- try:
676
- read(i, channel)
677
- except (IndexError, ValueError):
678
- continue
679
- readers = [read] # use this one from now on
680
- epoch = LIGOTimeGPS(s, ns)
681
- yield Segment(epoch, epoch + dt)
682
- break
683
- else: # none of the readers worked for this channel, warn
684
- if warn:
685
- warnings.warn(
686
- f"'{channel}' not found in frame {i} of {path}",
687
- )
688
-
689
-
690
- def _get_type(type_, enum):
691
- """Handle a type string, or just return an `int`
692
-
693
- Only to be called in relation to FrProcDataType and FrProcDataSubType
694
- """
695
- if isinstance(type_, int):
696
- return type_
697
- return enum[str(type_).upper()]
698
-
699
-
700
- def _get_frprocdata_type(series, type_):
701
- """Determine the appropriate `FrProcDataType` for this series
702
-
703
- Notes
704
- -----
705
- See Table 17 (§4.3.2.11) of LIGO-T970130 for more details
706
- """
707
- from ._framecpp import FrProcDataType
708
-
709
- if type_ is not None: # format user value
710
- return _get_type(type_, FrProcDataType)
711
-
712
- if series.ndim == 1 and series.xunit.is_equivalent("s"):
713
- type_ = FrProcDataType.TIME_SERIES
714
- elif series.ndim == 1 and series.xunit.is_equivalent("Hz"):
715
- type_ = FrProcDataType.FREQUENCY_SERIES
716
- elif series.ndim == 1:
717
- type_ = FrProcDataType.OTHER_1D_SERIES_DATA
718
- elif (
719
- series.ndim == 2
720
- and series.xunit.is_equivalent("s")
721
- and series.yunit.is_equivalent("Hz")
722
- ):
723
- type_ = FrProcDataType.TIME_FREQUENCY
724
- elif series.ndim > 2:
725
- type_ = FrProcDataType.MULTI_DIMENSIONAL
726
- else:
727
- type_ = FrProcDataType.UNKNOWN
728
-
729
- return type_
730
-
731
-
732
- def _get_frprocdata_subtype(series, subtype):
733
- """Determine the appropriate `FrProcDataSubType` for this series
734
-
735
- Notes
736
- -----
737
- See Table 17 (§4.3.2.11) of LIGO-T970130 for more details
738
- """
739
- from ._framecpp import FrProcDataSubType
740
-
741
- if subtype is not None: # format user value
742
- return _get_type(subtype, FrProcDataSubType)
743
-
744
- if series.unit == 'coherence':
745
- return FrProcDataSubType.COHERENCE
746
- return FrProcDataSubType.UNKNOWN
747
-
748
-
749
- def _series_name(series):
750
- """Returns the 'name' of a `Series` that should be written to GWF
751
-
752
- This is basically `series.name or str(series.channel) or ""`
753
-
754
- Parameters
755
- ----------
756
- series : `gwpy.types.Series`
757
- the input series that will be written
758
-
759
- Returns
760
- -------
761
- name : `str`
762
- the name to use when storing this series
763
- """
764
- return (
765
- series.name
766
- or str(series.channel or "")
767
- or None
768
- )