Skip to main content

pyCSLDV

Testing Python 3.10, 3.11, 3.12 MIT license Alpha

Continuous scanning laser Doppler vibrometry (CSLDV) in Python: Lissajous scan-trajectory design, virtual CSLDV experiments and reconstruction of operating deflection shapes (ODS) using the polynomial (sideband demodulation) approach.

In a CSLDV measurement the laser spot of the vibrometer sweeps continuously over the vibrating surface. For a harmonic response at frequency fz and a Lissajous scan with mirror frequencies fx and fy, the deflection shape modulates the measured velocity and appears as sidebands at fz ± n fx ± m fy. Demodulating these sidebands yields the coefficients of a two-dimensional Chebyshev series that describes the full-field ODS from a single-point sensor measurement.

The coefficients are recovered either by projecting the velocity onto each sideband in turn, or by fitting every mode at once as one global linear least-squares system. The second form carries complex poles instead of frequencies, so it also covers damped and complex modes, modes whose sidebands overlap, several sensors, and line scans.

This package is developed on the SDyPy template and is part of the wider SDyPy ecosystem effort.

Origin and attribution

pyCSLDV is an independent Python reimplementation of the Continuous Scanning Laser Doppler Vibrometry (CSLDV) Vibration Measurement & Simulation Suite (LabVIEW/MATLAB) by Joshua Bartlett and Pablo Tarazaga, FAST Laboratory, Texas A&M University, published under the CC-BY-4.0 license at zenodo.org/records/22032252 (DOI: 10.5281/zenodo.22032252; the concept DOI 10.5281/zenodo.21301125 always resolves to the latest version).

The measurement principle, the sideband-demodulation processing chain and the compensation of the mirror inertial lag follow the original suite, and the reconstruction is verified against a transcription of its ComputeODS.m (see tests/test_matlab_reference.py). The code is not a translation of the MATLAB routines:

  • the demodulation is formulated in complex arithmetic, which removes the explicit magnitude/phase quadrant and unwrapping heuristics;

  • the shape is kept in the Chebyshev basis instead of being converted to monomial coefficients, so the sideband normalization is applied explicitly rather than folded into a hard-coded transformation matrix;

  • the global phase is normalized on the dominant coefficient (align_phase) rather than on the response baseband phase;

  • overlapping sidebands are detected and reported;

  • besides the per-sideband projection, the coefficients of every mode can be fitted in a single linear least-squares system (demodulate_ods_2d, demodulate_ods_1d), which takes complex poles rather than real frequencies and so reconstructs damped and complex modes, resolves modes whose sidebands fall on top of each other, and fits several sensors and line scans;

  • the ODS rotation acts on the Chebyshev coefficients, so turning a shape onto another set of axes is exact rather than a resampling of a grid, and it is applied to the physical surface or to the normalized domain as the caller chooses (rotate_ods); the angle can be measured from the mirror feedback (scan_rotation) instead of being taken from the calibration record;

  • the scope is narrower: the LabVIEW acquisition and hardware control and the Rayleigh-Ritz model of the original suite have no counterpart here.

If you use pyCSLDV in academic work, please cite the original suite as well; complete citation metadata (with ORCIDs) is provided in the CITATION.cff file.

Development note

The initial Python implementation in this repository was written with Claude (Anthropic), working from the published source of the original suite.

The reconstruction chain is covered end to end by the test suite: an exact Chebyshev shape is recovered from a simulated measurement to within 1e-8, mirror and response phases are verified to be compensated, a plate mode measured with 5 % noise is reconstructed with MAC > 0.99, a damped complex mode is recovered from its pole, two modes whose sidebands coincide are separated, and the result is checked against the transcribed MATLAB reference implementation.

The chain is also run on a real measurement — the 4527 Hz plate scan published with the original suite — and agrees with the ODS that suite reconstructed from the same samples at MAC 0.9905, and at 0.9955 once the shape is turned onto the mirror axes. That is a comparison against one measurement processed by one other implementation, not a metrological validation; the package is at an alpha stage and results should be validated against your own reference data before being relied upon.

Installation

