Patch Processing

Open in JupyterLite

Patch-processing methods return new patches and are listed in the dascore.proc API.

import numpy as np

import dascore as dc

patch = dc.get_example_patch()

Shape and missing values

transpose changes dimension order, squeeze removes length-one dimensions, and dropna removes labels containing null values.

transposed = patch.transpose("time", "distance")

flat = patch.select(distance=0, samples=True)
squeezed = flat.squeeze()

data = np.array(patch.data)
data[:, 0] = np.nan
without_nan = patch.new(data=data).dropna("time")

transpose() with no arguments rotates dimension order. squeeze() can name dimensions to remove and rejects any named dimension whose length is not one. dropna keeps coordinate and data indexing synchronized.

Decimate

Patch.decimate downsamples a dimension and applies an anti-aliasing filter by default.

waves = dc.examples.get_example_patch(
    "sin_wav",
    sample_rate=1000,
    frequency=[200, 10],
    channel_count=2,
)

iir = waves.decimate(time=10, filter_type="iir")
fir = waves.decimate(time=10, filter_type="fir")
aliased = waves.decimate(time=10, filter_type=None)

Use filter_type=None only when aliasing is intentional or filtering has already been performed.

iir.viz.wiggle(show=True);
aliased.viz.wiggle(show=True);

Here the filtered result removes the 200 Hz component before reducing the 1,000 Hz sample rate. The unfiltered result folds that energy into a lower apparent frequency.

Taper

Patch.taper applies a cosine taper to a fraction of a dimension. A tuple controls the two ends; None leaves one end unchanged.

ones = patch.new(data=np.ones_like(patch.data))
both_ends = ones.taper(time=0.1)
different_ends = ones.taper(distance=(0.1, 0.3))
end_only = ones.taper(distance=(None, 0.1))
different_ends.viz.waterfall(show=True);

A scalar applies the same fraction at both ends. Tuple values let asymmetric records taper more strongly at one boundary, and may also be expressed with compatible units where supported.

See the edge-effects recipe for tapering before filters.

Rolling windows

Patch.rolling provides moving-window reductions. Incomplete windows produce NaNs, which can be removed with dropna.

event = dc.get_example_patch("example_event_1")
smoothed = event.rolling(time=50, samples=True).mean().dropna("time")
smoothed.viz.waterfall(show=True);

Rolling objects provide reductions such as mean, median, min, max, std, and sum. Specify a coordinate-unit window or set samples=True for an exact sample count.

Gain

Automatic gain control

Passing window to Patch.normalize divides every sample by a centered local norm. This raises weak arrivals but removes amplitude comparability across the record.

gained = event.normalize("time", norm="l2", window=0.1)
gained.viz.waterfall(scale=0.5, show=True);

norm may be l2 (RMS), l1 (mean absolute value), or max. Window length uses coordinate units unless samples=True. Reflected edge values keep the output shape and avoid the incomplete-window NaNs produced by rolling.

Without window, normalize uses one norm for the complete trace. With a window, every sample receives its own local scale. This distinction matters when amplitudes themselves carry scientific meaning.

Power gain

Patch.pow_coord multiplies data by a coordinate raised to a power. Because the curve depends only on position, relative amplitudes remain comparable between traces.

spread_corrected = event.pow_coord(time=2)
both_dimensions = event.pow_coord(time=2, distance=1)

The coordinate count begins at one rather than zero, so the first sample retains its amplitude. Time powers of one or two are common geometric-spreading corrections; other dimensions can be corrected in the same call.

Spectral whitening

Patch.whiten balances spectral amplitudes while largely preserving phase. It is commonly used before ambient-noise correlation.

signal = dc.get_example_patch(
    "sin_wav",
    frequency=[1, 10, 20, 54, 66],
    amplitude=[1, 2, 3, 4, 9],
    duration=1,
    sample_rate=200,
)

white = signal.whiten(time=(20, 80), smooth_size=1)
white.viz.wiggle(show=True);

hard_band = signal.whiten(time=(20, 40, 60, 80))
frequency_patch = signal.dft("time", real=True)
frequency_white = frequency_patch.whiten(time=(20, 40, 60, 80))
time_white = frequency_white.idft()

A four-value band controls the taper around the whitened interval. Frequency-domain patches are accepted and remain in that domain; use idft() when a time-domain result is needed. water_level stabilizes frequencies with near-zero amplitude.

smooth_size divides by a smoothed spectrum rather than forcing every bin to equal amplitude. This usually preserves broad spectral shape while suppressing narrow peaks.