Key Concepts#

This guide introduces the fundamental concepts you need to understand to work effectively with GWpy.


Core Data Structures#

GWpy provides three main data structures for representing scientific data:

Time-domain data

Detector strain, auxiliary channels, and any signal that varies with time.

TimeSeries
Frequency-domain data

Power spectra, amplitude spectral densities, transfer functions.

FrequencySeries
Time-frequency data

How frequency content evolves over time.

Spectrogram

TimeSeries: Time-Domain Data#

A TimeSeries represents data sampled at regular intervals in time. Think of it as a NumPy array with metadata:

from gwpy.timeseries import TimeSeries
import numpy as np

# Create a TimeSeries
data = TimeSeries([1, 2, 3, 4, 5], sample_rate=10, unit="m")

print(f"Data: {data.value}")         # [1 2 3 4 5]
print(f"Times: {data.times}")        # [0.0, 0.1, 0.2, 0.3, 0.4] s
print(f"Sample rate: {data.sample_rate}")  # 10.0 Hz
print(f"Duration: {data.duration}")  # 0.5 s

Key Attributes:

sample_rate

Data rate for this TimeSeries in samples per second (Hertz).

dt

Time (seconds) between successive samples.

t0

GPS start time of this series.

span

Time (seconds) spanned by this series.

unit

The physical unit of these data.

channel

Instrumental channel associated with these data.

FrequencySeries: Frequency-Domain Data#

A FrequencySeries represents data in the frequency domain, typically from a Fourier transform:

from gwpy.timeseries import TimeSeries

# Create time-domain signal
ts = TimeSeries.get("L1", 1126259446, 1126259478)

# Transform to frequency domain
asd = ts.asd(fftlength=4)

print(f"Frequency spacing: {asd.df}")  # 0.25 Hz
print(f"Frequency range: {asd.f0} to {asd.frequencies[-1]}")

Key Attributes:

df

Frequency spacing of this FrequencySeries

f0

Starting frequency for this FrequencySeries

frequencies

Series of frequencies for each sample

Spectrogram: Time-Frequency Data#

A Spectrogram is a 2D array showing how frequency content changes over time:

from gwpy.timeseries import TimeSeries

ts = TimeSeries.get("L1", 1126259446, 1126259478)

# Create spectrogram with 1-second time bins
specgram = ts.spectrogram(stride=1, fftlength=4)

print(f"Shape: {specgram.shape}")  # (time_bins, frequency_bins)
print(f"Time resolution: {specgram.dt}")  # 1.0 s
print(f"Frequency resolution: {specgram.df}")  # 0.25 Hz

Think of it as a stack of frequency spectra, one for each time bin.

Key Attributes:

t0

GPS time of first time bin.

dt

Time (seconds) between successive bins.

f0

Starting frequency for these data.

df

Frequency spacing for these data.

times

Series of GPS times for each sample

frequencies

Series of frequencies for these data.


GPS Time#

Gravitational-wave detectors use GPS time, which counts seconds since January 6, 1980, 00:00:00 UTC (the GPS epoch).

Why GPS Time?#

  • No leap seconds: Unlike UTC, GPS time is continuous

  • High precision: Integer seconds with sub-second fractions

  • Universal: Same at all detector sites

Converting Between Time Systems#

GWpy’s gwpy.time module helps convert between time systems:

from gwpy.time import to_gps, from_gps

# Convert from human-readable time
gps = to_gps("September 14, 2015 09:50:45 UTC")
print(gps)  # 1126259462

# Convert back
date = from_gps(1126259462)
print(date)  # 2015-09-14 09:50:45 UTC

Tip

GWpy’s to_gps() function is very flexible:

to_gps("now")
to_gps("Jan 1, 2020")
to_gps("2015-09-14 09:50:45")
to_gps(1126259462)  # GPS already, returns unchanged

Units and Quantities#

GWpy uses Astropy’s units framework to handle physical units safely:

from gwpy.timeseries import TimeSeries

# Units are preserved in operations
ts = TimeSeries([1, 2, 3], sample_rate=10, unit="m")

print(ts.unit)  # m (meters)

# Convert units
ts_mm = ts.to("mm")
print(ts_mm.value)  # [1000, 2000, 3000]
print(ts_mm.unit)   # mm

Why Units Matter#

Units catch errors at runtime:

from astropy import units as u

distance = 1 * u.m
time = 2 * u.s

speed = distance / time
print(speed)  # 0.5 m / s  ✓

# This would raise an error:
# result = distance + time  # Can't add meters and seconds!

