13 Commits

Author SHA1 Message Date
wendtalexander
565d6e5318 [plotting] move calibrtion plot outside of main window 2024-09-30 16:22:24 +02:00
wendtalexander
b35a9212ac [plotting] move calibration plot outside of mainwindow.py 2024-09-30 16:22:00 +02:00
wendtalexander
56c8b59ccd [project] changing to absolut imports 2024-09-30 12:10:54 +02:00
wendtalexander
cbc86598b0 [ui] adding information to text field 2024-09-30 12:10:28 +02:00
wendtalexander
031b5098d5 [plotting calibration] fixing power spectrum 2024-09-30 12:09:39 +02:00
wendtalexander
c33e4cc32f Merge branch 'main' into calibration 2024-09-30 10:13:48 +02:00
wendtalexander
4868b0e196 Merge branch 'jgrewe-icons' 2024-09-30 10:13:04 +02:00
wendtalexander
00f6b11740 [project] removing data.nix file 2024-09-30 10:10:32 +02:00
wendtalexander
e0491c5917 [repos/calibration] fixing plots 2024-09-30 10:10:12 +02:00
wendtalexander
9c80091d16 [repos/calibration] removing plots 2024-09-30 10:09:44 +02:00
b0897bf52d [icons] revert back to the qt resource system for icons 2024-09-30 10:03:29 +02:00
09dd7f3d51 [resources] add relacstux to resources 2024-09-30 10:03:04 +02:00
0a00875d2e [icons] replace with working images 2024-09-30 09:48:57 +02:00
12 changed files with 146 additions and 170 deletions

View File

