Signal synchronization tools
Project description
synctools
Python tools for synchronization of multi-channel data streams via null combinations.
Overview
synctools is a Python package for synchronizing misaligned time series data via optimization of null channels. It corrects for static time offsets (and clock jitter, if independently measured) between different measurement systems or channels, enabling accurate combination and analysis of multi-channel measurements. The synchronization method depends on the ability to form null combinations of at least two or at most three signals. In practice, this means that the user has fed the same signal to two measurement instruments or channels (TwoSignal combination, or split measurement), or has performed a three-signal test (ThreeSignal combination, or three-signal test).
The package is designed for applications in precision metrology, interferometry, and gravitational wave detection, where multiple instruments measure signals that need to be synchronized before combination.
Features
- Multi-signal synchronization: Synchronize multi-channel data streams by looking at null combinations of 2 or 3 signals
- Time offset correction: Automatically determine and correct time offsets between signals
- Clock jitter handling: Account for clock jitter using optional clock reference signals
- Flexible models: Support for "total" and "fluc" synchronization models
- Time and frequency domain optimization: Optimize in either time or frequency domain
- Spectral analysis: Integrated spectral analysis tools
Installation
Requirements
- Python >= 3.8
- numpy >= 1.20.0
- scipy >= 1.7.0
- speckit (for spectral analysis)
- pytdi (for time-shifting operations)
Install from PyPI
pip install synctools
Install from source
git clone <repository-url>
cd synctools
pip install -e .
Development dependencies
For development and testing:
pip install -e ".[dev]"
Quick Start
Synchronizing Two Signals
import numpy as np
from synctools import sync_signals
# Define parameters for spectral analysis.
p_lpsd = {
"olap": "default",
"bmin": 1,
"Lmin": 1,
"Jdes": 500,
"Kdes": 100,
"order": 2,
"win": "Kaiser",
"psll": 250,
}
# Create two frequency time series (in Hz)
fs = 10.0 # Sampling rate (Hz)
n_samples = 10000
signal1 = 1e6 + 0.1 * np.sin(2 * np.pi * 0.01 * np.arange(n_samples) / fs)
signal2 = signal1.copy() # Identical signals (zero offset expected)
# Synchronize the signals
unsynced, synced = sync_signals(
[signal1, signal2],
fs=fs,
p_lpsd=p_lpsd,
init_offsets=[0.0], # Initial guess for time offset (seconds)
model="fluc", # Use fluctuation model
domain="time", # Optimize in time domain
method="Nelder-Mead"
)
# Access results
print(f"Recovered time offset: {synced.timer_offsets[0]:.6f} seconds")
print(f"Synchronized frequency: {synced.freq['time'][:5]} Hz")
Synchronizing Three Signals
import numpy as np
from synctools import sync_signals
# Create three frequency time series
fs = 10.0
n_samples = 10000
t = np.arange(n_samples) / fs
signal1 = 1e6 + 0.1 * np.sin(2 * np.pi * 0.01 * t)
signal2 = 1e6 + 0.1 * np.sin(2 * np.pi * 0.01 * t)
signal3 = 1e6 + 0.1 * np.sin(2 * np.pi * 0.01 * t)
# Reuse the p_lpsd dictionary from the two-signal example above.
# Synchronize three signals
unsynced, synced = sync_signals(
[signal1, signal2, signal3],
fs=fs,
p_lpsd=p_lpsd,
init_offsets=[0.0, 0.0], # Two offsets for three signals
model="total",
domain="freq",
method="Powell"
)
# Access results
print(f"Time offsets: {synced.timer_offsets} seconds")
print(f"TDIR precision: {synced.TDIR_precision:.2e}")
With Clock References
If you have clock reference signals (differential clock measurements), you can provide them to account for clock jitter:
# Create clock reference arrays (differential clock frequency in Hz)
clock_ref1 = np.random.randn(n_samples) * 1e-9 # Small clock jitter
clock_ref2 = np.random.randn(n_samples) * 1e-9
unsynced, synced = sync_signals(
[signal1, signal2, signal3],
fs=fs,
p_lpsd=p_lpsd,
clock_refs=[clock_ref1, clock_ref2], # Clock references for secondary signals
init_offsets=[0.0, 0.0],
model="fluc"
)
Core Classes
FrequencyData
Represents a frequency time series with detrending capabilities:
from synctools import FrequencyData
fd = FrequencyData(
main_tot=signal_array, # 1D array, shape (n_samples,), units: Hz
fs=10.0, # Sampling rate, units: Hz
name='signal1', # Optional name
order=1 # Polynomial detrending order
)
# Access attributes
fd.total # Total frequency (Hz), shape (n_samples,)
fd.fit # Deterministic (fitted) frequency (Hz), shape (n_samples,)
fd.fluc # Stochastic (fluctuation) frequency (Hz), shape (n_samples,)
fd.tau # Time array (s), shape (n_samples,)
fd.fs # Sampling rate (Hz)
Synchronization
The main synchronization result object:
# After calling sync_signals(), access synced object:
synced.timer_offsets # Optimized time offsets (s), shape (n_signals-1,)
synced.freq['time'] # Synchronized frequency in time domain (Hz), shape (n_samples,)
synced.freq['asd'] # Frequency ASD (Hz/√Hz), shape (n_freq,)
synced.phase['time'] # Synchronized phase in time domain (rad), shape (n_samples,)
synced.phase['asd'] # Phase ASD (rad/√Hz), shape (n_freq,)
synced.fourier_freq # Fourier frequencies (Hz), shape (n_freq,)
synced.TDIR_precision # TDIR precision estimate (dimensionless)
synced.tau # Time array after truncation (s), shape (n_samples_trunc,)
API Reference
sync_signals()
Main entry point for signal synchronization.
Parameters:
in_signals(List[np.ndarray]): List of frequency signal arrays. Each array must be 1D with shape(n_samples,). Units: Hz. Must contain 2 or 3 signals.fs(float): Sampling rate. Units: Hz. Must be > 0.p_lpsd(Dict[str, Any]): SpecKit parameters dictionary for spectral analysis.init_offsets(Optional[List[float]]): Initial guesses for time offsets. Units: seconds. Length must belen(in_signals) - 1. If None, defaults to zeros.model(str): Synchronization model, either"total"or"fluc". Default:"total".domain(str): Optimization domain, either"time"or"freq". Default:"time".method(str): Optimization method forscipy.optimize.minimize. Default:"Nelder-Mead".interp_order(int): Interpolation order for time-shifting. Must be positive. Default: 121.n_truncate(Optional[int]): Number of points to truncate at each end. Must satisfyn_truncate < len(data) // 2. If None, auto-calculated.clock_refs(Optional[List[np.ndarray]]): Optional clock reference arrays. Each array must be 1D with shape(n_samples,). Units: Hz. Length must belen(in_signals) - 1.logger(Optional[logging.Logger]): Optional logger instance.
Returns:
unsynced_obj(TwoSignals or ThreeSignals): Object containing unsynchronized signal combination.synced_obj(Synchronization): Object containing synchronized results and optimization output.
Raises:
ValueError: If input validation fails (invalid array shapes, incompatible lengths, etc.).
Conversion Helpers
The root package also exports two stable conversion helpers:
convert_frequency_to_detrended_phase_in_time(data, fs): Integrates frequency data to phase and removes a linear phase trend.convert_phase_to_frequency_in_time(data, fs, prepend=np.nan): Differentiates phase data back to frequency.
sync_multiple_twosignals()
Use sync_multiple_twosignals() when you have one reference stream and multiple secondary streams, and each secondary should be synchronized independently against the reference. This is a star-topology batch wrapper around sync_signals([reference, secondary], ...); it is not a joint multi-signal null-combination solver.
Parameters:
in_signals(List[np.ndarray]): List of two or more 1D frequency arrays. The first array is the reference; each remaining array is synchronized against it. All arrays must have the same length.fs,p_lpsd,model,domain,method,interp_order,n_truncate,logger: Same meaning as insync_signals().init_offsets(Optional[List[Optional[List[float]]]]): One entry per secondary signal. Each entry is eitherNone(defaults to[0.0]) or a one-element offset list for that pair.clock_refs(Optional[List[Optional[np.ndarray]]]): One entry per secondary signal. Each entry is eitherNoneor a 1D differential clock reference for that pair.
Returns:
List[Tuple[TwoSignals, Synchronization]]: One(unsynced_obj, synced_obj)pair for each[reference, secondary]synchronization.
Use sync_signals() with three input arrays when the measurement is a true three-signal null combination and the offsets should be optimized jointly.
Advanced Usage
The stable public API is the root synctools namespace documented above and exported through synctools.__all__. The synctools.auxiliary module contains implementation utilities used by the public API; it is currently importable for expert workflows, but symbols outside the root namespace should be treated as provisional until the package reaches v1.0.
Examples
See the notebooks/ directory for detailed examples:
0.1_sync_signals.ipynb: Basic synchronization examples0.2_three-signals.ipynb: Three-signal synchronization examples
Testing
Run the test suite:
pytest
Run with coverage:
pytest --cov=synctools
License
BSD 3-Clause License
Authors
- Miguel Dovale (University of Arizona)
Citation
If you use synctools in your research, please cite appropriately.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file synctools-0.1.0.tar.gz.
File metadata
- Download URL: synctools-0.1.0.tar.gz
- Upload date:
- Size: 51.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f18eb8689f946d4e577b81a694112e5b7fc3f5225d5a0d7d6a8aefdc56c4078c
|
|
| MD5 |
3deec781e2b81fd8b845ce955a433b12
|
|
| BLAKE2b-256 |
4d57d9f38162ccb15602fc033b1af045d78dc7064bd7fd10708ac2bcc458a620
|
File details
Details for the file synctools-0.1.0-py3-none-any.whl.
File metadata
- Download URL: synctools-0.1.0-py3-none-any.whl
- Upload date:
- Size: 46.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d93e1ebf9a1e11134b7d67aa413eb2266ac0cc5310ccc0cafc883391084aca46
|
|
| MD5 |
a489cb163356f67bf944e481122c5672
|
|
| BLAKE2b-256 |
ea3c7715ef39c317ca291fa13191446cdccbc4b103bc61d47b5c001839d707c1
|