Patch Processing

The following shows some simple examples of patch processing. See the proc module for a list of all processing functions.

Basic

There are several “basic” processing functions which manipulate the patch metadata, shape, etc. Many of these are covered in the patch tutorial, but here are a few that aren’t:

Transpose

The transpose patch function patch function simply transposes the dimensions of the patch, either by rotating the dimensions or to a new specified dimension.

import dascore as dc

patch = dc.get_example_patch()
print(f"dims before transpose: {patch.dims}")

transposed = patch.transpose()
print(f"dims after transpose: {transposed.dims}")

# Dimension order can be manually specified as well.
transposed = patch.transpose("time", "distance")
dims before transpose: ('distance', 'time')
dims after transpose: ('time', 'distance')

Squeeze

squeeze removes dimensions which have a single value (see also numpy.squeeze).

import dascore as dc

patch = dc.get_example_patch()
# Select first distance value to make distance dim flat.

flat_patch = patch.select(distance=0, samples=True)
print(f"Pre-squeeze dims: {flat_patch.shape}")

squeezed = flat_patch.squeeze()
print(f"Post-squeeze dims: {squeezed.shape}")
Pre-squeeze dims: (1, 2000)
Post-squeeze dims: (2000,)

Dropna

The dropna patch function patch function drops “nullish” values from a given label.

import numpy as np

import dascore as dc

patch = dc.get_example_patch()

# Create a patch with nan values
data = np.array(patch.data)
data[:, 0] = np.nan
patch_with_nan = patch.new(data=data)

# Drop Nan along axis 1
dim = patch_with_nan.dims[1]
no_na_patch = patch_with_nan.dropna(dim)

assert not np.any(np.isnan(no_na_patch.data))

Decimate

decimate decimates a Patch along a given axis while by default performing low-pass filtering to avoid aliasing.

Data creation

First, we create a patch composed of two sine waves; one above the new decimation frequency and one below.

import dascore as dc

patch = dc.examples.get_example_patch(
    "sin_wav",
    sample_rate=1000,
    frequency=[200, 10],
    channel_count=2,
)
patch.viz.wiggle(show=True);

IIR filter

Next we decimate by 10x using IIR filter

decimated_iir = patch.decimate(time=10, filter_type='iir')
decimated_iir.viz.wiggle(show=True);

Notice the lowpass filter removed the 200 Hz signal and only the 10Hz wave remains.

FIR filter

Next we decimate by 10x using FIR filter.

decimated_fir = patch.decimate(time=10, filter_type='fir')
decimated_fir.viz.wiggle(show=True);

No Filter

Next, we decimate without a filter to purposely induce aliasing.

decimated_no_filt = patch.decimate(time=10, filter_type=None)
decimated_no_filt.viz.wiggle(show=True);

Taper

taper is used to taper the edges of a patch dimension to zero. To see this, let’s create a patch of all 1s and apply the taper function

import numpy as np
import dascore as dc

_patch = dc.get_example_patch()
patch_ones = _patch.new(data=np.ones_like(_patch.data))

The following code will apply a 10% taper, meaning a cosine taper is applied along the first and last 10% of the specified dimension.

patch_ones.taper(time=0.1).viz.waterfall();

Passing a tuple of values enables different amounts of tapering for each end of the dimension.

# Apply a 10% taper to the start and 30% to the end of distance dimension.
patch_ones.taper(distance=(0.1, 0.3)).viz.waterfall();

Either element of the tuple can be None which indicates no tapering is applied.

# Only apply 10% taper to the end of the distance dimension.
patch_ones.taper(distance=(None, 0.1)).viz.waterfall();

See also the edge effects recipe for using tapering to help filtering.

Rolling

The rolling patch function implements moving window operators similar to pandas rolling. It is useful for smoothing, calculating aggregated statistics, etc.

Here is an example of using a rolling mean to smooth along the time axis:

import dascore as dc

patch = dc.get_example_patch("example_event_1")
# Apply moving mean window over every 10 samples
dt = patch.get_coord('time').step

smoothed = patch.rolling(time=50*dt).mean()
smoothed.viz.waterfall(scale=.1);

Notice the nan values at the start of the time axis. These can be trimmed with Patch.dropna.

Whiten

The Patch.whiten function performs spectral whitening by balancing the amplitude spectra of the patch while leaving the phase (largely) unchanged. Spectral whitening is often a pre-processing step in ambient noise correlation workflows.

To demonstrate, we create some plotting code and an example patch.

import matplotlib.pyplot as plt
import numpy as np

import dascore as dc
from dascore.utils.time import to_float


rng = np.random.default_rng()


def plot_time_and_frequency(patch, channel=0):
    """ Make plots of time and frequency of patch with single channel."""
    sq_patch = patch.select(distance=channel, samples=True).squeeze()
    time_array = to_float(patch.get_array("time"))
    time = time_array - np.min(time_array)

    fig, (td_ax, fd_ax, phase_ax) = plt.subplots(1, 3, figsize=(9, 2.5))

    # Plot in time domain
    td_ax.plot(time, sq_patch.data, color="tab:blue")
    td_ax.set_title("Time Domain")
    td_ax.set_xlabel("time (s)")

    # Plot freq amplitdue
    ft_patch = sq_patch.dft("time", real=True)
    freq = ft_patch.get_array("ft_time")
    fd_ax.plot(freq, ft_patch.abs().data, color="tab:red")
    fd_ax.set_xlabel("Frequency (Hz)")
    fd_ax.set_title("Amplitude Spectra")

    # plot freq phase
    phase_ax.plot(freq, np.angle(ft_patch.data), color="tab:cyan")
    phase_ax.set_xlabel("Frequency (Hz)")
    phase_ax.set_title("Phase Angle")

    # fd_ax.set_xlim(0, 1000)
    return fig


def make_noisy_sine_patch():
    """Make a noisy sine wave patch."""
    patch = dc.get_example_patch(
        "sin_wav",
        frequency=[1, 10, 20, 54, 66],
        amplitude=[1, 2, 3, 4, 9],
        duration=1,
        sample_rate=200,
    )
    rand_noise = (rng.random(patch.data.shape) - 0.5) * 10
    patch = patch.new(data = patch.data + rand_noise)
    return patch
patch = make_noisy_sine_patch()
plot_time_and_frequency(patch);

The default whitening makes all spectral amplitudes equal.

white_patch = patch.whiten()
plot_time_and_frequency(white_patch);

The whitening can be restricted to certain frequency bands by specifying the dimension and a frequency range.

white_patch = patch.whiten(time=(20, 80))
plot_time_and_frequency(white_patch);

Four values can be used to control the start/end of the taper.

white_patch = patch.whiten(time=(20, 40, 60, 80))
plot_time_and_frequency(white_patch);

Whiten also supports using a smoothed amplitude for normalization which causes less drastic changes to the amplitude spectrum.

white_patch = patch.whiten(smooth_size=1)
_ = plot_time_and_frequency(white_patch)

white_patch = patch.whiten(smooth_size=2, time=(10, 20, 80, 90))
_ = plot_time_and_frequency(white_patch)

Whiten also accepts patches in the frequency domain, in which case a frequency patches are returned. This might be useful if whiten is only a part of a frequency domain workflow.

fft_patch = patch.dft("time", real=True)
dft_white = fft_patch.whiten(smooth_size=1, time=(20, 40, 60, 80))
td_patch = dft_white.idft()
plot_time_and_frequency(td_patch);

The water_level parameter can be useful for stabilizing frequencies that may have near-zero values.