From 1dc72d00bba1a7e8cfbd0a50b76b8186405a91a1 Mon Sep 17 00:00:00 2001
From: wendtalexander <wendtalexander@protonmail.com>
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 <wendtalexander@protonmail.com>
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 <wendtalexander@protonmail.com>
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 <wendtalexander@protonmail.com>
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 <wendtalexander@protonmail.com>
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