From 1dc72d00bba1a7e8cfbd0a50b76b8186405a91a1 Mon Sep 17 00:00:00 2001 From: wendtalexander Date: Thu, 26 Sep 2024 11:23:08 +0200 Subject: [PATCH 1/5] adding scipy --- poetry.lock | 52 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 01fff01..6ae791c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -704,6 +704,56 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "scipy" +version = "1.14.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + [[package]] name = "shellingham" version = "1.5.4" @@ -779,4 +829,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "6b680c385942c0a2c0eef934f3fb37fdc3d2e1dc058a7f2d891d4f2f0607d9c6" +content-hash = "477748fbc18bde095d13dea548108541c1b584242099398155787361b1dc31fe" diff --git a/pyproject.toml b/pyproject.toml index 7d320be..7ce6881 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ matplotlib = "^3.9.2" numpy = "^2.1.1" pyqt6 = "^6.7.1" tomli = "^2.0.1" +scipy = "^1.14.1" [build-system] From d3800ddfa2661122a09989ead47876745cba817b Mon Sep 17 00:00:00 2001 From: wendtalexander Date: Thu, 26 Sep 2024 11:24:03 +0200 Subject: [PATCH 2/5] handle initial connection, diggital trigger --- pyrelacs/repros/mccdac.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pyrelacs/repros/mccdac.py b/pyrelacs/repros/mccdac.py index 0696c60..fb74764 100644 --- a/pyrelacs/repros/mccdac.py +++ b/pyrelacs/repros/mccdac.py @@ -19,7 +19,11 @@ class MccDac: log.error("Did not found daq devices, please connect one") exit(1) self.daq_device = uldaq.DaqDevice(devices[0]) - self.daq_device.connect() + try: + self.daq_device.connect() + except uldaq.ul_exception.ULException: + self.disconnect_dac() + self.connect_dac() self.ai_device = self.daq_device.get_ai_device() self.ao_device = self.daq_device.get_ao_device() self.dio_device = self.daq_device.get_dio_device() @@ -85,8 +89,8 @@ class MccDac: buffer = c_double * len(data) data_analog_output = buffer(*data) - log.debug(f"Created C_double data {data_analog_output}") + try: err = self.ao_device.a_out_scan( channels[0], @@ -123,12 +127,13 @@ class MccDac: self.disconnect_dac() def diggital_trigger(self) -> None: - if not self.read_bit(channel=0): - self.write_bit(channel=0, bit=1) - else: + data = self.read_bit(channel=0) + if data: self.write_bit(channel=0, bit=0) time.time_ns() self.write_bit(channel=0, bit=1) + else: + self.write_bit(channel=0, bit=1) def write_bit(self, channel: int = 0, bit: int = 1) -> None: self.dio_device.d_config_bit( @@ -266,7 +271,7 @@ class MccDac: log.info("Muting channel one") binary_db2 = "00000000" - channels_db = binary_db1 + binary_db2 + channels_db = binary_db2 + binary_db1 self.write_bit(channel=4, bit=0) for b in channels_db: self.write_bit(channel=5, bit=int(b)) From 66ea22fb4a25b744d6937df887f174e62b520495 Mon Sep 17 00:00:00 2001 From: wendtalexander Date: Thu, 26 Sep 2024 11:25:13 +0200 Subject: [PATCH 3/5] checking amplitude, checking beat --- pyrelacs/repros/calbi.py | 188 ++++++++++++++++++++++++++++++++++----- 1 file changed, 164 insertions(+), 24 deletions(-) diff --git a/pyrelacs/repros/calbi.py b/pyrelacs/repros/calbi.py index 937eea1..9f837cd 100644 --- a/pyrelacs/repros/calbi.py +++ b/pyrelacs/repros/calbi.py @@ -1,12 +1,14 @@ -import ctypes +import signal +import sys import faulthandler - import time import uldaq from IPython import embed import numpy as np import matplotlib.pyplot as plt +from scipy.signal import peak_widths, welch, csd +from scipy.signal import find_peaks from pyrelacs.repros.mccdac import MccDac from pyrelacs.util.logging import config_logging @@ -19,13 +21,67 @@ class Calibration(MccDac): def __init__(self) -> None: super().__init__() + def segfault_handler(self, signum, frame): + print(f"Segmentation fault caught! Signal number: {signum}") + self.disconnect_dac() + sys.exit(1) # Gracefully exit the program + def check_amplitude(self): + db_values = [0.0, -5.0, -10.0, -20.0, -50.0] + colors = ["red", "green", "blue", "black", "yellow"] self.set_attenuation_level(db_channel1=0.0, db_channel2=0.0) # write to ananlog 1 t = np.arange(0, DURATION, 1 / SAMPLERATE) data = AMPLITUDE * np.sin(2 * np.pi * SINFREQ * t) - data_channels = np.zeros(2 * len(data)) - # c = [(i,for i,j in zip(data, data)] + fig, ax = plt.subplots() + + for i, db_value in enumerate(db_values): + self.set_attenuation_level(db_channel1=db_value, db_channel2=db_value) + log.debug(f"{db_value}") + + stim = self.write_analog( + data, + [0, 0], + SAMPLERATE, + ScanOption=uldaq.ScanOption.EXTTRIGGER, + ) + + data_channel_one = self.read_analog( + [0, 0], DURATION, SAMPLERATE, ScanOption=uldaq.ScanOption.EXTTRIGGER + ) + time.sleep(1) + + log.debug("Starting the Scan") + self.diggital_trigger() + + try: + self.ao_device.scan_wait(uldaq.WaitType.WAIT_UNTIL_DONE, 15) + log.debug("Scan finished") + self.write_bit(channel=0, bit=0) + time.sleep(1) + self.set_analog_to_zero() + except uldaq.ul_exception.ULException: + log.debug("Operation timed out") + # reset the diggital trigger + self.write_bit(channel=0, bit=0) + time.sleep(1) + self.set_analog_to_zero() + self.disconnect_dac() + + if i == 0: + ax.plot(t, stim, label=f"Input_{db_value}", color=colors[i]) + ax.plot(t, data_channel_one, label=f"Reaout {db_value}", color=colors[i]) + + ax.legend() + plt.show() + + self.disconnect_dac() + + def check_beat(self): + self.set_attenuation_level(db_channel1=-10.0, db_channel2=0.0) + t = np.arange(0, DURATION, 1 / SAMPLERATE) + data = AMPLITUDE * np.sin(2 * np.pi * SINFREQ * t) + # data = np.concatenate((data, data)) stim = self.write_analog( data, @@ -33,35 +89,119 @@ class Calibration(MccDac): SAMPLERATE, ScanOption=uldaq.ScanOption.EXTTRIGGER, ) - data_channel_one = self.read_analog( - [0, 0], DURATION, SAMPLERATE, ScanOption=uldaq.ScanOption.EXTTRIGGER + readout = self.read_analog( + [0, 1], + DURATION, + SAMPLERATE, + ScanOption=uldaq.ScanOption.EXTTRIGGER, ) - time.sleep(1) - self.diggital_trigger() + signal.signal(signal.SIGSEGV, self.segfault_handler) + log.info(self.ao_device) + ai_status = uldaq.ScanStatus.RUNNING + ao_status = uldaq.ScanStatus.RUNNING - try: - self.ai_device.scan_wait(uldaq.WaitType.WAIT_UNTIL_DONE, 15) - self.write_bit(channel=0, bit=0) - time.sleep(1) - self.set_analog_to_zero() - except uldaq.ul_exception.ULException: - log.debug("Operation timed out") - # reset the diggital trigger - self.write_bit(channel=0, bit=0) - time.sleep(1) - self.set_analog_to_zero() - self.disconnect_dac() + log.debug( + f"Status Analog_output {ao_status}\n, Status Analog_input {ai_status}" + ) + while (ai_status != uldaq.ScanStatus.IDLE) and ( + ao_status != uldaq.ScanStatus.IDLE + ): + log.debug("Scanning") + time.sleep(0.5) + ai_status = self.ai_device.get_scan_status()[0] + ao_status = self.ao_device.get_scan_status()[0] + + log.debug( + f"Status Analog_output {ao_status}\n, Status Analog_input {ai_status}" + ) + fig, axes = plt.subplots(2, 2, sharex="col") + channel1 = np.array(readout[::2]) + channel2 = np.array(readout[1::2]) + beat = channel1 + channel2 + beat_square = beat**2 + + f, powerspec = welch(beat, fs=SAMPLERATE) + powerspec = decibel(powerspec) + + f_sq, powerspec_sq = welch(beat_square, fs=SAMPLERATE) + powerspec_sq = decibel(powerspec_sq) + peaks = find_peaks(powerspec_sq, prominence=20)[0] + + f_stim, powerspec_stim = welch(channel1, fs=SAMPLERATE) + powerspec_stim = decibel(powerspec_stim) + + f_in, powerspec_in = welch(channel2, fs=SAMPLERATE) + powerspec_in = decibel(powerspec_in) + + axes[0, 0].plot(t, channel1, label="Readout Channel0") + axes[0, 0].plot(t, channel2, label="Readout Channel1") + + axes[0, 1].plot(f_stim, powerspec_stim, label="powerspec Channel0") + axes[0, 1].plot(f_in, powerspec_in, label="powerspec Channel2") + axes[0, 1].set_xlabel("Freq [HZ]") + axes[0, 1].set_ylabel("dB") + + axes[1, 0].plot(t, beat, label="Beat") + axes[1, 0].plot(t, beat**2, label="Beat squared") + axes[1, 0].legend() + + axes[1, 1].plot(f, powerspec) + axes[1, 1].plot(f_sq, powerspec_sq) + axes[1, 1].scatter(f_sq[peaks], powerspec_sq[peaks]) + axes[1, 1].set_xlabel("Freq [HZ]") + axes[1, 1].set_ylabel("dB") + axes[0, 0].legend() embed() exit() +def decibel(power, ref_power=1.0, min_power=1e-20): + """Transform power to decibel relative to ref_power. + + \\[ decibel = 10 \\cdot \\log_{10}(power/ref\\_power) \\] + Power values smaller than `min_power` are set to `-np.inf`. + + Parameters + ---------- + power: float or array + Power values, for example from a power spectrum or spectrogram. + ref_power: float or None or 'peak' + Reference power for computing decibel. + If set to `None` or 'peak', the maximum power is used. + min_power: float + Power values smaller than `min_power` are set to `-np.inf`. + + Returns + ------- + decibel_psd: array + Power values in decibel relative to `ref_power`. + """ + if np.isscalar(power): + tmp_power = np.array([power]) + decibel_psd = np.array([power]) + else: + tmp_power = power + decibel_psd = power.copy() + if ref_power is None or ref_power == "peak": + ref_power = np.max(decibel_psd) + decibel_psd[tmp_power <= min_power] = float("-inf") + decibel_psd[tmp_power > min_power] = 10.0 * np.log10( + decibel_psd[tmp_power > min_power] / ref_power + ) + if np.isscalar(power): + return decibel_psd[0] + else: + return decibel_psd + + if __name__ == "__main__": SAMPLERATE = 40_000.0 DURATION = 5 - AMPLITUDE = 0.5 - SINFREQ = 10 + AMPLITUDE = 1 + SINFREQ = 1000 cal = Calibration() - # cal.ccheck_attenuator() - cal.check_amplitude() + # cal.check_attenuator() + # cal.check_amplitude() + cal.check_beat() From 43e0d4b75a55a6132b34ed56fae4dc7944b9c326 Mon Sep 17 00:00:00 2001 From: wendtalexander Date: Thu, 26 Sep 2024 14:29:47 +0200 Subject: [PATCH 4/5] adding calbi for different db --- pyrelacs/repros/calbi.py | 192 ++++++++++++++++++++++++--------------- 1 file changed, 121 insertions(+), 71 deletions(-) diff --git a/pyrelacs/repros/calbi.py b/pyrelacs/repros/calbi.py index 9f837cd..4ffef54 100644 --- a/pyrelacs/repros/calbi.py +++ b/pyrelacs/repros/calbi.py @@ -82,78 +82,128 @@ class Calibration(MccDac): t = np.arange(0, DURATION, 1 / SAMPLERATE) data = AMPLITUDE * np.sin(2 * np.pi * SINFREQ * t) # data = np.concatenate((data, data)) - - stim = self.write_analog( - data, - [0, 0], - SAMPLERATE, - ScanOption=uldaq.ScanOption.EXTTRIGGER, - ) - readout = self.read_analog( - [0, 1], - DURATION, - SAMPLERATE, - ScanOption=uldaq.ScanOption.EXTTRIGGER, - ) - self.diggital_trigger() - signal.signal(signal.SIGSEGV, self.segfault_handler) - log.info(self.ao_device) - ai_status = uldaq.ScanStatus.RUNNING - ao_status = uldaq.ScanStatus.RUNNING - - log.debug( - f"Status Analog_output {ao_status}\n, Status Analog_input {ai_status}" - ) - while (ai_status != uldaq.ScanStatus.IDLE) and ( - ao_status != uldaq.ScanStatus.IDLE - ): - log.debug("Scanning") - time.sleep(0.5) - ai_status = self.ai_device.get_scan_status()[0] - ao_status = self.ao_device.get_scan_status()[0] - - log.debug( - f"Status Analog_output {ao_status}\n, Status Analog_input {ai_status}" - ) + db_values = [0.0, -5.0, -8.5, -10.0] + colors = ["red", "blue", "black", "green"] + colors_in = ["lightcoral", "lightblue", "grey", "lightgreen"] fig, axes = plt.subplots(2, 2, sharex="col") - channel1 = np.array(readout[::2]) - channel2 = np.array(readout[1::2]) - beat = channel1 + channel2 - beat_square = beat**2 - - f, powerspec = welch(beat, fs=SAMPLERATE) - powerspec = decibel(powerspec) - - f_sq, powerspec_sq = welch(beat_square, fs=SAMPLERATE) - powerspec_sq = decibel(powerspec_sq) - peaks = find_peaks(powerspec_sq, prominence=20)[0] - - f_stim, powerspec_stim = welch(channel1, fs=SAMPLERATE) - powerspec_stim = decibel(powerspec_stim) - - f_in, powerspec_in = welch(channel2, fs=SAMPLERATE) - powerspec_in = decibel(powerspec_in) - - axes[0, 0].plot(t, channel1, label="Readout Channel0") - axes[0, 0].plot(t, channel2, label="Readout Channel1") - - axes[0, 1].plot(f_stim, powerspec_stim, label="powerspec Channel0") - axes[0, 1].plot(f_in, powerspec_in, label="powerspec Channel2") - axes[0, 1].set_xlabel("Freq [HZ]") - axes[0, 1].set_ylabel("dB") - - axes[1, 0].plot(t, beat, label="Beat") - axes[1, 0].plot(t, beat**2, label="Beat squared") - axes[1, 0].legend() - - axes[1, 1].plot(f, powerspec) - axes[1, 1].plot(f_sq, powerspec_sq) - axes[1, 1].scatter(f_sq[peaks], powerspec_sq[peaks]) - axes[1, 1].set_xlabel("Freq [HZ]") - axes[1, 1].set_ylabel("dB") - axes[0, 0].legend() - embed() - exit() + for i, db_value in enumerate(db_values): + self.set_attenuation_level(db_channel1=db_value) + stim = self.write_analog( + data, + [0, 0], + SAMPLERATE, + ScanOption=uldaq.ScanOption.EXTTRIGGER, + ) + readout = self.read_analog( + [0, 1], + DURATION, + SAMPLERATE, + ScanOption=uldaq.ScanOption.EXTTRIGGER, + ) + self.diggital_trigger() + signal.signal(signal.SIGSEGV, self.segfault_handler) + log.info(self.ao_device) + ai_status = uldaq.ScanStatus.RUNNING + ao_status = uldaq.ScanStatus.RUNNING + + log.debug( + f"Status Analog_output {ao_status}\n, Status Analog_input {ai_status}" + ) + while (ai_status != uldaq.ScanStatus.IDLE) and ( + ao_status != uldaq.ScanStatus.IDLE + ): + # log.debug("Scanning") + time.time_ns() + ai_status = self.ai_device.get_scan_status()[0] + ao_status = self.ao_device.get_scan_status()[0] + + self.write_bit(channel=0, bit=0) + log.debug( + f"Status Analog_output {ao_status}\n, Status Analog_input {ai_status}" + ) + channel1 = np.array(readout[::2]) + channel2 = np.array(readout[1::2]) + beat = channel1 + channel2 + beat_square = beat**2 + + f, powerspec = welch(beat, fs=SAMPLERATE) + powerspec = decibel(powerspec) + + f_sq, powerspec_sq = welch(beat_square, fs=SAMPLERATE) + powerspec_sq = decibel(powerspec_sq) + peaks = find_peaks(powerspec_sq, prominence=20)[0] + + f_stim, powerspec_stim = welch(channel1, fs=SAMPLERATE) + powerspec_stim = decibel(powerspec_stim) + + f_in, powerspec_in = welch(channel2, fs=SAMPLERATE) + powerspec_in = decibel(powerspec_in) + + axes[0, 0].plot( + t, + channel1, + label=f"{db_value} Readout Channel0", + color=colors[i], + ) + axes[0, 0].plot( + t, + channel2, + label=f"{db_value} Readout Channel1", + color=colors_in[i], + ) + + axes[0, 1].plot( + f_stim, + powerspec_stim, + label=f"{db_value} powerspec Channel0", + color=colors[i], + ) + axes[0, 1].plot( + f_in, + powerspec_in, + label=f"{db_value} powerspec Channel2", + color=colors_in[i], + ) + axes[0, 1].set_xlabel("Freq [HZ]") + axes[0, 1].set_ylabel("dB") + + axes[1, 0].plot( + t, + beat, + label="Beat", + color=colors[i], + ) + axes[1, 0].plot( + t, + beat**2, + label="Beat squared", + color=colors_in[i], + ) + axes[1, 0].legend() + + axes[1, 1].plot( + f, + powerspec, + color=colors[i], + ) + axes[1, 1].plot( + f_sq, + powerspec_sq, + color=colors_in[i], + label=f"dB {db_value}, first peak {np.min(f_sq[peaks])}", + ) + axes[1, 1].scatter( + f_sq[peaks], + powerspec_sq[peaks], + color="maroon", + ) + axes[1, 1].set_xlabel("Freq [HZ]") + axes[1, 1].set_ylabel("dB") + axes[0, 0].legend() + axes[1, 1].legend() + plt.show() + self.set_analog_to_zero() + self.disconnect_dac() def decibel(power, ref_power=1.0, min_power=1e-20): From dd3e0d045d1c6b08f97e1b871a0ac0477c0d65a2 Mon Sep 17 00:00:00 2001 From: wendtalexander Date: Fri, 27 Sep 2024 09:12:11 +0200 Subject: [PATCH 5/5] rewriting the project structure --- pyrelacs/app.py | 63 +++---------------- pyrelacs/{repros => devices}/mccdac.py | 0 pyrelacs/repros/__init__.py | 0 pyrelacs/repros/calbi.py | 48 +++++++------- pyrelacs/repros/inandout.py | 86 -------------------------- pyrelacs/repros/input.py | 28 --------- pyrelacs/repros/output.py | 28 --------- pyrelacs/repros/repros.py | 33 ++++++++++ pyrelacs/worker.py | 75 ++++++++++++++++++++++ 9 files changed, 139 insertions(+), 222 deletions(-) rename pyrelacs/{repros => devices}/mccdac.py (100%) create mode 100644 pyrelacs/repros/__init__.py delete mode 100644 pyrelacs/repros/inandout.py delete mode 100644 pyrelacs/repros/input.py delete mode 100644 pyrelacs/repros/output.py create mode 100644 pyrelacs/repros/repros.py create mode 100644 pyrelacs/worker.py diff --git a/pyrelacs/app.py b/pyrelacs/app.py index 288f8b1..ea8c152 100644 --- a/pyrelacs/app.py +++ b/pyrelacs/app.py @@ -1,7 +1,6 @@ from PyQt6.QtGui import QAction import sys import pathlib -import ctypes from PyQt6.QtCore import QProcess, QSize, QThreadPool, Qt from PyQt6.QtWidgets import ( @@ -13,12 +12,13 @@ from PyQt6.QtWidgets import ( QMainWindow, QPlainTextEdit, ) -import tomli import uldaq from IPython import embed import numpy as np from pyrelacs.util.logging import config_logging +from pyrelacs.worker import Worker +from pyrelacs.repros.repros import Repro log = config_logging() @@ -30,8 +30,7 @@ class PyRelacs(QMainWindow): self.setMinimumSize(1000, 1000) self.threadpool = QThreadPool() - # for starting a Qprocess - self.p = None + self.repros = Repro() self.daq_connect_button = QPushButton("Connect Daq") self.daq_connect_button.setCheckable(True) @@ -51,7 +50,7 @@ class PyRelacs(QMainWindow): self.toolbar = QToolBar("Repros") self.addToolBar(self.toolbar) - self.repro() + self.repros_to_toolbar() self.setFixedSize(QSize(400, 300)) widget = QWidget() @@ -81,60 +80,18 @@ class PyRelacs(QMainWindow): except AttributeError: log.debug("DAQ was not connected") - def repro(self): - repos_path = pathlib.Path(__file__).parent / "repros" - repos_names = list(repos_path.glob("*.py")) - # exclude the repos.py file - repos_names = [ - f.with_suffix("").name for f in repos_names if not f.name == "repos.py" - ] - for rep in repos_names: + def repros_to_toolbar(self): + repro_names, file_names = self.repros.names_of_repros() + for rep, fn in zip(repro_names, file_names): individual_repro_button = QAction(rep, self) individual_repro_button.setStatusTip("Button") individual_repro_button.triggered.connect( - lambda checked, n=rep: self.run_repro(n) + lambda checked, n=rep, f=fn: self.run_repro(n, f) ) self.toolbar.addAction(individual_repro_button) - def message(self, s): - self.text.appendPlainText(s) - - def run_repro(self, name_of_repo): - if self.p is None: - self.message(f"Executing process {name_of_repo}") - self.p = QProcess() - self.p.setWorkingDirectory(str(pathlib.Path(__file__).parent / "repros/")) - # log.debug(pathlib.Path(__file__).parent / "repos") - self.p.readyReadStandardOutput.connect(self.handle_stdout) - self.p.readyReadStandardError.connect(self.handle_stderr) - self.p.stateChanged.connect(self.handle_state) - self.p.finished.connect(self.process_finished) - self.p.start("python3", [f"{name_of_repo}" + ".py"]) - - def handle_stderr(self): - if self.p is not None: - data = self.p.readAllStandardError() - stderr = bytes(data).decode("utf8") - self.message(stderr) - - def handle_stdout(self): - if self.p is not None: - data = self.p.readAllStandardOutput() - stdout = bytes(data).decode("utf8") - self.message(stdout) - - def handle_state(self, state): - states = { - QProcess.ProcessState.NotRunning: "Not running", - QProcess.ProcessState.Starting: "Starting", - QProcess.ProcessState.Running: "Running", - } - state_name = states[state] - self.message(f"State changed: {state_name}") - - def process_finished(self): - self.text.appendPlainText("Process finished") - self.p = None + def run_repro(self, n, fn): + self.text.appendPlainText(f"started Repro {n}, {fn}") if __name__ == "__main__": diff --git a/pyrelacs/repros/mccdac.py b/pyrelacs/devices/mccdac.py similarity index 100% rename from pyrelacs/repros/mccdac.py rename to pyrelacs/devices/mccdac.py diff --git a/pyrelacs/repros/__init__.py b/pyrelacs/repros/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyrelacs/repros/calbi.py b/pyrelacs/repros/calbi.py index 4ffef54..9b36a0a 100644 --- a/pyrelacs/repros/calbi.py +++ b/pyrelacs/repros/calbi.py @@ -7,10 +7,10 @@ import uldaq from IPython import embed import numpy as np import matplotlib.pyplot as plt -from scipy.signal import peak_widths, welch, csd +from scipy.signal import welch, csd from scipy.signal import find_peaks -from pyrelacs.repros.mccdac import MccDac +from pyrelacs.devices.mccdac import MccDac from pyrelacs.util.logging import config_logging log = config_logging() @@ -20,6 +20,12 @@ faulthandler.enable() class Calibration(MccDac): def __init__(self) -> None: super().__init__() + self.SAMPLERATE = 40_000.0 + self.DURATION = 5 + self.AMPLITUDE = 1 + self.SINFREQ = 750 + + def run(self): def segfault_handler(self, signum, frame): print(f"Segmentation fault caught! Signal number: {signum}") @@ -31,8 +37,8 @@ class Calibration(MccDac): colors = ["red", "green", "blue", "black", "yellow"] self.set_attenuation_level(db_channel1=0.0, db_channel2=0.0) # write to ananlog 1 - t = np.arange(0, DURATION, 1 / SAMPLERATE) - data = AMPLITUDE * np.sin(2 * np.pi * SINFREQ * t) + t = np.arange(0, self.DURATION, 1 / self.SAMPLERATE) + data = self.AMPLITUDE * np.sin(2 * np.pi * self.SINFREQ * t) fig, ax = plt.subplots() for i, db_value in enumerate(db_values): @@ -42,12 +48,12 @@ class Calibration(MccDac): stim = self.write_analog( data, [0, 0], - SAMPLERATE, + self.SAMPLERATE, ScanOption=uldaq.ScanOption.EXTTRIGGER, ) data_channel_one = self.read_analog( - [0, 0], DURATION, SAMPLERATE, ScanOption=uldaq.ScanOption.EXTTRIGGER + [0, 0], self.DURATION, self.SAMPLERATE, ScanOption=uldaq.ScanOption.EXTTRIGGER ) time.sleep(1) @@ -79,8 +85,8 @@ class Calibration(MccDac): def check_beat(self): self.set_attenuation_level(db_channel1=-10.0, db_channel2=0.0) - t = np.arange(0, DURATION, 1 / SAMPLERATE) - data = AMPLITUDE * np.sin(2 * np.pi * SINFREQ * t) + t = np.arange(0, self.DURATION, 1 / self.SAMPLERATE) + data = self.AMPLITUDE * np.sin(2 * np.pi * self.SINFREQ * t) # data = np.concatenate((data, data)) db_values = [0.0, -5.0, -8.5, -10.0] colors = ["red", "blue", "black", "green"] @@ -91,13 +97,13 @@ class Calibration(MccDac): stim = self.write_analog( data, [0, 0], - SAMPLERATE, + self.SAMPLERATE, ScanOption=uldaq.ScanOption.EXTTRIGGER, ) readout = self.read_analog( [0, 1], - DURATION, - SAMPLERATE, + self.DURATION, + self.SAMPLERATE, ScanOption=uldaq.ScanOption.EXTTRIGGER, ) self.diggital_trigger() @@ -126,17 +132,17 @@ class Calibration(MccDac): beat = channel1 + channel2 beat_square = beat**2 - f, powerspec = welch(beat, fs=SAMPLERATE) + f, powerspec = welch(beat, fs=self.SAMPLERATE) powerspec = decibel(powerspec) - f_sq, powerspec_sq = welch(beat_square, fs=SAMPLERATE) + f_sq, powerspec_sq = welch(beat_square, fs=self.SAMPLERATE) powerspec_sq = decibel(powerspec_sq) peaks = find_peaks(powerspec_sq, prominence=20)[0] - f_stim, powerspec_stim = welch(channel1, fs=SAMPLERATE) + f_stim, powerspec_stim = welch(channel1, fs=self.SAMPLERATE) powerspec_stim = decibel(powerspec_stim) - f_in, powerspec_in = welch(channel2, fs=SAMPLERATE) + f_in, powerspec_in = welch(channel2, fs=self.SAMPLERATE) powerspec_in = decibel(powerspec_in) axes[0, 0].plot( @@ -243,15 +249,3 @@ def decibel(power, ref_power=1.0, min_power=1e-20): return decibel_psd[0] else: return decibel_psd - - -if __name__ == "__main__": - SAMPLERATE = 40_000.0 - DURATION = 5 - AMPLITUDE = 1 - SINFREQ = 1000 - - cal = Calibration() - # cal.check_attenuator() - # cal.check_amplitude() - cal.check_beat() diff --git a/pyrelacs/repros/inandout.py b/pyrelacs/repros/inandout.py deleted file mode 100644 index 20d81aa..0000000 --- a/pyrelacs/repros/inandout.py +++ /dev/null @@ -1,86 +0,0 @@ -import ctypes - -import uldaq -from IPython import embed -import matplotlib.pyplot as plt -import numpy as np - - -from pyrelacs.util.logging import config_logging - -log = config_logging() - - -class ReadWrite: - def __init__(self) -> None: - devices = uldaq.get_daq_device_inventory(uldaq.InterfaceType.USB) - log.debug(f"Found daq devices {len(devices)}, connecting to the first one") - self.daq_device = uldaq.DaqDevice(devices[0]) - self.daq_device.connect() - log.debug("Connected") - # self.daq_device.enable_event( - # uldaq.DaqEventType.ON_DATA_AVAILABLE, - # 1, - # self.read_write, - # (uldaq.DaqEventType.ON_DATA_AVAILABLE, 1, 1), - # ) - - def read_write(self) -> None: - # event_type = callback_args.event_type - # event_data = callback_args.event_data - # user_data = callback_args.user_data - - FS = 30_000.0 - DURATION = 10 - FREQUENCY = 100 - - time = np.arange(0, DURATION, 1 / FS) - data = 2 * np.sin(2 * np.pi * FREQUENCY * time) - - buffer = ctypes.c_double * len(time) - data_c = buffer(*data) - buf = uldaq.create_float_buffer(1, len(time)) - - # Get the Ananlog In device and Analog Info - ai_device = self.daq_device.get_ai_device() - ai_info = ai_device.get_info() - - # Get the Analog Out device - ao_device = self.daq_device.get_ao_device() - ao_info = ao_device.get_info() - - er_ao = ao_device.a_out_scan( - 0, - 0, - uldaq.Range.BIP10VOLTS, - int(len(data)), - 30_000.0, - uldaq.ScanOption.DEFAULTIO, - uldaq.AOutScanFlag.DEFAULT, - data_c, - ) - - er_ai = ai_device.a_in_scan( - 1, - 1, - uldaq.AiInputMode.SINGLE_ENDED, - uldaq.Range.BIP10VOLTS, - len(time), - FS, - uldaq.ScanOption.DEFAULTIO, - uldaq.AInScanFlag.DEFAULT, - data=buf, - ) - ai_device.scan_wait(uldaq.WaitType.WAIT_UNTIL_DONE, timeout=-1) - log.debug("Scanning") - - self.daq_device.disconnect() - self.daq_device.release() - plt.plot(buf) - plt.plot(data_c) - plt.show() - - -if __name__ == "__main__": - daq_input = ReadWrite() - daq_input.read_write() diff --git a/pyrelacs/repros/input.py b/pyrelacs/repros/input.py deleted file mode 100644 index 7497a76..0000000 --- a/pyrelacs/repros/input.py +++ /dev/null @@ -1,28 +0,0 @@ -import uldaq -import matplotlib.pyplot as plt - -from pyrelacs.util.logging import config_logging -from .repos import MccDac - -log = config_logging() - - -class ReadData(MccDac): - def __init__(self) -> None: - super().__init__() - - def analog_in(self) -> None: - # Get the Ananlog In device and Analog Info - data = self.read_analog_daq( - [0, 0], - 10, - 3000.0, - ) - plt.plot(data) - plt.show() - self.disconnect_dac() - - -if __name__ == "__main__": - daq_input = ReadData() - daq_input.analog_in() diff --git a/pyrelacs/repros/output.py b/pyrelacs/repros/output.py deleted file mode 100644 index 7a80d5f..0000000 --- a/pyrelacs/repros/output.py +++ /dev/null @@ -1,28 +0,0 @@ -import ctypes - -import uldaq -from IPython import embed -from pyrelacs.repros.repos import MccDac -from pyrelacs.util.logging import config_logging -import numpy as np -import matplotlib.pyplot as plt - -log = config_logging() - - -class Output_daq(MccDac): - def __init__(self) -> None: - super().__init__() - - def write_daq(self): - log.debug("running repro") - time = np.arange(0, 10, 1 / 30_000.0) - data = 1 * np.sin(2 * np.pi * 1 * time) - self.write_analog(data, [0, 0], 30_000, ScanOption=uldaq.ScanOption.EXTTRIGGER) - self.diggital_trigger() - - -if __name__ == "__main__": - daq_input = Output_daq() - daq_input.write_daq() - # daq_input.trigger() diff --git a/pyrelacs/repros/repros.py b/pyrelacs/repros/repros.py new file mode 100644 index 0000000..eb96578 --- /dev/null +++ b/pyrelacs/repros/repros.py @@ -0,0 +1,33 @@ +import ast +import pathlib + +from IPython import embed + + +class Repro: + def __init__(self) -> None: + pass + + def run_repro(self, name: str, *args, **kwargs) -> None: + pass + + def names_of_repros(self): + file_path_cur = pathlib.Path(__file__).parent + python_files = list(file_path_cur.glob("**/*.py")) + exclude_files = ["repros.py", "__init__.py"] + python_files = [f for f in python_files if f.name not in exclude_files] + repro_names = [] + file_names = [] + for python_file in python_files: + with open(python_file, "r") as file: + file_content = file.read() + tree = ast.parse(file_content) + class_name = [ + node.name + for node in ast.walk(tree) + if isinstance(node, ast.ClassDef) + ] + repro_names.extend(class_name) + file_names.append(python_file) + file.close() + return repro_names, file_names diff --git a/pyrelacs/worker.py b/pyrelacs/worker.py new file mode 100644 index 0000000..769198f --- /dev/null +++ b/pyrelacs/worker.py @@ -0,0 +1,75 @@ +import sys +import traceback + +from PyQt6.QtCore import QRunnable, pyqtSlot, QObject, pyqtSignal + + +class WorkerSignals(QObject): + """ + Defines the signals available from a running worker thread. + + Supported signals are: + + finished + No data + + error + tuple (exctype, value, traceback.format_exc() ) + + result + object data returned from processing, anything + + progress + int indicating % progress + + """ + + finished = pyqtSignal() + error = pyqtSignal(tuple) + result = pyqtSignal(object) + progress = pyqtSignal(int) + + +class Worker(QRunnable): + """ + Worker thread + + Inherits from QRunnable to handler worker thread setup, signals and wrap-up. + + :param callback: The function callback to run on this worker thread. Supplied args and + kwargs will be passed through to the runner. + :type callback: function + :param args: Arguments to pass to the callback function + :param kwargs: Keywords to pass to the callback function + + """ + + def __init__(self, fn, *args, **kwargs): + super(Worker, self).__init__() + + # Store constructor arguments (re-used for processing) + self.fn = fn + self.args = args + self.kwargs = kwargs + self.signals = WorkerSignals() + + # Add the callback to our kwargs + self.kwargs["progress_callback"] = self.signals.progress + + @pyqtSlot() + def run(self): + """ + Initialise the runner function with passed args, kwargs. + """ + + # Retrieve args/kwargs here; and fire processing using them + try: + result = self.fn(*self.args, **self.kwargs) + except: + traceback.print_exc() + exctype, value = sys.exc_info()[:2] + self.signals.error.emit((exctype, value, traceback.format_exc())) + else: + self.signals.result.emit(result) # Return the result of the processing + finally: + self.signals.finished.emit() # Done