@@ -6,10 +6,13 @@ from PyQt6.QtWidgets import QApplication
from pyrelacs import info from pyrelacs import info
from pyrelacs.ui.mainwindow import PyRelacs from pyrelacs.ui.mainwindow import PyRelacs
from pyrelacs.util.logging import config_logging from pyrelacs.util.logging import config_logging
import resources
log = config_logging() log = config_logging()
from pyrelacs import (
resources,
) # best created with pyside6-rcc resources.qrc -o resources.py (rcc produces an error...)
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -159,87 +159,7 @@ class Calibration(MccDac):
unit="s", unit="s",
) )
beat = channel1 + channel2
beat_square = beat**2
f, powerspec = welch(beat, fs=self.SAMPLERATE)
powerspec = decibel(powerspec)
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=self.SAMPLERATE)
powerspec_stim = decibel(powerspec_stim)
f_in, powerspec_in = welch(channel2, fs=self.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.set_analog_to_zero()
self.disconnect_dac()
def decibel(power, ref_power=1.0, min_power=1e-20): def decibel(power, ref_power=1.0, min_power=1e-20):

View File

@@ -5,5 +5,6 @@
<file>icons/disconnect.png</file> <file>icons/disconnect.png</file>
<file>icons/record.png</file> <file>icons/record.png</file>
<file>icons/stop.png</file> <file>icons/stop.png</file>
<file>icons/relacstuxheader.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -1,5 +1,3 @@
import pathlib
from PyQt6.QtGui import QPixmap from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QLabel, QVBoxLayout, QWidget from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QLabel, QVBoxLayout, QWidget
from PyQt6.QtCore import Qt from PyQt6.QtCore import Qt
@@ -40,8 +38,7 @@ class About(QWidget):
rtd_link.setAlignment(Qt.AlignmentFlag.AlignCenter) rtd_link.setAlignment(Qt.AlignmentFlag.AlignCenter)
iconlabel = QLabel() iconlabel = QLabel()
_root = pathlib.Path(__file__).parent.parent pixmap = QPixmap(":/icons/relacstuxheader.png")
pixmap = QPixmap(str(pathlib.Path.joinpath(_root, "icons/relacstuxheader.png")))
s = pixmap.size() s = pixmap.size()
new_height = int(s.height() * 300/s.width()) new_height = int(s.height() * 300/s.width())
pixmap = pixmap.scaled(300, new_height, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation) pixmap = pixmap.scaled(300, new_height, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation)

View File

@@ -22,6 +22,7 @@ from pyrelacs.worker import Worker
from pyrelacs.repros.repros import Repro from pyrelacs.repros.repros import Repro
from pyrelacs.util.logging import config_logging from pyrelacs.util.logging import config_logging
from pyrelacs.ui.about import AboutDialog from pyrelacs.ui.about import AboutDialog
from pyrelacs.ui.plots.calibration import CalibrationPlot
log = config_logging() log = config_logging()
_root = path(__file__).parent.parent _root = path(__file__).parent.parent
@@ -36,8 +37,16 @@ class PyRelacs(QMainWindow):
Qt.ToolButtonStyle.ToolButtonTextBesideIcon Qt.ToolButtonStyle.ToolButtonTextBesideIcon
) # Ensure icons are displayed with text ) # Ensure icons are displayed with text
self.setWindowTitle("PyRelacs") self.setWindowTitle("PyRelacs")
self.beat_plot = pg.PlotWidget()
self.power_plot = pg.PlotWidget() self.figure = pg.GraphicsLayoutWidget()
filename = path.joinpath(path.cwd(), "data.nix")
if filename.exists():
self.nix_file = nix.File.open(str(filename), nix.FileMode.ReadOnly)
else:
self.nix_file = nix.File.open(str(filename), nix.FileMode.Overwrite)
self.calibration_plot = CalibrationPlot(self.figure, self.nix_file)
self.threadpool = QThreadPool() self.threadpool = QThreadPool()
self.repros = Repro() self.repros = Repro()
@@ -52,21 +61,15 @@ class PyRelacs(QMainWindow):
self.create_toolbars() self.create_toolbars()
layout = QGridLayout() layout = QGridLayout()
layout.addWidget(self.beat_plot, 0, 0, 1, 2) layout.addWidget(self.figure, 0, 0, 2, 2)
layout.addWidget(self.power_plot, 1, 0, 1, 2)
layout.addWidget(self.text, 2, 0, 1, 2) layout.addWidget(self.text, 2, 0, 1, 2)
widget = QWidget() widget = QWidget()
widget.setLayout(layout) widget.setLayout(layout)
self.setCentralWidget(widget) self.setCentralWidget(widget)
filename = path.joinpath(path.cwd(), "data")
self.nix_file = nix.File.open(str(filename), nix.FileMode.Overwrite)
def create_actions(self): def create_actions(self):
self._rlx_exitaction = QAction( self._rlx_exitaction = QAction(QIcon(":/icons/exit.png"), "Exit", self)
QIcon(str(path.joinpath(_root, "icons/exit.png"))), "Exit", self
)
self._rlx_exitaction.setStatusTip("Close relacs") self._rlx_exitaction.setStatusTip("Close relacs")
self._rlx_exitaction.setShortcut(QKeySequence("Alt+q")) self._rlx_exitaction.setShortcut(QKeySequence("Alt+q"))
self._rlx_exitaction.triggered.connect(self.on_exit) self._rlx_exitaction.triggered.connect(self.on_exit)
@@ -77,29 +80,25 @@ class PyRelacs(QMainWindow):
self._rlx_aboutaction.triggered.connect(self.on_about) self._rlx_aboutaction.triggered.connect(self.on_about)
self._daq_connectaction = QAction( self._daq_connectaction = QAction(
QIcon(str(path.joinpath(_root, "icons/connect.png"))), "Connect DAQ", self QIcon(":icons/connect.png"), "Connect DAQ", self
) )
self._daq_connectaction.setStatusTip("Connect to daq device") self._daq_connectaction.setStatusTip("Connect to daq device")
# self._daq_connectaction.setShortcut(QKeySequence("Alt+d")) # self._daq_connectaction.setShortcut(QKeySequence("Alt+d"))
self._daq_connectaction.triggered.connect(self.connect_dac) self._daq_connectaction.triggered.connect(self.connect_dac)
self._daq_disconnectaction = QAction( self._daq_disconnectaction = QAction(
QIcon(str(path.joinpath(_root, "icons/disconnect.png"))), QIcon(":/icons/disconnect.png"), "Disconnect DAQ", self
"Disconnect DAQ",
self,
) )
self._daq_disconnectaction.setStatusTip("Disconnect the DAQ device") self._daq_disconnectaction.setStatusTip("Disconnect the DAQ device")
# self._daq_connectaction.setShortcut(QKeySequence("Alt+d")) # self._daq_connectaction.setShortcut(QKeySequence("Alt+d"))
self._daq_disconnectaction.triggered.connect(self.disconnect_dac) self._daq_disconnectaction.triggered.connect(self.disconnect_dac)
self._daq_calibaction = QAction( self._daq_calibaction = QAction(
QIcon(str(path.joinpath(_root, "icons/calibration.png"))), QIcon(":/icons/calibration.png"), "Plot calibration", self
"Plot calibration",
self,
) )
self._daq_calibaction.setStatusTip("Calibrate the attenuator device") self._daq_calibaction.setStatusTip("Calibrate the attenuator device")
# self._daq_calibaction.setShortcut(QKeySequence("Alt+d")) # self._daq_calibaction.setShortcut(QKeySequence("Alt+d"))
self._daq_calibaction.triggered.connect(self.plot_calibration) self._daq_calibaction.triggered.connect(self.calibration_plot.plot)
self.create_menu() self.create_menu()
def create_menu(self): def create_menu(self):
@@ -162,70 +161,7 @@ class PyRelacs(QMainWindow):
self.plot_calibration_button = QPushButton("Plot Calibration") self.plot_calibration_button = QPushButton("Plot Calibration")
self.plot_calibration_button.setCheckable(True) self.plot_calibration_button.setCheckable(True)
self.plot_calibration_button.clicked.connect(self.plot_calibration) self.plot_calibration_button.clicked.connect(self.calibration_plot.plot)
def plot_calibration(self):
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
block = self.nix_file.blocks[0]
colors = ["red", "green", "blue", "black", "yellow"]
for i, (stim, fish) in enumerate(
zip(list(block.data_arrays)[::2], list(block.data_arrays)[1::2])
):
beat = stim[:] + fish[:]
beat_squared = beat**2
f, powerspec = welch(beat, fs=40_000.0)
powerspec = decibel(powerspec)
f_sq, powerspec_sq = welch(beat_squared, fs=40_000.0)
powerspec_sq = decibel(powerspec_sq)
peaks = find_peaks(powerspec_sq, prominence=20)[0]
pen = pg.mkPen(colors[i])
self.beat_plot.plot(
np.arange(0, len(beat)) / 40_000.0,
beat_squared,
pen=pen,
# name=stim.name,
)
self.power_plot.plot(f_sq, powerspec_sq, pen=pen)
self.power_plot.plot(f[peaks], powerspec_sq[peaks], pen=None, symbol="x")
def connect_dac(self): def connect_dac(self):
devices = uldaq.get_daq_device_inventory(uldaq.InterfaceType.USB) devices = uldaq.get_daq_device_inventory(uldaq.InterfaceType.USB)
@@ -267,8 +203,12 @@ class PyRelacs(QMainWindow):
self.threadpool.start(worker) self.threadpool.start(worker)
def add_to_textfield(self, s: str):
self.text.appendPlainText(s)
def on_exit(self): def on_exit(self):
print("exit button!") log.info("exit button!")
self.add_to_textfield("exiting")
self.close() self.close()
def on_about(self, e): def on_about(self, e):
@@ -276,10 +216,12 @@ class PyRelacs(QMainWindow):
about.show() about.show()
def print_output(self, s): def print_output(self, s):
print(s) log.info(s)
self.add_to_textfield(s)
def thread_complete(self): def thread_complete(self):
print("THREAD COMPLETE!") log.info("Thread complete!")
self.add_to_textfield("Thread complete!")
def progress_fn(self, n): def progress_fn(self, n):
print("%d%% done" % n) print("%d%% done" % n)