Install the released package from PyPI:

$ pip install pyCSLDV

or the current development version from the repository:

$ pip install git+https://github.com/ladisk/pyCSLDV.git

For development, clone the repository and install in editable mode:

$ uv venv
$ uv pip install -e ".[dev]"

Usage

A complete virtual experiment — simulate the LDV velocity signal for a known deflection shape, reconstruct the ODS and quantify the agreement:

import pycsldv

fs = 25000          # sampling frequency [Hz]
fx, fy = 1.4, 8.0   # scan frequencies [Hz]
fz = 2000.0         # response frequency [Hz]
n = int(10 * fs)    # 10 s measurement

# Virtual measurement of a plate mode with 5 % noise
shape = pycsldv.plate_mode(2, 3)
t, x, y = pycsldv.lissajous(fx, fy, n, fs)
velocity = pycsldv.simulate_response(shape, fz, x, y, fs, noise_std=0.05)

# ODS reconstruction by sideband demodulation
C = pycsldv.demodulate_ods(velocity, x, y, fs, fx, fy, fz, order=8)
C = pycsldv.align_phase(C)

# Compare with the exact shape
X, Y, Z = pycsldv.evaluate_ods(C, resolution=100)
print(pycsldv.mac(shape(X, Y), Z.real))

# Visualize
pycsldv.plot_ods(C)

Several modes, damping, several sensors, line scans

demodulate_ods above projects the velocity onto each sideband in turn. It takes the recorded mirror signals, so the mirror phases — the inertial lag of the galvos — are estimated from the signals themselves.

demodulate_ods_2d instead fits

v(t) = sum_k Re{ C_k(x(t), y(t)) exp(lambda_k t) }

as one global linear least-squares system. It rebuilds the trajectory from fx, fy and the mirror phases, which therefore have to be passed as phi_x and phi_y (reference_phase(x, fx, fs) measures them from recorded feedback). In exchange it takes complex poles lambda = sigma + i omega instead of real frequencies, and any number of modes and sensors at once:

# one undamped mode and one ringing down, fitted together,
# to different orders
poles = [2j * np.pi * 500.0, -0.5 + 2j * np.pi * 502.8]
C_first, C_second = pycsldv.demodulate_ods_2d(velocity, fs, fx, fy,
                                              poles=poles, order=[6, 8])

# the three heads of a 3D scanning vibrometer: (locations, n_samples) in,
# one coefficient matrix per location out
C = pycsldv.demodulate_ods_2d(np.vstack([v_x, v_y, v_z]), fs, fx, fy,
                              fn=500.0, order=6)

# a line scan: one Chebyshev variable; evaluate_ods and plot_ods
# recognize it from the shape of the coefficients
C = pycsldv.demodulate_ods_1d(velocity, fs, fn=500.0, fx=fx, order=5)
points, z = pycsldv.evaluate_ods(C, resolution=200)

The results follow the input: several modes come back as a list, several sensors as an array stacked along a leading axis, and evaluate_ods, plot_ods and rotate_ods take either.

A fit that the measurement cannot determine is reported rather than returned quietly. numpy’s least-squares solver answers a rank-deficient system with its minimum-norm solution, which looks like a set of coefficients and is not one, so both the rank and the conditioning are checked: a record that does not cover the scan, or one that barely does, produces a warning naming the scan period or the condition number.

Fitting the modes together matters when they are close enough for the sidebands of one to land on those of another. The spacing that hurts is not “a few Hz” but a multiple of a scan frequency: a second mode 2 fx away puts its carrier exactly on the first mode’s n = 2 sideband, where the projection cannot tell the two apart while the global fit still recovers both.

The design matrix is n_samples by 2 sum_k (Px + 1)(Py + 1), which for a long record is larger than everything else in the problem put together, so it is never held whole: it is built a block of rows at a time and only the normal equations are accumulated. Those are as wide as the unknowns and no wider, so the memory does not follow the length of the record. Forming them squares the condition number of the system, which is one more reason the fit reports how well the scan determines it.

Measured data

