Module redvox.common.cross_stats

This module contains functions for computing the cross correlation between data sets of equal or unequal length.

Expand source code
"""
This module contains functions for computing the cross correlation between data sets of equal or unequal length.
"""

from typing import Tuple

import numpy as np
from scipy import signal

import redvox.common.errors as errors


def xcorr_all(
    sig: np.ndarray, sig_ref: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """
    Generalized two-sensor cross correlation, including unequal lengths.

    :param sig: The original signal with the same sample rate in Hz as sig_ref.
    :param sig_ref: The reference signal with the same sample rate in Hz as sig..
    :return: A 4-tuple containing xcorr_indexes, xcorr, xcorr_offset_index, and xcorr_offset_samples. xcorr_indexes
             are relative to sig_ref. xcorr is the normalized cross-correlation. xcorr_offset_index contains the index
             of max xcorr. xcorr_offset_samples is the number of offset samples for xcorr.
    """
    # Generalized two-sensor cross correlation, including unequal lengths
    # Same as main, but more outputs
    sig_len: int = len(sig)
    ref_len: int = len(sig_ref)
    # Faster as floats
    sig = 1.0 * sig
    sig_ref = 1.0 * sig_ref
    if sig_len > ref_len:
        # Cross Correlation 'full' sums over the dimension of sigX
        xcorr_indexes: np.ndarray = np.arange(1 - sig_len, ref_len)
        xcorr: np.ndarray = signal.correlate(sig_ref, sig, mode="full")
        # Normalize
        xcorr /= np.sqrt(sig_len * ref_len) * sig.std() * sig_ref.std()
        xcorr_offset_index: np.ndarray = xcorr.argmax()
        xcorr_offset_samples: np.ndarray = xcorr_indexes[xcorr_offset_index]
    elif sig_len < ref_len:
        # Cross Correlation 'full' sums over the dimension of sigY
        xcorr_indexes = np.arange(1 - ref_len, sig_len)
        xcorr = signal.correlate(sig, sig_ref, mode="full")
        # Normalize
        xcorr /= np.sqrt(sig_len * ref_len) * sig.std() * sig_ref.std()
        xcorr_offset_index = xcorr.argmax()
        # Flip sign
        xcorr_offset_samples = -xcorr_indexes[xcorr_offset_index]
    elif sig_len == ref_len:
        # Cross correlation is centered in the middle of the record and has length sig_len
        # Fastest, o(sig_len) and can use FFT solution
        if sig_len % 2 == 0:
            xcorr_indexes = np.arange(-int(sig_len / 2), int(sig_len / 2))
        else:
            xcorr_indexes = np.arange(-int(sig_len / 2), int(sig_len / 2) + 1)

        xcorr = signal.correlate(sig_ref, sig, mode="same")
        # Normalize
        xcorr /= np.sqrt(sig_len * ref_len) * sig.std() * sig_ref.std()
        xcorr_offset_index = xcorr.argmax()
        xcorr_offset_samples = xcorr_indexes[xcorr_offset_index]
    else:
        raise errors.RedVoxError("One of the waveforms is broken")

    return xcorr_indexes, xcorr, xcorr_offset_index, xcorr_offset_samples


def xcorr_main(
    sig: np.ndarray, sig_ref: np.ndarray, sample_rate_hz: float
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Generalized two-sensor cross correlation, including unequal lengths. Provides summarized results.

    :param sig: The original signal with the same sample rate in Hz as sig_ref.
    :param sig_ref: The reference signal with the same sample rate in Hz as sig.
    :param sample_rate_hz: The sample rate in Hz.
    :return: A 3-tuple containing xcorr_normalized_max, xcorr_offset_samples, and xcorr_offset_seconds.
             Where xcorr_normalized_max is the maxx xcorr, xcorr_offset_samples is the number of offset samples for the
             max xcorr, and xcorr_offset_seconds is samples converted to offset seconds for the max xcorr,
             requires sample rate in Hz.
    """

    xcorr_result: Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray] = xcorr_all(
        sig, sig_ref
    )
    xcorr: np.ndarray = xcorr_result[1]
    xcorr_offset_samples: np.ndarray = xcorr_result[3]

    # noinspection PyArgumentList
    xcorr_normalized_max: np.ndarray = xcorr.max()
    xcorr_offset_seconds: np.ndarray = xcorr_offset_samples / sample_rate_hz

    return xcorr_normalized_max, xcorr_offset_samples, xcorr_offset_seconds

Functions

def xcorr_all(sig: numpy.ndarray, sig_ref: numpy.ndarray) ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray]

Generalized two-sensor cross correlation, including unequal lengths.

:param sig: The original signal with the same sample rate in Hz as sig_ref. :param sig_ref: The reference signal with the same sample rate in Hz as sig.. :return: A 4-tuple containing xcorr_indexes, xcorr, xcorr_offset_index, and xcorr_offset_samples. xcorr_indexes are relative to sig_ref. xcorr is the normalized cross-correlation. xcorr_offset_index contains the index of max xcorr. xcorr_offset_samples is the number of offset samples for xcorr.

Expand source code
def xcorr_all(
    sig: np.ndarray, sig_ref: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """
    Generalized two-sensor cross correlation, including unequal lengths.

    :param sig: The original signal with the same sample rate in Hz as sig_ref.
    :param sig_ref: The reference signal with the same sample rate in Hz as sig..
    :return: A 4-tuple containing xcorr_indexes, xcorr, xcorr_offset_index, and xcorr_offset_samples. xcorr_indexes
             are relative to sig_ref. xcorr is the normalized cross-correlation. xcorr_offset_index contains the index
             of max xcorr. xcorr_offset_samples is the number of offset samples for xcorr.
    """
    # Generalized two-sensor cross correlation, including unequal lengths
    # Same as main, but more outputs
    sig_len: int = len(sig)
    ref_len: int = len(sig_ref)
    # Faster as floats
    sig = 1.0 * sig
    sig_ref = 1.0 * sig_ref
    if sig_len > ref_len:
        # Cross Correlation 'full' sums over the dimension of sigX
        xcorr_indexes: np.ndarray = np.arange(1 - sig_len, ref_len)
        xcorr: np.ndarray = signal.correlate(sig_ref, sig, mode="full")
        # Normalize
        xcorr /= np.sqrt(sig_len * ref_len) * sig.std() * sig_ref.std()
        xcorr_offset_index: np.ndarray = xcorr.argmax()
        xcorr_offset_samples: np.ndarray = xcorr_indexes[xcorr_offset_index]
    elif sig_len < ref_len:
        # Cross Correlation 'full' sums over the dimension of sigY
        xcorr_indexes = np.arange(1 - ref_len, sig_len)
        xcorr = signal.correlate(sig, sig_ref, mode="full")
        # Normalize
        xcorr /= np.sqrt(sig_len * ref_len) * sig.std() * sig_ref.std()
        xcorr_offset_index = xcorr.argmax()
        # Flip sign
        xcorr_offset_samples = -xcorr_indexes[xcorr_offset_index]
    elif sig_len == ref_len:
        # Cross correlation is centered in the middle of the record and has length sig_len
        # Fastest, o(sig_len) and can use FFT solution
        if sig_len % 2 == 0:
            xcorr_indexes = np.arange(-int(sig_len / 2), int(sig_len / 2))
        else:
            xcorr_indexes = np.arange(-int(sig_len / 2), int(sig_len / 2) + 1)

        xcorr = signal.correlate(sig_ref, sig, mode="same")
        # Normalize
        xcorr /= np.sqrt(sig_len * ref_len) * sig.std() * sig_ref.std()
        xcorr_offset_index = xcorr.argmax()
        xcorr_offset_samples = xcorr_indexes[xcorr_offset_index]
    else:
        raise errors.RedVoxError("One of the waveforms is broken")

    return xcorr_indexes, xcorr, xcorr_offset_index, xcorr_offset_samples
def xcorr_main(sig: numpy.ndarray, sig_ref: numpy.ndarray, sample_rate_hz: float) ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]

Generalized two-sensor cross correlation, including unequal lengths. Provides summarized results.

:param sig: The original signal with the same sample rate in Hz as sig_ref. :param sig_ref: The reference signal with the same sample rate in Hz as sig. :param sample_rate_hz: The sample rate in Hz. :return: A 3-tuple containing xcorr_normalized_max, xcorr_offset_samples, and xcorr_offset_seconds. Where xcorr_normalized_max is the maxx xcorr, xcorr_offset_samples is the number of offset samples for the max xcorr, and xcorr_offset_seconds is samples converted to offset seconds for the max xcorr, requires sample rate in Hz.

Expand source code
def xcorr_main(
    sig: np.ndarray, sig_ref: np.ndarray, sample_rate_hz: float
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Generalized two-sensor cross correlation, including unequal lengths. Provides summarized results.

    :param sig: The original signal with the same sample rate in Hz as sig_ref.
    :param sig_ref: The reference signal with the same sample rate in Hz as sig.
    :param sample_rate_hz: The sample rate in Hz.
    :return: A 3-tuple containing xcorr_normalized_max, xcorr_offset_samples, and xcorr_offset_seconds.
             Where xcorr_normalized_max is the maxx xcorr, xcorr_offset_samples is the number of offset samples for the
             max xcorr, and xcorr_offset_seconds is samples converted to offset seconds for the max xcorr,
             requires sample rate in Hz.
    """

    xcorr_result: Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray] = xcorr_all(
        sig, sig_ref
    )
    xcorr: np.ndarray = xcorr_result[1]
    xcorr_offset_samples: np.ndarray = xcorr_result[3]

    # noinspection PyArgumentList
    xcorr_normalized_max: np.ndarray = xcorr.max()
    xcorr_offset_seconds: np.ndarray = xcorr_offset_samples / sample_rate_hz

    return xcorr_normalized_max, xcorr_offset_samples, xcorr_offset_seconds