Data Structure Module¶
Core data structures for storing and managing measurement data.
Main Container¶
DataSet ¶
Attributes¶
_LIST_ATTRS
class-attribute
instance-attribute
¶
_LIST_ATTRS = ('time_data_list', 'freq_data_list', 'cross_spec_data_list', 'tf_data_list', 'modal_data_list', 'sono_data_list', 'meta_data_list')
Methods:¶
__setstate__ ¶
Restore a pickled DataSet, forward-normalising older layouts.
Unpickling instantiates the CURRENT class but restores ONLY the
attributes that were actually saved, so a DataSet written by an
older pydvma is missing any *_list attribute that postdates it.
Historically the lists were added at different times
(cross_spec_data_list with multi-channel analysis;
sono_data_list with sonograms; modal_data_list with modal
fitting), so a legacy .npy pickle can lack one or more of them —
e.g. the 2019/4C6-era files lack modal_data_list. The rest of
the code (and container.save) assumes every list is present, so
loading such a file used to raise AttributeError: 'DataSet'
object has no attribute 'modal_data_list'.
We fill in any absent list with an empty instance of the right type
(and stamp a placeholder pydvma_version when the file predates
the version field) so files saved by pydvma <= 1.4.0 load forever —
the compatibility contract — on BOTH the Qt load path
(file.load_data → np.load) and the browser legacy-import
path (glue.legacy_to_dvma → np.load → container.save_bytes).
Called only when unpickling; freshly constructed DataSets go through
__init__ and never touch this. Newly saved objects already carry
every attribute, so this is a no-op for them.
calculate_fft_set ¶
Calls analysis.calculate_fft on each TimeData item in the TimeDataList and adds FreqDataList object to dataset
calculate_tf_set ¶
Calls analysis.calculate_tf on each TimeData item in the TimeDataList and adds TfDataList object to dataset
calculate_cross_spectrum_matrix_set ¶
calculate_cross_spectrum_matrix_set(ch_in=0, time_range=None, window='hann', N_frames=1, overlap=0.5)
Calls analysis.calculate_cross_spectrum_matrix on each TimeData item in the TimeDataList and adds CrossSpecDataList object to dataset
calculate_tf_averaged ¶
Calls analysis.calculate_tf_averaged on the whole TimeDataList (ensemble average) and adds a single-item TfDataList to dataset
calculate_cross_spectra_averaged ¶
Calls analysis.calculate_cross_spectra_averaged on the whole TimeDataList (ensemble average) and adds a single-item CrossSpecDataList to dataset
clean_impulse ¶
Calls analysis.clean_impulse on each TimeData item in the TimeDataList and returns a copy of the new dataset.
Note that calling this function does not change the data, and just returns a copy.
subset ¶
Return a new DataSet holding chosen measurement(s) and everything derived from them.
This is the Python counterpart of the web app's Save
"Choose sets…" picker (subsetDataset in
webui/src/lib/analysis/actions.ts) — the same inclusion
rule, mirrored here so a notebook workflow and the browser
produce the same subset from the same dataset:
- the chosen
TimeDataitem(s) themselves; - every
FreqData/CrossSpecData/TfData/SonoDataitem whoseid_linkresolves into a chosen item'sunique_id. A scalarid_link(analysis.calculate_fft,analysis.calculate_tf,analysis.calculate_cross_spectrum_matrix,analysis.calculate_sonogram/calculate_cwt) matches directly; a LISTid_link(analysis.calculate_tf_averaged,analysis.calculate_cross_spectra_averaged,analysis.calculate_bla— one entry per sourceTimeDatain the ensemble) matches on ANY member, not all — an ensemble result whose sources only partly overlap the pick still rides along; - a
ModalDatafit when ANY of its links lands in the chosen lineage — see_modal_item_in_subsetfor the exact link shapes checked (ownid_link, scalar or nested-list, plus a browser-authoredsource_targetsextra when present). This is deliberately an ANY rule, not ALL: a fit is worth carrying with any set it describes. Note the web app's own loader is stricter — it re-seeds a live, editable fit only when EVERYsource_targetslink resolves — so a subset spanning only part of a shared-pole fit still carries theModalDataas data (readable, replottable, reloadable) without silently re-seeding a fit session for a model that no longer has all its sources; carrying the modes as DATA is the contract here, not re-seeding a fit. MetaData, and any derived item whoseid_linkcannot be resolved into the pick (an orphan, or a link to aTimeDataoutside the pick), are excluded — that is the point of a subset.
Items are SHARED, not copied. The returned DataSet's
lists hold the SAME objects as self's, so mutating a
TimeData (or any derived item) reached through either
dataset is visible through both — consistent with the rest of
this class (add_to_dataset, replace_data_item never copy
either). Take your own copy.deepcopy first if independent
objects are needed. Lists keep their original relative order.
Unlike the web app's subsetDataset, there is no
"picking every set returns the live document" short-circuit:
this always builds a fresh DataSet (still item-SHARING, per
above), so passing every valid index is not the same as
"everything" — a genuinely unattributable item (an orphan
derived item, MetaData) is excluded even then, whereas the
web app's "everything" pick keeps such items because it
returns the untouched document unchanged.
Parameters:
-
sets(int or Iterable[int]) –Index or indices into
time_data_listto keep (0-based). Duplicate indices are ignored after the first.
Returns:
-
dataset(DataSet) –A new
DataSet, withpydvma_versioncopied fromself, containing the chosen measurements and their resolved derived/modal items, sharing objects withself.
Raises:
-
IndexError–setscontains an index outsiderange(len(self.time_data_list)).
save_data ¶
Saves the whole DataSet via file.save_data — writes the
.dvma container format by default (legacy pickle format if
filename explicitly ends in .npy). Shows a save dialog
if no filename is given.
Parameters:
-
filename(str, default:None) –Output filename, dialog shown if not provided.
-
sets(int or Iterable[int], default:None) –If given, saves
self.subset(sets)instead of the whole dataset — seesubsetfor the exact inclusion rule.None(the default) saves everything, unchanged.
export_to_matlab_jwlogger ¶
Time Domain Data¶
TimeData ¶
One block of acquired time-series data plus its acquisition metadata.
Held inside a DataSet.time_data_list. Produced by log_data,
by the test-data factories in testdata, and on import from
Matlab. The numeric content is in volts (see "Voltage-Based
I/O" in the user-guide acquisition page); apply
channel_cal_factors to convert to engineering units at display
or fit time. analysis.calculate_* functions copy units and
channel_cal_factors onto their derived FreqData / TfData /
CrossSpecData / SonoData outputs.
Attributes:
-
time_axis(ndarray) –1D sample times in seconds.
-
time_data(ndarray) –Shape
(n_samples, n_channels)voltage samples. -
settings(MySettings) –Snapshot of the acquisition configuration.
-
timestamp(datetime) –Capture start time.
-
timestring(str) –Filesystem-safe rendering of
timestamp. -
units(list[str] or None) –Engineering units per channel (e.g.
['N', 'm/s', 'g']). None if unset. -
channel_cal_factors(ndarray) –Per-channel multipliers from volts to engineering units. Defaults to all-ones.
-
id_link–Reference to a source TimeData (used when this object is derived rather than freshly acquired).
-
test_name(str or None) –Free-form label, displayed in plots.
-
unique_id(UUID) –Generated at construction; used by derived objects to link back to their source via
id_link.
TimeDataList ¶
Bases: list
Methods:¶
calculate_fft_set ¶
Calls analysis.calculate_fft on each item in the list and returns FreqDataList object
calculate_tf_set ¶
Calls analysis.calculate_tf on each item in the list and returns TfDataList object
calculate_cross_spectrum_matrix_set ¶
Calls analysis.calculate_tf on each item in the list and returns TfDataList object
calculate_tf_averaged ¶
Calls analysis.calculate_tf_averaged on whole list and returns TfData object
calculate_cross_spectra_averaged ¶
Calls analysis.calculate_cross_spectra_averaged on whole list and returns CrossSpecData object
calculate_sono_set ¶
Calls analysis.calculate_sonogram on each item in the list and returns SonoDataList object
Frequency Domain Data¶
FreqData ¶
One-sided complex frequency spectrum of a TimeData capture.
Produced by analysis.calculate_fft. The spectrum is the raw
np.fft.rfft of the (optionally windowed) time data — i.e. it is
not scaled to a PSD or amplitude spectrum; consumers that need
PSD should square the magnitude themselves. units and
channel_cal_factors are copied verbatim from the source TimeData.
Attributes:
-
freq_axis(ndarray) –Frequency bins in Hz (length
N//2+1). -
freq_data(ndarray) –Shape
(n_freq, n_channels)complex spectrum, one column per channel. -
settings(MySettings) –Snapshot of the analysis configuration (includes the window choice and the time range that was used).
-
units(list[str] or None) –Engineering units per channel.
-
channel_cal_factors(ndarray) –Per-channel multipliers from volts to engineering units; applied at display time.
-
id_link(UUID) –unique_idof the source TimeData. -
unique_id(UUID) –This item's own identity, minted at construction. It is what makes a pull → modify → push round trip through :class:
pydvma.session.SessionREPLACE this result in place instead of appending a second copy beside it. Optional in the container: a file written before derived items carried ids restores without the attribute. -
test_name(str or None) –Free-form label.
-
timestamp(datetime) –When this FreqData was constructed.
-
timestring(str) –Filesystem-safe rendering of
timestamp. -
source_signature(str) –OPTIONAL, set post-construction by
analysis.calculate_fft— a 16-hex-character hash of the SOURCE samples and rate (pydvma._signature), so a loaded file can tell an intact compute chain from one whose time data changed after the compute. Genuinely optional (needs ahasattrguard): items written before signatures existed make no claim about their chain. -
source_settings(dict) –OPTIONAL, set alongside
source_signature— the analysis knobs that call used, as JSON-safe scalars, so the result is self-describing. A settings change does NOT invalidate a stored result; only a source-sample change does.
Attributes¶
timestring
instance-attribute
¶
timestring = '_' + str(t.year) + '_' + str(t.month) + '_' + str(t.day) + '_at_' + str(t.hour) + '_' + str(t.minute) + '_' + str(t.second)
Methods:¶
__init__ ¶
__init__(freq_axis, freq_data, settings, units=None, channel_cal_factors=None, id_link=None, test_name=None)
FreqDataList ¶
Transfer Function Data¶
TfData ¶
Transfer function H(f) from one input channel to one or more outputs.
Produced by analysis.calculate_tf (single TimeData),
analysis.calculate_tf_averaged (ensemble TimeDataList) or
analysis.calculate_bla (a best-linear-approximation run). For the
first two the convention is Pxy[in, out] / Pxy[in, in] per output
channel and tf_coherence carries the corresponding coherence.
BLA sets are different: no cross-spectrum estimator is involved
at all — the FRF comes from inverting the excitation matrix at each
excited bin — so the Pxy convention does not describe them,
tf_coherence is None (the bla_sigma_* pair is the quality
measure instead), settings.ch_in may be None (commanded-drive
mode has no measured input channel), and freq_axis holds only the
excited bins rather than a full rfft grid. bla is non-None exactly
on those sets.
Calibration: channel_cal_factors[k] holds the ratio
cal[out_k] / cal[in] — i.e. multiplying tf_data[:, k] *
channel_cal_factors[k] at display time gives the TF in
engineering units. Units are constructed as
"<out_unit>/<in_unit>" per output channel.
Attributes:
-
freq_axis(ndarray) –One-sided frequency bins in Hz.
-
tf_data(ndarray) –Shape
(n_freq, n_outputs), complex. One column per non-input channel. -
tf_coherence(ndarray) –Same shape, real, in [0, 1].
-
settings(MySettings) –Snapshot including the chosen
ch_inand the derivedch_out_set(the channel indices intf_data's second axis). -
units(list[str] or None) –Per-output-channel unit strings (e.g.
['m/s/N', 'g/N']). -
channel_cal_factors(ndarray) –Per-output cal ratios (cal[out] / cal[in]). A manual override here overwrites the inherited ratio.
-
id_link–unique_idof the source TimeData (or list when averaged). -
unique_id(UUID) –This item's own identity, minted at construction — see
FreqData.unique_idfor why a derived item needs one. -
test_name(str or None) –Free-form label.
-
timestamp(datetime) –When constructed.
-
timestring(str) –Filesystem-safe rendering of
timestamp. -
flag_modal_TF(bool) –True after a modal fit has consumed this TfData (avoids double-fitting); used by
modal.py. -
bla_sigma_nl(ndarray or None) –Nonlinear-distortion standard deviation, shape
(n_freq, n_outputs), real, in the same linear units asabs(tf_data)— a std, not a variance, so it goes straight onto a dB axis with no further square root. PER-REALISATION: it is the distortion level of one realisation, not the error bar ontf_data, which issqrt(M)smaller. Set byanalysis.calculate_bla; None on an ordinary transfer function. -
bla_sigma_n(ndarray or None) –Measurement-noise standard deviation, same shape, units and per-realisation reading as
bla_sigma_nl. Set byanalysis.calculate_bla; None on an ordinary transfer function. -
bla(dict or None) –The BLA run spec that produced this estimate (multisine design, x-mode, channel roles, capture fs, excited bins and which excitation
qthis TfData belongs to). JSON-clean scalars only, so it round-trips through the .dvma manifest. None on an ordinary transfer function. -
source_signature(str) –OPTIONAL, set post-construction by
analysis.calculate_tf/analysis.calculate_tf_averaged— a 16-hex-character hash of the SOURCE samples and rate (pydvma._signature; for an ensemble, every source in list order), so a loaded file can tell an intact compute chain from one whose time data changed after the compute. Genuinely optional (needs ahasattrguard): items written before signatures existed, and BLA estimates, carry no signature and make no claim about their chain. -
source_settings(dict) –OPTIONAL, set alongside
source_signature— the analysis knobs that call used, as JSON-safe scalars, so the result is self-describing. A settings change does NOT invalidate a stored result; only a source-sample change does.
All three BLA attributes are set in __init__ and are declared
container fields, so they survive a .dvma round trip as None or as
their value — no hasattr guard needed, unlike the genuinely
optional iw_power_counter.
Attributes¶
timestring
instance-attribute
¶
timestring = '_' + str(t.year) + '_' + str(t.month) + '_' + str(t.day) + '_at_' + str(t.hour) + '_' + str(t.minute) + '_' + str(t.second)
Methods:¶
__init__ ¶
__init__(freq_axis, tf_data, tf_coherence, settings, units=None, channel_cal_factors=None, id_link=None, test_name=None)
Sonogram Data¶
SonoData ¶
Short-time-FFT spectrogram (sonogram) of a multi-channel TimeData.
Produced by analysis.calculate_sonogram. Each frame is a windowed
FFT of a nperseg-sample segment of the source data; segments are
overlapped by noverlap and the resulting matrix lets you see how
spectral content evolves over time. analysis.calculate_cwt produces
the same object from a Morlet wavelet transform instead. Used by
analysis.calculate_damping_from_sono to extract per-mode damping
from free-decay measurements.
Also produced by the WEB APP, when a Save is told to include the
sonogram. Such an item differs in one way a reader must know about:
its third axis holds only the channels the user chose to save, in
the order they were saved, NOT every channel of the source. So
sono_data[:, :, k], units[k] and channel_cal_factors[k]
are all indexed by PLANE, and the source channel each plane came
from is recorded in source_settings['channels'][k]. A
single-channel save is the common case (it is the default the prompt
offers), and then sono_data.shape[2] == 1 however many channels
the measurement has.
Attributes:
-
time_axis(ndarray) –Frame midpoints in seconds.
-
freq_axis(ndarray) –One-sided frequency bins in Hz.
-
sono_data(ndarray) –Shape
(n_freq, n_frames, n_channels), complex. Magnitude-squared gives a per-bin power spectrogram. For an app-written item the last axis is the SAVED channel subset — see above. -
settings(MySettings) –Snapshot including
pretrig_samples(used bycalculate_damping_from_sonoto pick the free-decay start time). -
units(list[str] or None) –Engineering units, one per PLANE of
sono_data. -
channel_cal_factors(ndarray) –Multipliers from volts to engineering units, one per PLANE of
sono_data. -
id_link–unique_idof the source TimeData. -
unique_id(UUID) –This item's own identity, minted at construction — see
FreqData.unique_idfor why a derived item needs one. -
test_name(str or None) –Free-form label.
-
timestamp(datetime) –When constructed.
-
timestring(str) –Filesystem-safe rendering of
timestamp.
Attributes¶
timestring
instance-attribute
¶
timestring = '_' + str(t.year) + '_' + str(t.month) + '_' + str(t.day) + '_at_' + str(t.hour) + '_' + str(t.minute) + '_' + str(t.second)
Methods:¶
__init__ ¶
__init__(time_axis, freq_axis, sono_data, settings, units=None, channel_cal_factors=None, id_link=None, test_name=None)
SonoDataList ¶
Bases: list
Cross-Spectrum Data¶
CrossSpecData ¶
Full cross-spectrum matrix Pxy[i,j,f] and coherence matrix Cxy[i,j,f].
Produced by analysis.calculate_cross_spectrum_matrix (single
TimeData) or analysis.calculate_cross_spectra_averaged (ensemble
TimeDataList). The diagonal Pxy[i, i, :] is the per-channel
auto-spectrum (= scipy.signal.welch with scaling='spectrum');
off-diagonal Pxy[i, j, :] matches scipy.signal.csd with the same
settings. Pxy is Hermitian — Pxy[j, i, :] = conj(Pxy[i, j, :]).
Attributes:
-
freq_axis(ndarray) –One-sided frequency bins in Hz.
-
Pxy(ndarray) –Shape
(n_channels, n_channels, n_freq), complex. Cross-spectrum matrix. -
Cxy(ndarray) –Same shape, real, in [0, 1]. Coherence matrix.
-
settings(MySettings) –Includes
window,time_range,N_frames,overlapactually used. -
units(list[str] or None) –Engineering units per channel.
-
channel_cal_factors(ndarray) –Per-channel multipliers from volts to engineering units.
-
id_link–unique_idof the source TimeData (or list of ids when averaged across a TimeDataList). -
unique_id(UUID) –This item's own identity, minted at construction — see
FreqData.unique_idfor why a derived item needs one. -
test_name(str or None) –Free-form label.
-
timestamp(datetime) –When constructed.
-
timestring(str) –Filesystem-safe rendering of
timestamp.
Attributes¶
timestring
instance-attribute
¶
timestring = '_' + str(t.year) + '_' + str(t.month) + '_' + str(t.day) + '_at_' + str(t.hour) + '_' + str(t.minute) + '_' + str(t.second)
Methods:¶
__init__ ¶
__init__(freq_axis, Pxy, Cxy, settings, units=None, channel_cal_factors=None, id_link=None, test_name=None)
CrossSpecDataList ¶
Bases: list
Modal Data¶
ModalData ¶
A set of fitted modes — each row of M is one mode's
(fn, zn, an[chan...], pn[chan...], rk[chan...], rm[chan...])
parameter vector as produced by
modal.modal_fit_all_channels.
Use add_mode to append further modes (e.g. across separate
frequency-band fits); rows are kept sorted by fn. After any
add/delete, the summary arrays fn, zn, an, pn are
refreshed and indexable per mode.
Attributes:
-
M(ndarray) –Shape
(n_modes, 2 + 4*n_channels). Each row packs[fn, zn, an_0..an_C, pn_0..pn_C, rk_0..rk_C, rm_0..rm_C]. -
fn(ndarray) –Per-mode natural frequencies in Hz.
-
zn(ndarray) –Per-mode damping ratios.
-
an(ndarray) –Shape
(n_modes, n_channels)modal-constant amplitudes. -
pn(ndarray) –Same shape; modal-constant phases in radians.
-
channels(int) –Number of channels (=
n_channelsabove). -
settings(MySettings) –Snapshot including the source TF's settings.
-
units–Engineering units (passed through from source).
-
id_link–unique_id(s) of the TFs that produced these modes. -
unique_id(UUID) –This item's own identity, minted at construction — see
FreqData.unique_id. Modal fits are pushed back from notebooks like any other item, and without an id every push would append another copy of the fit. -
test_name(str or None) –Free-form label.
Attributes¶
timestring
instance-attribute
¶
timestring = '_' + str(t.year) + '_' + str(t.month) + '_' + str(t.day) + '_at_' + str(t.hour) + '_' + str(t.minute) + '_' + str(t.second)
Methods:¶
add_mode ¶
Appends one mode (a packed parameter row as per 'x' in modal.py: [fn, zn, an x N, pn x N, rk x N, rm x N]) to the modal matrix, keeping rows sorted by natural frequency and refreshing the unpacked summary properties (fn, zn, an, pn).
delete_mode ¶
Deletes one or more modes (rows) from the modal matrix by index and refreshes the unpacked summary properties (fn, zn, an, pn).
Deleting the LAST remaining mode is valid: the matrix becomes an
empty (0, 2+4*channels) and the summaries become zero-length
(fn/zn) / (0, channels) (an/pn). This no longer raises the
IndexError that modal.unpack_matrix used to throw on an emptied
matrix (the round-4 "Fit -> Reject" crash, and the same latent crash
on Qt's Reject). channels is preserved — it is encoded in the
column count, not the number of mode rows.
ModalDataList ¶
Bases: list
Metadata¶
MetaData ¶
Dataset-level units and calibration, kept for legacy datasets.
Attributes:
-
units–Engineering units.
-
channel_cal_factors–Per-channel multipliers (legacy; always None here — calibration lives on each data item instead).
-
tf_cal_factors–Per-TF multipliers (legacy; always None here).
-
timestamp(datetime) –When this MetaData was built.
-
timestring(str) –Filesystem-safe rendering of
timestamp. -
unique_id(UUID) –This item's own identity, minted at construction — see
FreqData.unique_id.
MetaDataList ¶
Bases: list