{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "# pydvma logger template\n", "\n", "*A template for using pydvma, the python dynamics and vibration measurement and analysis package*\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Start by importing the necessary modules (pydvma is a python package written for data acquisition at CUED)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# If the import doesn't work below, then uncomment and run the line below first.\n", "# The logger uses pyqt5: sometimes you need to set this backend explicity.\n", "# %matplotlib qt" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pydvma as ma\n", "import matplotlib\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "%matplotlib widget" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2025-09-15 12:44:52.047 python[26506:1388942] The class 'NSSavePanel' overrides the method identifier. This method is implemented by class 'NSWindow'\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Data saved as /Users/tore/Library/CloudStorage/OneDrive-UniversityofCambridge/Work Teaching - onedrive/4C6/2025/Lectures Notes/media/nonlinear_2.npy\n" ] }, { "data": { "text/plain": [ "'/Users/tore/Library/CloudStorage/OneDrive-UniversityofCambridge/Work Teaching - onedrive/4C6/2025/Lectures Notes/media/nonlinear_2.npy'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d = ma.create_test_impulse_data_nonlinear_v2()\n", "d.save_data()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Oscilloscope\n", "\n", "
\n", "\n", "Choose your acquisition settings:\n", "\n", "* channels=2 (number of channels to record)\n", "* fs=44100 (sampling rate in Hz)\n", "* chunk_size=200 (how many samples to collect at a time, effectively controls refresh rate of oscilloscope)\n", "* stored_time=2 (time in seconds to record data for)\n", "* viewed_time=2 (time in seconds to display on oscilloscope)\n", "* device_index = 1 (Windows default input)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "settings = ma.MySettings(channels=1,\n", " fs=44100,\n", " chunk_size=200,\n", " stored_time=2,\n", " viewed_time=2,\n", " device_driver='soundcard')\n", "# when using national instruments acquisition cards, change device_driver='nidaq'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now open a PC oscilloscope using your settings. This shows three plots:\n", "\n", "* the top one is like a normal oscilloscope showing the signal (toggle on/off with 'T');\n", "* the middle one shows the frequency spectrum of the signal (toggle on/off with 'F');\n", "* the bottom one shows the signal amplitudes (toggle on/off with 'L');\n", "* you can pause the data shown by pressing 'P' (press again to continue streaming data);\n", "* you can toggle whether the window is always on top by pressing 'A'\n", "\n", "Press the **space bar** to record data from the past 'stored_time' seconds.\n", "\n", "* The first time you press it you will be prompted for where to save your data.\n", "* Use the save dialog to navigate to where you want to save your data\n", "* Subsequent times you press it will auto-save to the same folder with a number added to the filename.\n", "* Press 's' if you want to save data to a new filename or location. Pressing space after that will auto-save with the new name.\n", "\n", "**Note that pressing 'space' captures the past N seconds of data: so you need to tap the beam, wait a second or so, then press space!**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "osc = ma.Oscilloscope(settings)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Logger GUI\n", "\n", "
\n", "\n", "You can also log data with the logger gui:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "||PaMacCore (AUHAL)|| AUHAL component not found.__________________________________________________________\n", "\n", "Devices available using device_driver=soundcard, by index:\n", "__________________________________________________________\n", "\n", "0: TB phone Microphone\n", "1: BlackHole 2ch\n", "2: MacBook Pro Microphone\n", "3: MacBook Pro Speakers\n", "4: Tore\n", "5: Microsoft Teams Audio\n", "\n", "Default device is: [1] BlackHole 2ch\n", "\n", "Default device is: [3] MacBook Pro Speakers\n", "\n", "\n", "______________________________________________________\n", "\n", "Devices available using device_driver=nidaq, by index:\n", "______________________________________________________\n", "\n", "no NI devices found\n", "\n", "||PaMacCore (AUHAL)|| AUHAL component not found.__________________________________________________________\n", "\n", "Devices available using device_driver=soundcard, by index:\n", "__________________________________________________________\n", "\n", "0: TB phone Microphone\n", "1: BlackHole 2ch\n", "2: MacBook Pro Microphone\n", "3: MacBook Pro Speakers\n", "4: Tore\n", "5: Microsoft Teams Audio\n", "\n", "Default device is: [1] BlackHole 2ch\n", "\n", "Default device is: [3] MacBook Pro Speakers\n", "\n", "\n", "______________________________________________________\n", "\n", "Devices available using device_driver=nidaq, by index:\n", "______________________________________________________\n", "\n", "no NI devices found\n", "\n", "__________________________________________________________\n", "\n", "Devices available using device_driver=soundcard, by index:\n", "__________________________________________________________\n", "\n", "0: TB phone Microphone\n", "1: BlackHole 2ch\n", "2: MacBook Pro Microphone\n", "3: MacBook Pro Speakers\n", "4: Tore\n", "5: Microsoft Teams Audio\n", "\n", "Default device is: [1] BlackHole 2ch\n", "\n", "Default device is: [3] MacBook Pro Speakers\n", "\n", "\n", "______________________________________________________\n", "\n", "Devices available using device_driver=nidaq, by index:\n", "______________________________________________________\n", "\n", "no NI devices found\n", "\n", "||PaMacCore (AUHAL)|| AUHAL component not found.||PaMacCore (AUHAL)|| AUHAL component not found." ] }, { "ename": "PortAudioError", "evalue": "Error opening InputStream: Unanticipated host error [PaErrorCode -9999]: '' [ error 0]", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mPortAudioError\u001b[39m Traceback (most recent call last)", "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/pydvma/pydvma/gui.py:1114\u001b[39m, in \u001b[36mLogger.button_clicked_osc\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1112\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mbutton_clicked_osc\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m 1113\u001b[39m \u001b[38;5;66;03m# launch oscilloscope\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1114\u001b[39m \u001b[38;5;28mself\u001b[39m.osc = Oscilloscope(\u001b[38;5;28mself\u001b[39m.settings, flag_standalone=\u001b[38;5;28;01mFalse\u001b[39;00m)\n", "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/pydvma/pydvma/gui.py:2610\u001b[39m, in \u001b[36mOscilloscope.__init__\u001b[39m\u001b[34m(self, settings, flag_standalone)\u001b[39m\n\u001b[32m 2603\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m'''Creates an Oscilloscope\u001b[39;00m\n\u001b[32m 2604\u001b[39m \u001b[33;03mArgs:\u001b[39;00m\n\u001b[32m 2605\u001b[39m \u001b[33;03m settings: An object of the class MySettings\u001b[39;00m\n\u001b[32m 2606\u001b[39m \u001b[33;03m'''\u001b[39;00m\n\u001b[32m 2608\u001b[39m \u001b[38;5;28mself\u001b[39m.settings = settings\n\u001b[32m-> \u001b[39m\u001b[32m2610\u001b[39m streams.start_stream(settings)\n\u001b[32m 2611\u001b[39m \u001b[38;5;28mself\u001b[39m.rec = streams.REC\n\u001b[32m 2614\u001b[39m \u001b[38;5;28mself\u001b[39m.timer = QtCore.QTimer()\n", "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/pydvma/pydvma/streams.py:42\u001b[39m, in \u001b[36mstart_stream\u001b[39m\u001b[34m(settings)\u001b[39m\n\u001b[32m 40\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m settings.device_driver == \u001b[33m'\u001b[39m\u001b[33msoundcard\u001b[39m\u001b[33m'\u001b[39m:\n\u001b[32m 41\u001b[39m REC_SC = Recorder(settings)\n\u001b[32m---> \u001b[39m\u001b[32m42\u001b[39m REC_SC.init_stream(settings)\n\u001b[32m 43\u001b[39m REC = REC_SC\n\u001b[32m 44\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m settings.device_driver == \u001b[33m'\u001b[39m\u001b[33mnidaq\u001b[39m\u001b[33m'\u001b[39m:\n", "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/pydvma/pydvma/streams.py:230\u001b[39m, in \u001b[36mRecorder.init_stream\u001b[39m\u001b[34m(self, settings, _input_, _output_)\u001b[39m\n\u001b[32m 227\u001b[39m settings.device_full_info = sd.query_devices()[settings.device_index]\n\u001b[32m 229\u001b[39m dtype = \u001b[33m'\u001b[39m\u001b[33mfloat32\u001b[39m\u001b[33m'\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m230\u001b[39m \u001b[38;5;28mself\u001b[39m.audio_stream = sd.InputStream(samplerate=settings.fs, \n\u001b[32m 231\u001b[39m blocksize=settings.chunk_size, \n\u001b[32m 232\u001b[39m device=settings.device_index, \n\u001b[32m 233\u001b[39m channels=settings.channels, \n\u001b[32m 234\u001b[39m dtype=dtype, \n\u001b[32m 235\u001b[39m latency=\u001b[33m'\u001b[39m\u001b[33mlow\u001b[39m\u001b[33m'\u001b[39m, \n\u001b[32m 236\u001b[39m extra_settings=\u001b[38;5;28;01mNone\u001b[39;00m, \n\u001b[32m 237\u001b[39m callback=\u001b[38;5;28mself\u001b[39m.callback, \n\u001b[32m 238\u001b[39m finished_callback=\u001b[38;5;28;01mNone\u001b[39;00m, \n\u001b[32m 239\u001b[39m clip_off=\u001b[38;5;28;01mNone\u001b[39;00m, \n\u001b[32m 240\u001b[39m dither_off=\u001b[38;5;28;01mNone\u001b[39;00m, \n\u001b[32m 241\u001b[39m never_drop_input=\u001b[38;5;28;01mNone\u001b[39;00m, \n\u001b[32m 242\u001b[39m prime_output_buffers_using_stream_callback=\u001b[38;5;28;01mNone\u001b[39;00m) \n\u001b[32m 243\u001b[39m \u001b[38;5;28mself\u001b[39m.audio_stream.start()\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/envs/py12/lib/python3.12/site-packages/sounddevice.py:1440\u001b[39m, in \u001b[36mInputStream.__init__\u001b[39m\u001b[34m(self, samplerate, blocksize, device, channels, dtype, latency, extra_settings, callback, finished_callback, clip_off, dither_off, never_drop_input, prime_output_buffers_using_stream_callback)\u001b[39m\n\u001b[32m 1408\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, samplerate=\u001b[38;5;28;01mNone\u001b[39;00m, blocksize=\u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 1409\u001b[39m device=\u001b[38;5;28;01mNone\u001b[39;00m, channels=\u001b[38;5;28;01mNone\u001b[39;00m, dtype=\u001b[38;5;28;01mNone\u001b[39;00m, latency=\u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 1410\u001b[39m extra_settings=\u001b[38;5;28;01mNone\u001b[39;00m, callback=\u001b[38;5;28;01mNone\u001b[39;00m, finished_callback=\u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 1411\u001b[39m clip_off=\u001b[38;5;28;01mNone\u001b[39;00m, dither_off=\u001b[38;5;28;01mNone\u001b[39;00m, never_drop_input=\u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 1412\u001b[39m prime_output_buffers_using_stream_callback=\u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[32m 1413\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"PortAudio input stream (using NumPy).\u001b[39;00m\n\u001b[32m 1414\u001b[39m \n\u001b[32m 1415\u001b[39m \u001b[33;03m This has the same methods and attributes as `Stream`, except\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 1438\u001b[39m \n\u001b[32m 1439\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1440\u001b[39m _StreamBase.\u001b[34m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, kind=\u001b[33m'\u001b[39m\u001b[33minput\u001b[39m\u001b[33m'\u001b[39m, wrap_callback=\u001b[33m'\u001b[39m\u001b[33marray\u001b[39m\u001b[33m'\u001b[39m,\n\u001b[32m 1441\u001b[39m **_remove_self(\u001b[38;5;28mlocals\u001b[39m()))\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/envs/py12/lib/python3.12/site-packages/sounddevice.py:909\u001b[39m, in \u001b[36m_StreamBase.__init__\u001b[39m\u001b[34m(self, kind, samplerate, blocksize, device, channels, dtype, latency, extra_settings, callback, finished_callback, clip_off, dither_off, never_drop_input, prime_output_buffers_using_stream_callback, userdata, wrap_callback)\u001b[39m\n\u001b[32m 907\u001b[39m userdata = _ffi.NULL\n\u001b[32m 908\u001b[39m \u001b[38;5;28mself\u001b[39m._ptr = _ffi.new(\u001b[33m'\u001b[39m\u001b[33mPaStream**\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m909\u001b[39m _check(_lib.Pa_OpenStream(\u001b[38;5;28mself\u001b[39m._ptr, iparameters, oparameters,\n\u001b[32m 910\u001b[39m samplerate, blocksize, stream_flags,\n\u001b[32m 911\u001b[39m callback_ptr, userdata),\n\u001b[32m 912\u001b[39m \u001b[33mf\u001b[39m\u001b[33m'\u001b[39m\u001b[33mError opening \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m.\u001b[34m__class__\u001b[39m.\u001b[34m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m)\n\u001b[32m 914\u001b[39m \u001b[38;5;66;03m# dereference PaStream** --> PaStream*\u001b[39;00m\n\u001b[32m 915\u001b[39m \u001b[38;5;28mself\u001b[39m._ptr = \u001b[38;5;28mself\u001b[39m._ptr[\u001b[32m0\u001b[39m]\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/envs/py12/lib/python3.12/site-packages/sounddevice.py:2794\u001b[39m, in \u001b[36m_check\u001b[39m\u001b[34m(err, msg)\u001b[39m\n\u001b[32m 2792\u001b[39m hosterror_text = _ffi.string(info.errorText).decode()\n\u001b[32m 2793\u001b[39m hosterror_info = host_api, info.errorCode, hosterror_text\n\u001b[32m-> \u001b[39m\u001b[32m2794\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m PortAudioError(errormsg, err, hosterror_info)\n\u001b[32m 2796\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m PortAudioError(errormsg, err)\n", "\u001b[31mPortAudioError\u001b[39m: Error opening InputStream: Unanticipated host error [PaErrorCode -9999]: '' [ error 0]" ] }, { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n", "\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n", "\u001b[1;31mClick here for more info. \n", "\u001b[1;31mView Jupyter log for further details." ] } ], "source": [ "# COMMON SETTINGS EXAMPLE 1 (normal acquisition)\n", "settings = ma.MySettings(channels=1,\n", " fs=44100,\n", " stored_time=2,\n", " device_driver = 'soundcard')\n", "logger1 = ma.Logger(settings,\n", " test_name = 'ENTER_YOUR_TEST_NAME',\n", " default_window='hann')\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# COMMON SETTINGS EXAMPLE 2 (acquisition with pre-trigger)\n", "settings = ma.MySettings(channels=2,\n", " fs=3000,\n", " stored_time=2,\n", " pretrig_samples=100,\n", " device_driver = 'soundcard',\n", " device_index=1)\n", "logger2 = ma.Logger(settings,\n", " test_name = 'ENTER_YOUR_TEST_NAME',\n", " default_window=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " \n", " Understanding the logger:
\n", "\n", "When you press 'Save Dataset', the logger saves a <DataSet> object which has the following structure:\n", "\n", "
\n",
    "    <DataSet> class:\n",
    "          time_data_list: [<TimeData>, <TimeData>, <TimeData>]\n",
    "          freq_data_list: [<FreqData>, <FreqData>, <FreqData>]\n",
    "    cross_spec_data_list: []\n",
    "            tf_data_list: [<TfData>, <TfData>, <TfData>]\n",
    "         modal_data_list: [<ModalData>]\n",
    "          sono_data_list: []\n",
    "          meta_data_list: []\n",
    "          \n",
    "
\n", "\n", " \n", "Each type of data is arranged in 'sets', e.g. each measurement will add another <TimeData> set to time_data_list. This example has three sets of time, frequency and transfer function data types, and a <ModalData> set. Similarly each time you load data it will add the loaded sets to the appropriate data list.

\n", "\n", "
  • When you press 'Calc FFT' then a <FreqData> item is calculated for each <TimeData> set.
  • \n", "
  • When you press 'Calc TF' then a <TfData> item is calculated for each <TimeData> set.
  • \n", "
  • When you press 'Calc TF average' then a single <TfData> item is calculated averaging across all <TimeData> items, with an assumption that the time data sets all have the same settings.
  • \n", "
    " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
    \n", "\n", "## Command line tools\n", "\n", "
    \n", "\n", "Another way to log data directly from the command line:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# SETTINGS\n", "settings = ma.MySettings(channels=1,\n", " fs=44100,\n", " stored_time=2,\n", " device_driver = 'soundcard')\n", "d = ma.log_data(settings)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.plot_time_data()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Calculate the FFT or transfer function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.calculate_fft_set(window='hanning')\n", "d.calculate_tf_set(window='hanning',N_frames=3)\n", "d.plot_freq_data()\n", "d.plot_tf_data()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Save / load the dataset:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ma.save_data(d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = ma.load_data()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "py12", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 2 }