For measured data, pass the recorded velocity and mirror feedback signals to demodulate_ods directly — the mirror phases (inertial lag) are estimated from the feedback signals themselves. pyCSLDV reads no file formats of its own; the two steps a measurement does need before the reconstruction are mapping each mirror signal onto the normalized domain and telling the channels apart by their scan frequency:

x, x_centre, x_amplitude = pycsldv.normalize_scan(x_mm, fx, fs)
pycsldv.dominant_frequency(x_mm, fs)   # which mirror is this?

If the object was mounted askew and the scan was rotated to follow its edges, the reconstruction already comes out on the object’s own axes, because only the phase of each mirror at its scan frequency is used. The angle is needed only to step back to the mirror axes, and it can be measured from the feedback signals rather than trusted to the calibration record:

angle = pycsldv.scan_rotation(x, y, fx, fy, fs)
C_lab = pycsldv.rotate_ods(C, -angle)

An adapter for the export format of the original LabVIEW/MATLAB suite is provided as an example rather than as part of the package, in examples/csldv_suite_export.py.

Examples and notebooks

The bundled example can be run from the project base directory with:

$ python -m examples.simulate_and_reconstruct

For a guided walk-through of the complete workflow — trajectory design, sideband spectrum, ODS reconstruction and MAC validation — see the Showcase notebook. The same workflow on a real measurement, compared with the ODS the original suite reconstructed from the same samples, is in Showcase_measured.ipynb. That dataset is published with the original suite and is not part of this repository: it is 80 MB, and both the notebook and tests/test_measured_reference.py download it from Zenodo the first time they need it, into examples/data/. Set PYCSLDV_NO_DOWNLOAD=1 to forbid that — the tests then skip instead of fetching 52 MB, which is what a continuous-integration run wants — and PYCSLDV_DATA to keep the files somewhere else. The download is also skipped, rather than failed, when there is no network.

References

  • J. Bartlett, P. Tarazaga: Continuous Scanning Laser Doppler Vibrometry (CSLDV) Vibration Measurement & Simulation Suite, version 1.1, Zenodo, 2026, DOI: 10.5281/zenodo.22032252 (concept DOI 10.5281/zenodo.21301125 resolves to the latest version). Version 1.1 adds the measured dataset the tests and Showcase_measured.ipynb run on.

  • A. B. Stanbridge, D. J. Ewins: Modal testing using a scanning laser Doppler vibrometer, Mechanical Systems and Signal Processing 13(2), 1999, 255–270.

  • L. Mignanelli, P. Chiariotti, P. Castellini, M. Martarelli: Blind Identification of Operational Deflection Shapes from Continuous Scanning Laser Doppler Vibrometry Data, Sensors and Instrumentation 5, 2016, 105–111.

  • S. Rothberg et al.: An international review of laser Doppler vibrometry: Making light work of vibration measurement, Optics and Lasers in Engineering 99, 2017, 11–22.

  • D. Di Maio et al.: Continuous Scanning Laser Vibrometry: A raison d’être and applications to vibration measurements, Mechanical Systems and Signal Processing 156, 2021, 107573.

Release files for pyCSLDV 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyCSLDV 0.1.0
File Size Uploaded
pycsldv-0.1.0.tar.gz 21.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyCSLDV 0.1.0
File Interpreter ABI Platform
pycsldv-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 44.3 kB

Release files / pycsldv-0.1.0.tar.gz

Download URL pycsldv-0.1.0.tar.gz
Size 21.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0a698545071fc2dd1e27cd663d71ca4e3a9b0402269551520e9a5d2017d0721b
BLAKE2b-256 checksum
How to use checksums
5fdc3eaba1cf4f2db66b02bee86b74f84e590437c423c10dd7f763b1575e145f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / pycsldv-0.1.0-py3-none-any.whl

Download URL pycsldv-0.1.0-py3-none-any.whl
Size 22.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
429462e2d6a1a4bb5fea2c324458b7df4263dffc29d049e35997c8780343fdd7
BLAKE2b-256 checksum
How to use checksums
b76c11c6c6b29d172b3a43419f0af6a00e17c66fd25d72c05dda22cb7b27bb2a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page