Common Units in GWpy#

  • Strain: dimensionless (detector output)

  • ASD: strain / sqrt(Hz) (noise amplitude)

  • PSD: strain^2 / Hz (noise power)

  • Time: s (seconds)

  • Frequency: Hz (Hertz)


Channels#

In gravitational-wave detectors, a channel is a named data stream from a sensor or derived calculation.

Channel Naming#

Channels follow the pattern IFO:SUBSYSTEM-SIGNAL_NAME:

# Examples:
"L1:GDS-CALIB_STRAIN"      # LIGO Livingston calibrated strain
"H1:GDS-CALIB_STRAIN"      # LIGO Hanford calibrated strain
"V1:Hrec_hoft_16384Hz"     # Virgo strain
"L1:PSL-ISS_PDA_OUT_DQ"    # Auxiliary channel

Components:

  • L1 / H1 / V1 - Interferometer name

  • GDS - Subsystem (Global Diagnostics System)

  • CALIB_STRAIN - Signal name (calibrated strain)

See also

The Channel class for more about channel names


Data Quality and Segments#

Not all detector data are suitable for analysis. Data quality flags indicate when data meets certain criteria.

Segments and SegmentLists#

A Segment is a semi-open GPS time interval [start, stop):

from gwpy.segments import Segment, SegmentList

# Single segment
seg = Segment(1126259447, 1126259479)
print(f"Duration: {abs(seg)} seconds")  # 32 seconds

# List of segments
seglist = SegmentList([
    Segment(0, 10),
    Segment(20, 30),
    Segment(25, 35),  # Overlaps with previous
])

# Coalesce overlapping segments
seglist.coalesce()
print(seglist)  # [(0, 10), (20, 35)]

Data Quality Flags#

A DataQualityFlag has:

  • known segments - when data exists

  • active segments - when the condition is true

from gwpy.segments import DataQualityFlag

# Example: detector in "analysis ready" state
dqflag = DataQualityFlag.query(
    "L1:DMT-ANALYSIS_READY:1",
    1126259447,
    1126259479,
)

print(f"Known time: {abs(dqflag.known)}")
print(f"Active time: {abs(dqflag.active)}")
print(f"Efficiency: {dqflag.active / dqflag.known * 100:.1f}%")

See also

Data-quality segments: for a complete guide to segments.


Signal Processing Basics#

Filtering#

Filters remove unwanted frequencies from signals:

from gwpy.timeseries import TimeSeries

ts = TimeSeries.get("L1", 1126259446, 1126259478)

# Highpass: remove frequencies below 30 Hz
hp = ts.highpass(30)

# Lowpass: remove frequencies above 300 Hz
lp = ts.lowpass(300)

# Bandpass: keep only 30-300 Hz
bp = ts.bandpass(30, 300)

# Notch: remove a single frequency (e.g., 60 Hz power line)
notched = ts.notch(60)

Whitening#

Whitening normalizes the frequency-domain amplitude, making all frequencies equally important:

# Whiten using a 4-second ASD estimate
whitened = ts.whiten(fftlength=4)

This is crucial for matched filtering and viewing weak signals.

Spectral Estimation#

Compute power spectral density (PSD) or amplitude spectral density (ASD):

# PSD: power per frequency bin
psd = ts.psd(fftlength=4, method="median")

# ASD: sqrt(PSD), more intuitive units
asd = ts.asd(fftlength=4, method="median")

Common methods:

  • 'welch' - mean of overlapping FFTs (default)

  • 'median' - median of overlapping FFTs (robust to outliers)

  • 'bartlett' - mean of non-overlapping FFTs


Arrays and Metadata#

GWpy objects are built on NumPy arrays but add metadata:

Accessing the Array#

from gwpy.timeseries import TimeSeries
import numpy as np

ts = TimeSeries([1, 2, 3, 4, 5], sample_rate=1)

# Get the underlying array
arr = ts.value  # NumPy array

# Or use as array directly
mean = np.mean(ts)
std = np.std(ts)

Operations Preserve Metadata#

# Arithmetic operations preserve metadata
ts2 = ts * 2
print(ts2.sample_rate)  # Still 1.0 Hz

# Slicing preserves metadata
segment = ts[2:5]
print(segment.t0)  # Adjusted to slice start time

NumPy Compatibility#

Use GWpy objects with NumPy functions:

import numpy as np

# NumPy functions work directly
maximum = np.max(ts)
smoothed = np.convolve(ts, np.ones(5)/5, mode="same")

# But note: some operations may lose metadata

Next Steps#

Now that you understand these concepts:

📖 Browse User Guide

Learn to accomplish specific tasks

User Guide
🔍 Explore API

Detailed reference for all classes

API Reference
🎨 See Examples

Working code examples

Examples gallery