View File

@@ -0,0 +1,113 @@
from IPython import embed
import pyqtgraph as pg
import numpy as np
from scipy.signal import welch, find_peaks
from scipy.integrate import romb
class CalibrationPlot:
def __init__(self, figure: pg.GraphicsLayoutWidget, nix_file):
self.figure = figure
self.nix_file = nix_file
def plot(self):
self.figure.setBackground("w")
self.beat_plot = self.figure.addPlot(row=0, col=0)
self.power_plot = self.figure.addPlot(row=1, col=0)
self.beat_plot.addLegend()
self.power_plot.addLegend()
# self.power_plot.setLogMode(x=False, y=True)
block = self.nix_file.blocks[0]
colors = ["red", "green", "blue", "black", "yellow"]
for i, (stim, fish) in enumerate(
zip(list(block.data_arrays)[::2], list(block.data_arrays)[1::2])
):
f_stim, stim_power = welch(
stim[:],
fs=40_000.0,
window="flattop",
nperseg=100_000,
)
stim_power = self.decibel(stim_power)
stim_max_power_index = np.argmax(stim_power)
freq_stim = f_stim[stim_max_power_index]
f_fish, fish_power = welch(
fish[:],
fs=40_000.0,
window="flattop",
nperseg=100_000,
)
fish_power = self.decibel(fish_power)
fish_max_power_index = np.argmax(fish_power)
freq_fish = f_fish[fish_max_power_index]
beat_frequency = np.abs(freq_fish - freq_stim)
beat = stim[:] + fish[:]
beat_squared = beat**2
f, powerspec = welch(
beat_squared,
window="flattop",
fs=40_000.0,
nperseg=100_000,
)
powerspec = self.decibel(powerspec)
padding = 20
integration_window = powerspec[
(f > beat_frequency - padding) & (f < beat_frequency + padding)
]
peaks = find_peaks(powerspec, prominence=40)[0]
pen = pg.mkPen(colors[i])
self.beat_plot.plot(
np.arange(0, len(beat)) / 40_000.0,
beat,
pen=pen,
name=stim.name,
)
self.power_plot.plot(f, powerspec, pen=pen, name=stim.name)
self.power_plot.plot(f[peaks], powerspec[peaks], pen=None, symbol="x")
def decibel(self, 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