45 Commits

Author SHA1 Message Date
wendtalexander
0113e94625 [dataio/nix] adding comments 2024-11-04 08:04:20 +01:00
wendtalexander
cce7c55fe7 [daq] trying different channels 2024-10-25 18:03:43 +02:00
wendtalexander
6c49f19b43 [dataio/daq] updating log messages 2024-10-25 18:03:23 +02:00
wendtalexander
2ef018ef07 [dataio/daq] making the buffer bigger 2024-10-24 16:35:09 +02:00
wendtalexander
3ab7a33b52 [config] updating config 2024-10-24 11:51:27 +02:00
wendtalexander
acd41302c7 [ui/plots] plotting now two different channels from the buffer 2024-10-24 11:51:17 +02:00
wendtalexander
1fe854b74e [ui] moving creation of nix to nix_file 2024-10-24 11:50:49 +02:00
wendtalexander
a53ff62d96 [dataio/nix] moving creation of nix to nix writer 2024-10-24 11:50:32 +02:00
wendtalexander
3d3d9a0cdd [dataio/daq] writes now two channels in the ringbuffer 2024-10-24 11:50:00 +02:00
wendtalexander
8f6c9b1e5e [ui/structure] updating repros to tab bar with individual plots and nix blocks 2024-10-23 11:09:44 +02:00
wendtalexander
574e9a8110 [ui/plots] fixing error if nothing has been plotted, the gui could not close 2024-10-23 11:08:54 +02:00
wendtalexander
75619cf1c8 [repro/class] provides with repros with args and kwargs 2024-10-23 11:07:55 +02:00
wendtalexander
02911e57f8 [repros] moving to sperate folders 2024-10-23 11:07:19 +02:00
wendtalexander
557535ffa4 [repros] moving to sperate folder, writing run method 2024-10-23 11:06:51 +02:00
wendtalexander
110629dae0 [calibration] moving calibration to own folder, rewriting run method 2024-10-23 11:06:26 +02:00
wendtalexander
7c4b5098c1 [mccdaq] printing exception error 2024-10-23 11:05:41 +02:00
wendtalexander
e2b7ed3a61 [ui] creates a nix file with pressing the record button 2024-10-22 10:11:07 +02:00
wendtalexander
038327bfeb [ui/continously] updating plot to plot the last chunk 2024-10-22 10:10:40 +02:00
wendtalexander
4f7ebbe8c3 [dataio/nix] adding mutex, writing data_array, and the last chunk 2024-10-22 10:10:09 +02:00
wendtalexander
12e82dceee [dataio] adding time sleep for performance 2024-10-21 17:44:02 +02:00
wendtalexander
e4e86cbc49 [ui/plot] updating plot 2024-10-21 10:29:38 +02:00
wendtalexander
e36db5e7b0 [dataio/producer] removing comments 2024-10-21 10:28:46 +02:00
wendtalexander
33f046c072 [dataio/nix] adding sleep while writing 2024-10-21 10:28:25 +02:00
wendtalexander
6d2eb09c65 [ui/plot] changing chunk size of the plot depending on the sampling rate 2024-10-18 16:46:33 +02:00
wendtalexander
4029034174 [config] adding bool daq, hints 2024-10-18 16:32:05 +02:00
wendtalexander
9fd4892325 [ui/mainwindow] organising file 2024-10-18 16:31:45 +02:00
wendtalexander
85b5a71ccb [ui/plots] updating plot 2024-10-18 16:31:22 +02:00
wendtalexander
5d62cb0384 [repros] adding example sinuns 2024-10-18 16:31:03 +02:00
wendtalexander
64cd1b00ad [repros] updating names of repros 2024-10-18 16:30:46 +02:00
wendtalexander
f703687ed7 [repros] renaming file 2024-10-18 16:30:27 +02:00
wendtalexander
4e0a1f0ac5 [devices] spelling 2024-10-18 16:30:09 +02:00
wendtalexander
4864538213 [dataio/sinus] removing code 2024-10-18 16:29:27 +02:00
wendtalexander
28dd0b7080 [dataio/nix] removing log messages 2024-10-18 16:28:53 +02:00
wendtalexander
b94078634d [dataio/daq] fixing daq_buffer to int 2024-10-18 16:28:24 +02:00
wendtalexander
e3c867f4fd [dataio] changing samplerate from int to float 2024-10-18 16:27:55 +02:00
wendtalexander
13897f29e3 [config] adding bool for daq 2024-10-18 16:27:32 +02:00
wendtalexander
afc37adb1a [project] adding quantities for unit conversion 2024-10-16 08:52:49 +02:00
wendtalexander
b87e7f1ffa [app] adding config 2024-10-16 08:52:27 +02:00
wendtalexander
f576f33cc5 [ui/mainwindow] adding unitconversion 2024-10-16 08:52:16 +02:00
wendtalexander
e16211b988 [config] adding config loading function 2024-10-16 08:51:59 +02:00
wendtalexander
b22ef04317 [project] adding dacite for config loading 2024-10-16 07:57:09 +02:00
wendtalexander
d2f7d0e966 [config] fixing config 2024-10-16 07:56:46 +02:00
wendtalexander
cc9089f503 [config] fixing config loader 2024-10-16 07:56:32 +02:00
wendtalexander
351850e05c [config] remaping values into dict of value and unit 2024-10-15 07:58:02 +02:00
wendtalexander
e44e021982 [config] converting config into dataclasses 2024-10-15 07:57:33 +02:00
16 changed files with 799 additions and 295 deletions

33
poetry.lock generated
View File

@@ -142,6 +142,19 @@ files = [
docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
tests = ["pytest", "pytest-cov", "pytest-xdist"] tests = ["pytest", "pytest-cov", "pytest-xdist"]
[[package]]
name = "dacite"
version = "1.8.1"
description = "Simple creation of data classes from dictionaries."
optional = false
python-versions = ">=3.6"
files = [
{file = "dacite-1.8.1-py3-none-any.whl", hash = "sha256:cc31ad6fdea1f49962ea42db9421772afe01ac5442380d9a99fcf3d188c61afe"},
]
[package.extras]
dev = ["black", "coveralls", "mypy", "pre-commit", "pylint", "pytest (>=5)", "pytest-benchmark", "pytest-cov"]
[[package]] [[package]]
name = "decorator" name = "decorator"
version = "5.1.1" version = "5.1.1"
@@ -1047,6 +1060,24 @@ files = [
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
] ]
[[package]]
name = "quantities"
version = "0.16.0"
description = "Support for physical quantities with units, based on numpy"
optional = false
python-versions = ">=3.8"
files = [
{file = "quantities-0.16.0-py3-none-any.whl", hash = "sha256:189e573953e7864d8c303a3472f6ad39fbe0698c3d75c17059b70bc457c7c66d"},
{file = "quantities-0.16.0.tar.gz", hash = "sha256:211cce2d268da7e202abab5c2533ce3200ff619dd8ac2a3cd98f861b8a57c6eb"},
]
[package.dependencies]
numpy = ">=1.20"
[package.extras]
doc = ["sphinx"]
test = ["pytest", "wheel"]
[[package]] [[package]]
name = "rich" name = "rich"
version = "13.9.2" version = "13.9.2"
@@ -1235,4 +1266,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.11, <3.13" python-versions = ">=3.11, <3.13"
content-hash = "4dc01682622b8aa364feedb15edecf3d3a9bffc9979e92bbbac118a43d0bbae8" content-hash = "31433ed1dc0cc83dd7e781546dad5b8f068d59eaf692b4d3c12e07c604b1a6dd"

View File

@@ -34,6 +34,8 @@ pyqtgraph = "^0.13.7"
pytest = "^8.3.3" pytest = "^8.3.3"
pglive = "^0.7.6" pglive = "^0.7.6"
pyyaml = "^6.0.2" pyyaml = "^6.0.2"
dacite = "^1.8.1"
quantities = "^0.16.0"
[tool.poetry.scripts] [tool.poetry.scripts]
pyrelacs = "pyrelacs.app:main" pyrelacs = "pyrelacs.app:main"

View File

@@ -2,9 +2,10 @@ import sys
from PyQt6.QtCore import QSettings from PyQt6.QtCore import QSettings
from PyQt6.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
from IPython import embed
from pyrelacs import info from pyrelacs import info
from pyrelacs.config.config_loader import Config from pyrelacs.config.config_loader import load_config
from pyrelacs.ui.mainwindow import PyRelacs from pyrelacs.ui.mainwindow import PyRelacs
from pyrelacs import ( from pyrelacs import (
@@ -28,10 +29,9 @@ def main():
# load the config # load the config
config = Config() config = load_config()
# start the app # start the app
window = PyRelacs() window = PyRelacs(config)
window.setMinimumWidth(200) window.setMinimumWidth(200)
window.setMinimumHeight(200) window.setMinimumHeight(200)
window.resize(width, height) window.resize(width, height)

View File

@@ -1,71 +1,78 @@
Settings: settings:
Repros: [Calibration, Sinus] # If true daq should be used, else starts without daq
Path: ~/projects/pyrelacs/test/ daq: True
# class names of the repros to run
repros: [Calibration, Sinus]
path: ~/projects/pyrelacs/test/
# string to append after the file name
postappend: ehpys
Metadata: metadata:
Identifier (Name): Setup1 SetupName : Setup1
Maintainer : Your name Maintainer : Your name
Creator : Whoever Creator : Whoever
Location (Room) : virtual SetupLocation : virtual
Lab : XYZ-Lab Lab : XYZ-Lab
Institute : Your institute Institute : Your institute
University : Your university University : Your university
Address : Your institute's address Address : Your institute's address
PyRelacs: pyrelacs:
Data: data:
input: input:
inputsamplerate : 20kHz inputsamplerate : 20
inputtracecapacity : 600s # Unit is rescaled to Hz
inputunipolar : false inputsamplerateunit : kHz
inputtraceid : [ V-1, EOD, LocalEOD-1, GlobalEFieldStimulus ] # BufferSize
inputtracescale : [ 100, 1, 10, 1 ] inputtracecapacity : 600
inputtraceunit : [ mV, mV, mV, mV ] # Unit is rescaled to s
inputtracedevice : [ ai-1, ai-1, ai-1, ai-1 ] inputtracecapacityunit : s
inputtracechannel : [ 0, 2, 4, 6 ] inputunipolar : false
inputtracereference: [ ground, ground, ground, ground ] inputtraceid : [ V-1, EOD, LocalEOD-1, GlobalEFieldStimulus ]
inputtracemaxvalue : [ 100, 2, 2, 10 ] inputtracescale : [ 100, 1, 10, 1 ]
inputtracecenter : [ true, false, false, false ] inputtraceunit : [ mV, mV, mV, mV ]
inputtracedevice : [ ai-1, ai-1, ai-1, ai-1 ]
inputtracechannel : [ 0, 2, 4, 6 ]
inputtracereference : [ ground, ground, ground, ground ]
inputtracemaxvalue : [ 100, 2, 2, 10 ]
inputtracecenter : [ true, false, false, false ]
output: output:
outputtraceid : [ GlobalEField, GlobalEFieldAM, LocalEField, I ] outputtraceid : [ GlobalEField, GlobalEFieldAM, LocalEField, I ]
outputtracedevice : [ ao-1, ao-1, ao-1, ao-1 ] outputtracedevice : [ ao-1, ao-1, ao-1, ao-1 ]
outputtracechannel : [ 0, 1, 2, 3 ] outputtracechannel : [ 0, 1, 2, 3 ]
outputtracescale : [ 1, 1, 1, 1 ] outputtracescale : [ 1, 1, 1, 1 ]
outputtraceunit : [ V, V, V, V ] outputtraceunit : [ V, V, V, V ]
outputtracemaxrate : [ 40kHz, 40kHz, 40kHz, 40kHz ] outputtracemaxrate : [ 40, 40, 40, 40]
outputtracemodality: [ electric, electric, electric, current ] outputtracemaxrateunit : [kHz, kHz, kHz, kHz]
outputtracemodality : [ electric, electric, electric, current ]
SimData: sinus:
Sinus: inputsamplerate : 20
input: inputsamplerateunit : kHz
inputsamplerate : 20kHz # BufferSize
inputtracecapacity : 600s inputtracecapacity : 600
output: inputtracecapacityunit : s
outputtraceid : [ Sinus ] outputtraceid : [ Sinus ]
outputtracedevice : [ ao-0 ] outputtracedevice : [ ao-0 ]
outputtracechannel : [ 0 ] outputtracechannel : [ 0 ]
outputtracescale : [ 1 ] outputtracescale : [ 1 ]
outputtraceunit : [ V ] outputtraceunit : [ V ]
outputtracemaxrate : [ 40kHz ] outputtracemaxrate : [ 40 ]
outputtracemodality: [ electric ] outputtracemaxrateunit : [kHz]
outputtracemodality : [ electric ]
Devices: devices:
DAQFlexCore: DAQFlexCore:
AnalogOutputPins: [0,1] analogoutputpins : [0, 1]
AnalogInputPinsHigh: [0,1,2,3,4,5,6,7] analoginputpinshigh : [0, 1, 2, 3,4,5,6,7]
AnalogInputPinsLow: [1,2] analoginputpinslow : [1,2]
DigitalPins: [0,1,2,3] digitalpins : [0,1,2,3]
CS3310DIO: CS3310DIO:
ident : attdev-1 ident : attdev-1
strobepin : 6 strobepin : 6
datainpin : 5 datainpin : 5
dataoutpin: -1 dataoutpin : -1
cspin : 4 cspin : 4
mutepin : 7 mutepin : 7
zcenpin : -1 zcenpin : -1

View File

@@ -1,7 +1,11 @@
from typing import TypedDict, Union
from dataclasses import dataclass from dataclasses import dataclass
import pathlib import pathlib
import dacite
import yaml import yaml
from dacite import from_dict
from IPython import embed from IPython import embed
from pyrelacs.util.logging import config_logging from pyrelacs.util.logging import config_logging
@@ -10,17 +14,119 @@ log = config_logging()
@dataclass @dataclass
class Config: class ValueUnit:
# Path: str value: int
unit: str
@classmethod
def load_config(cls): @dataclass
pyrelacs_config_path = pathlib.Path(__file__).parent / "config.yaml" class Settings:
log.debug(pyrelacs_config_path) daq: bool
if not pyrelacs_config_path.is_file(): repros: list[str]
log.error("Config File was not found") path: str
with open(pyrelacs_config_path, "r") as config_file:
@dataclass
class Metadata:
SetupName: str
Maintainer: str
Creator: str
SetupLocation: str
Lab: str
Institute: str
University: str
Address: str
@dataclass
class Input:
inputsamplerate: int
inputsamplerateunit: str
# BufferSize
inputtracecapacity: int
inputtracecapacityunit: str
inputunipolar: bool
inputtraceid: list[str]
inputtracescale: list[int]
inputtraceunit: list[str]
inputtracedevice: list[str]
inputtracechannel: list[int]
inputtracereference: list[str]
inputtracemaxvalue: list[int]
inputtracecenter: list[bool]
@dataclass
class Output:
outputtraceid: list[str]
outputtracedevice: list[str]
outputtracechannel: list[int]
outputtracescale: list[int]
outputtraceunit: list[str]
outputtracemaxrate: list[int]
outputtracemaxrateunit: list[str]
outputtracemodality: list[str]
@dataclass
class Data:
input: Input
output: Output
@dataclass
class Sinus:
inputsamplerate: int
inputsamplerateunit: str
# BufferSize
inputtracecapacity: int
inputtracecapacityunit: str
outputtraceid: list[str]
outputtracedevice: list[str]
outputtracechannel: list[int]
outputtracescale: list[int]
outputtraceunit: list[str]
outputtracemaxrate: list[int]
outputtracemaxrateunit: list[str]
outputtracemodality: list[str]
@dataclass
class PyRelacs:
data: Data
sinus: Sinus
@dataclass
class Config:
settings: Settings
metadata: Metadata
pyrelacs: PyRelacs
def load_config():
pyrelacs_config_path = pathlib.Path(__file__).parent.parent / "config.yaml"
log.debug(pyrelacs_config_path)
if not pyrelacs_config_path.is_file():
log.error("Config File was not found")
with open(pyrelacs_config_path, "r") as config_file:
try:
data = yaml.full_load(config_file)
try: try:
return yaml.full_load(config_file) config = from_dict(data_class=Config, data=data)
except yaml.YAMLError as e: return config
raise yaml.YAMLError(f"Error parsing YAML file: {e}") except dacite.DaciteError as e:
log.error(f"Invalid Config, {e}")
except yaml.YAMLError as e:
raise yaml.YAMLError(f"Error parsing YAML file: {e}")
if __name__ == "__main__":
pyrelacs_config_path = pathlib.Path(__file__).parent.parent / "config.yaml"
log.debug(pyrelacs_config_path)
if not pyrelacs_config_path.is_file():
log.error("Config File was not found")
with open(pyrelacs_config_path, "r") as config_file:
data = yaml.full_load(config_file)
embed()
exit()

View File

@@ -9,7 +9,7 @@ class CircBuffer:
self, self,
size: int, size: int,
channels: int = 1, channels: int = 1,
samplerate: int = 40_000, samplerate: float = 40_000.0,
mutex: QMutex = QMutex(), mutex: QMutex = QMutex(),
): ):
self.__size = size self.__size = size

View File

@@ -21,7 +21,6 @@ class DaqProducer:
self.device = device self.device = device
self.ai_device = self.device.get_ai_device() self.ai_device = self.device.get_ai_device()
self.channels = channels self.channels = channels
self.stop = False self.stop = False
def read_analog_continously( def read_analog_continously(
@@ -41,7 +40,7 @@ class DaqProducer:
) )
# let the buffer for the daq device hold 5 seconds of data # let the buffer for the daq device hold 5 seconds of data
daq_buffer_size = self.buffer.samplerate * 5 daq_buffer_size = int(self.buffer.samplerate * 30)
data_in = uldaq.create_float_buffer(channel_range.size, daq_buffer_size) data_in = uldaq.create_float_buffer(channel_range.size, daq_buffer_size)
log.debug(f"Buffersize for daq {len(data_in)}") log.debug(f"Buffersize for daq {len(data_in)}")
@@ -70,7 +69,12 @@ class DaqProducer:
prev_count = 0 prev_count = 0
prev_index = 0 prev_index = 0
while not self.stop: while not self.stop:
daq_status, transfer_status = self.ai_device.get_scan_status() try:
daq_status, transfer_status = self.ai_device.get_scan_status()
except uldaq.ul_exception.ULException as e:
log.error("Could not get the scan status")
log.error("Stopping writting")
break
# The index into the data buffer immediately following the last sample transferred. # The index into the data buffer immediately following the last sample transferred.
current_index = transfer_status.current_index current_index = transfer_status.current_index
# total samples since start of the scan # total samples since start of the scan
@@ -79,7 +83,7 @@ class DaqProducer:
channel_samples = transfer_status.current_scan_count channel_samples = transfer_status.current_scan_count
new_data_count = total_samples - prev_count new_data_count = total_samples - prev_count
# check if counts if new data is bigger than the buffer # check if new data is bigger than the buffer
# if that happends stop the acquisition # if that happends stop the acquisition
if new_data_count > len(data_in): if new_data_count > len(data_in):
self.ai_device.scan_stop() self.ai_device.scan_stop()
@@ -92,23 +96,54 @@ class DaqProducer:
if prev_index + chunk_size > len(data_in) - 1: if prev_index + chunk_size > len(data_in) - 1:
log.debug("Chunk wraps around buffersize") log.debug("Chunk wraps around buffersize")
first_chunk = len(data_in) - prev_index first_chunk = len(data_in) - prev_index
data_first_channel = data_in[
prev_index : prev_index + first_chunk : 2
]
data_second_channel = data_in[
prev_index + 1 : prev_index + first_chunk : 2
]
[ [
self.buffer.append(data_in[prev_index + i]) self.buffer.append(data_first_channel[i], channel=0)
for i in range(first_chunk) for i in range(int(first_chunk / 2))
]
[
self.buffer.append(data_second_channel[i], channel=1)
for i in range(int(first_chunk / 2))
] ]
second_chunk = chunk_size - first_chunk second_chunk = chunk_size - first_chunk
data_first_channel = data_in[0:second_chunk:2]
data_second_channel = data_in[1:second_chunk:2]
[ [
self.buffer.append(data_in[i]) self.buffer.append(data_first_channel[i], channel=0)
for i in range(second_chunk) for i in range(int(second_chunk / 2))
]
[
self.buffer.append(data_second_channel[i], channel=1)
for i in range(int(second_chunk / 2))
] ]
else: else:
log.debug("Writing chunk to buffer") log.debug("Writing chunk to buffer")
# appending to the first channel
data_first_channel = data_in[
prev_index : prev_index + chunk_size : 2
]
data_second_channel = data_in[
prev_index + 1 : prev_index + chunk_size : 2
]
[ [
self.buffer.append(data_in[prev_index + i]) self.buffer.append(data_first_channel[i], channel=0)
for i in range(chunk_size) for i in range(int(chunk_size / 2))
] ]
self.buffer.append(data_in[current_index]) [
self.buffer.append(data_second_channel[i], channel=1)
for i in range(int(chunk_size / 2))
]
if total_samples - prev_count > len(data_in): if total_samples - prev_count > len(data_in):
self.ai_device.scan_stop() self.ai_device.scan_stop()
@@ -124,33 +159,70 @@ class DaqProducer:
self.ai_device.scan_stop() self.ai_device.scan_stop()
daq_status, transfer_status = self.ai_device.get_scan_status() daq_status, transfer_status = self.ai_device.get_scan_status()
current_index = transfer_status.current_index
log.debug(daq_status) log.debug(daq_status)
chunk_size = transfer_status.current_total_count - prev_count
log.debug(transfer_status.current_index) log.debug(f"DAQ current index {transfer_status.current_index}")
log.debug(transfer_status.current_total_count) log.debug(f"DAQ total count {transfer_status.current_total_count}")
log.debug(transfer_status.current_scan_count) log.debug(
log.debug(self.buffer.totalcount()) f"DAQ Samples per channel {transfer_status.current_scan_count}"
)
log.debug("Appending last chunk") log.debug("Appending last chunk")
if prev_index + chunk_size > len(data_in) - 1: if prev_index + chunk_size > len(data_in) - 1:
log.debug("Chunk wraps around buffersize") log.debug("Chunk wraps around buffersize")
first_chunk = len(data_in) - prev_index first_chunk = len(data_in) - prev_index
[
self.buffer.append(data_in[prev_index + i]) data_first_channel = data_in[
for i in range(first_chunk) prev_index : prev_index + first_chunk : 2
] ]
second_chunk = chunk_size - first_chunk data_second_channel = data_in[
[self.buffer.append(data_in[i]) for i in range(second_chunk)] prev_index + 1 : prev_index + first_chunk : 2
else:
log.debug("Writing chunk to buffer")
[
self.buffer.append(data_in[prev_index + i])
for i in range(chunk_size)
] ]
self.buffer.append(data_in[current_index]) [
self.buffer.append(data_first_channel[i], channel=0)
for i in range(int(first_chunk / 2))
]
[
self.buffer.append(data_second_channel[i], channel=1)
for i in range(int(first_chunk / 2))
]
second_chunk = chunk_size - first_chunk
data_first_channel = data_in[0:second_chunk:2]
data_second_channel = data_in[1:second_chunk:2]
[
self.buffer.append(data_first_channel[i], channel=0)
for i in range(int(second_chunk / 2))
]
[
self.buffer.append(data_second_channel[i], channel=1)
for i in range(int(second_chunk / 2))
]
else:
log.debug("Writing chunk to buffer")
# appending to the first channel
data_first_channel = data_in[
prev_index : prev_index + chunk_size : 2
]
data_second_channel = data_in[
prev_index + 1 : prev_index + chunk_size : 2
]
[
self.buffer.append(data_first_channel[i], channel=0)
for i in range(int(chunk_size / 2))
]
[
self.buffer.append(data_second_channel[i], channel=1)
for i in range(int(chunk_size / 2))
]
log.info("stopping") log.info("stopping")
log.debug(self.buffer.totalcount())
log.debug(self.ai_device.get_scan_status())
break break
break break

View File

@@ -1,4 +1,11 @@
from datetime import datetime
import itertools
import string
import time
from dataclasses import asdict
from IPython import embed from IPython import embed
from PyQt6.QtCore import QMutex
import nixio import nixio
from pyrelacs.dataio.circbuffer import CircBuffer from pyrelacs.dataio.circbuffer import CircBuffer
@@ -8,34 +15,95 @@ log = config_logging()
class NixWriter: class NixWriter:
def __init__(self, buffer: CircBuffer) -> None: def __init__(self, buffer: CircBuffer, config) -> None:
self.buffer = buffer self.buffer = buffer
self.config = config
def write_nix(self, *args, **kwargs): def write_nix(
self._write_header() self,
items = 0 data_array: nixio.DataArray,
chunk = 1000 mutex: QMutex,
channel: int = 0,
chunk_size=1000,
*args,
**kwargs,
):
index = 0
log.debug("Starting the writing") log.debug("Starting the writing")
self.write = True self.write = True
while self.write: while self.write:
log.debug(items) total_count = self.buffer.totalcount(channel=channel)
try: if total_count - index >= chunk_size:
data, _ = self.buffer.read(items, extend=chunk) mutex.lock()
self.data_array.append(data) log.debug(index)
except IndexError as e: try:
log.debug(f"{e}") _, data = self.buffer.read(
index, extend=chunk_size, channel=channel
)
if index == 0:
data_array.write_direct(data)
else:
data_array.append(data)
index += chunk_size
except IndexError as e:
time.sleep(0.001)
log.debug(f"{e}")
mutex.unlock()
else:
time.sleep(0.001)
continue continue
items += chunk total_count = self.buffer.totalcount(channel=channel)
try:
mutex.lock()
_, data = self.buffer.read(
index, extend=total_count - index, channel=channel
)
data_array.append(data)
mutex.unlock()
index += total_count - index
except IndexError as e:
log.error(f"Could not read the last samples, {e}")
log.debug("Stoppint the writing") log.debug("Stoppint the writing")
log.debug(f"Samples written {items}") log.debug(f"Samples written {index}")
self.nix_file.close()
def _write_header(self): def _write_header(self):
"""
Writing the header of the nix file
"""
self.nix_file = nixio.File.open(path="data.nix", mode=nixio.FileMode.Overwrite) self.nix_file = nixio.File.open(path="data.nix", mode=nixio.FileMode.Overwrite)
self.block = self.nix_file.create_block("recording", "testfile") self.block = self.nix_file.create_block("recording", "testfile")
self.data_array = self.block.create_data_array( self.data_array = self.block.create_data_array(
"Analog1", "ndarray", shape=(1000,), dtype=nixio.DataType.Double "Analog1", "ndarray", shape=(1000,), dtype=nixio.DataType.Double
) )
def generate_letter_sequence(self):
alphabet = string.ascii_lowercase
for size in range(2, 3):
for combo in itertools.product(alphabet, repeat=size):
yield "".join(combo)
def create_nix_file(self, file_path, metadata):
data_time = datetime.now().strftime("%Y-%m-%t_%H-%M-%S")
sequence_generator = self.generate_letter_sequence()
sequence = next(sequence_generator)
self.nix_file = nixio.File.open(
path=f"{file_path}/{data_time}_{sequence}.nix",
mode=nixio.FileMode.Overwrite,
)
self.block = self.nix_file.create_block("recording", "testfile")
self.section = self.nix_file.create_section("metadata", "config.yaml")
for key, value in asdict(metadata).items():
self.section[key] = value
self.data_array_analog1 = self.block.create_data_array(
"Analog1", "ndarray", shape=(1000,), dtype=nixio.DataType.Double
)
self.data_array_analog2 = self.block.create_data_array(
"Analog2", "ndarray", shape=(1000,), dtype=nixio.DataType.Double
)
return self.data_array_analog1, self.data_array_analog2
def stop_writing(self): def stop_writing(self):
self.write = False self.write = False

View File

@@ -8,7 +8,6 @@ from pyrelacs.util.logging import config_logging
log = config_logging() log = config_logging()
# stopbutton: QAction
class SinProducer: class SinProducer:
def __init__( def __init__(
self, self,
@@ -39,12 +38,7 @@ class SinProducer:
log.debug(f"duration sinus {end_time-start_time}") log.debug(f"duration sinus {end_time-start_time}")
log.debug(f"Stimulation time {t}") log.debug(f"Stimulation time {t}")
log.debug(f"{self.buffer.totalcount()}") log.debug(f"Total samples produced {self.buffer.totalcount()}")
# data = self.buffer.get_all()
# log.debug(data.shape[0])
# log.debug(data.shape[0] / self.buffer.samplerate)
# plt.plot(np.arange(data.size) / self.buffer.samplerate, data)
# plt.show()
def stop_request(self): def stop_request(self):
self.stop = True self.stop = True

View File

@@ -41,13 +41,16 @@ class MccDaq:
try: try:
self.daq_device.connect() self.daq_device.connect()
except uldaq.ul_exception.ULException: except uldaq.ul_exception.ULException:
self.disconnect_dac() self.disconnect_daq()
self.connect_dac() self.connect_dac()
self.ai_device = self.daq_device.get_ai_device() self.ai_device = self.daq_device.get_ai_device()
self.ao_device = self.daq_device.get_ao_device() self.ao_device = self.daq_device.get_ao_device()
self.dio_device = self.daq_device.get_dio_device() self.dio_device = self.daq_device.get_dio_device()
log.debug("Connected")
log.debug("Connected to MccDaq")
log.debug("Activating the Attenuator")
self.activate_attenuator()
def connect_dac(self): def connect_dac(self):
""" """
@@ -194,7 +197,7 @@ class MccDaq:
except Exception as e: except Exception as e:
print(f"{e}") print(f"{e}")
self.set_analog_to_zero() self.set_analog_to_zero()
self.disconnect_dac() self.disconnect_daq()
return data_analog_output return data_analog_output
@@ -221,10 +224,10 @@ class MccDaq:
uldaq.AOutListFlag.DEFAULT, uldaq.AOutListFlag.DEFAULT,
[0, 0], [0, 0],
) )
except Exception as e: except Exception as er:
log.error("f{e}") log.error(f"{er}")
log.error("disconnection dac") log.error("disconnection dac")
self.disconnect_dac() # self.disconnect_daq()
def digital_trigger(self, ch: int = 0) -> None: def digital_trigger(self, ch: int = 0) -> None:
""" """
@@ -281,7 +284,9 @@ class MccDaq:
bit = self.dio_device.d_bit_in(uldaq.DigitalPortType.AUXPORT, channel) bit = self.dio_device.d_bit_in(uldaq.DigitalPortType.AUXPORT, channel)
return bit return bit
def disconnect_dac(self): def disconnect_daq(self):
log.debug("Disconnecting DAQ")
self.deactivate_attenuator()
self.daq_device.disconnect() self.daq_device.disconnect()
self.daq_device.release() self.daq_device.release()
@@ -329,12 +334,12 @@ class MccDaq:
except uldaq.ul_exception.ULException: except uldaq.ul_exception.ULException:
log.debug("Operation timed out") log.debug("Operation timed out")
self.write_bit(channel=0, bit=0) self.write_bit(channel=0, bit=0)
self.disconnect_dac() self.disconnect_daq()
self.connect_dac() self.connect_dac()
self.set_analog_to_zero() self.set_analog_to_zero()
finally: finally:
self.write_bit(channel=0, bit=0) self.write_bit(channel=0, bit=0)
self.disconnect_dac() self.disconnect_daq()
self.connect_dac() self.connect_dac()
self.set_analog_to_zero() self.set_analog_to_zero()

View File

@@ -6,6 +6,8 @@ import uldaq
from IPython import embed from IPython import embed
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from scipy.signal import welch, find_peaks
import pyqtgraph as pg
from pyrelacs.devices.mccdaq import MccDaq from pyrelacs.devices.mccdaq import MccDaq
from pyrelacs.util.logging import config_logging from pyrelacs.util.logging import config_logging
@@ -15,40 +17,50 @@ log = config_logging()
faulthandler.enable() faulthandler.enable()
class Calibration(MccDaq): class Calibration:
def __init__(self) -> None: def __init__(self, config, mccdaq: MccDaq) -> None:
super().__init__() self.config = config
self.mccdaq = mccdaq
self.SAMPLERATE = 40_000.0 self.SAMPLERATE = 40_000.0
self.DURATION = 5 self.DURATION = 5
self.AMPLITUDE = 1 self.AMPLITUDE = 1
self.SINFREQ = 750 self.SINFREQ = 750
@staticmethod @staticmethod
def run(nix_file: nix.File): def run(*args, **kwargs):
calb = Calibration() nix_block = args[0]
calb.check_beat(nix_file) figure = args[1]
mccdaq = args[2]
config = args[3]
calb = Calibration(config, mccdaq)
calb.check_beat(nix_block)
calb.plot(figure, nix_block)
return "finished"
def check_amplitude(self): def check_amplitude(self):
db_values = [0.0, -5.0, -10.0, -20.0, -50.0] db_values = [0.0, -5.0, -10.0, -20.0, -50.0]
colors = ["red", "green", "blue", "black", "yellow"] colors = ["red", "green", "blue", "black", "yellow"]
self.set_attenuation_level(db_channel1=0.0, db_channel2=0.0) self.mccdaq.set_attenuation_level(db_channel1=0.0, db_channel2=0.0)
# write to ananlog 1 # write to ananlog 1
t = np.arange(0, self.DURATION, 1 / self.SAMPLERATE) t = np.arange(0, self.DURATION, 1 / self.SAMPLERATE)
data = self.AMPLITUDE * np.sin(2 * np.pi * self.SINFREQ * t) data = self.AMPLITUDE * np.sin(2 * np.pi * self.SINFREQ * t)
fig, ax = plt.subplots() fig, ax = plt.subplots()
for i, db_value in enumerate(db_values): for i, db_value in enumerate(db_values):
self.set_attenuation_level(db_channel1=db_value, db_channel2=db_value) self.mccdaq.set_attenuation_level(
db_channel1=db_value, db_channel2=db_value
)
log.debug(f"{db_value}") log.debug(f"{db_value}")
stim = self.write_analog( stim = self.mccdaq.write_analog(
data, data,
[0, 0], [0, 0],
self.SAMPLERATE, self.SAMPLERATE,
ScanOption=uldaq.ScanOption.EXTTRIGGER, ScanOption=uldaq.ScanOption.EXTTRIGGER,
) )
data_channel_one = self.read_analog( data_channel_one = self.mccdaq.read_analog(
[0, 0], [0, 0],
self.DURATION, self.DURATION,
self.SAMPLERATE, self.SAMPLERATE,
@@ -57,21 +69,21 @@ class Calibration(MccDaq):
time.sleep(1) time.sleep(1)
log.debug("Starting the Scan") log.debug("Starting the Scan")
self.digital_trigger() self.mccdaq.digital_trigger()
try: try:
self.ao_device.scan_wait(uldaq.WaitType.WAIT_UNTIL_DONE, 15) self.mccdaq.ao_device.scan_wait(uldaq.WaitType.WAIT_UNTIL_DONE, 15)
log.debug("Scan finished") log.debug("Scan finished")
self.write_bit(channel=0, bit=0) self.mccdaq.write_bit(channel=0, bit=0)
time.sleep(1) time.sleep(1)
self.set_analog_to_zero() self.mccdaq.set_analog_to_zero()
except uldaq.ul_exception.ULException: except uldaq.ul_exception.ULException:
log.debug("Operation timed out") log.debug("Operation timed out")
# reset the diggital trigger # reset the diggital trigger
self.write_bit(channel=0, bit=0) self.mccdaq.write_bit(channel=0, bit=0)
time.sleep(1) time.sleep(1)
self.set_analog_to_zero() self.mccdaq.set_analog_to_zero()
self.disconnect_dac() # self.mccdaq.disconnect_daq()
if i == 0: if i == 0:
ax.plot(t, stim, label=f"Input_{db_value}", color=colors[i]) ax.plot(t, stim, label=f"Input_{db_value}", color=colors[i])
@@ -80,34 +92,33 @@ class Calibration(MccDaq):
ax.legend() ax.legend()
plt.show() plt.show()
self.disconnect_dac() # self.mccdaq.disconnect_daq()
def check_beat(self, nix_file: nix.File): def check_beat(self, nix_block: nix.Block):
self.set_attenuation_level(db_channel1=-10.0, db_channel2=0.0) self.mccdaq.set_attenuation_level(db_channel1=-10.0, db_channel2=0.0)
t = np.arange(0, self.DURATION, 1 / self.SAMPLERATE) t = np.arange(0, self.DURATION, 1 / self.SAMPLERATE)
data = self.AMPLITUDE * np.sin(2 * np.pi * self.SINFREQ * t) data = self.AMPLITUDE * np.sin(2 * np.pi * self.SINFREQ * t)
# data = np.concatenate((data, data)) # data = np.concatenate((data, data))
db_values = [0.0, -5.0, -8.5, -10.0] db_values = [0.0, -5.0, -8.5, -10.0]
colors = ["red", "blue", "black", "green"] colors = ["red", "blue", "black", "green"]
colors_in = ["lightcoral", "lightblue", "grey", "lightgreen"] colors_in = ["lightcoral", "lightblue", "grey", "lightgreen"]
block = nix_file.create_block("Calibration", "data")
# fig, axes = plt.subplots(2, 2, sharex="col") # fig, axes = plt.subplots(2, 2, sharex="col")
for i, db_value in enumerate(db_values): for i, db_value in enumerate(db_values):
self.set_attenuation_level(db_channel1=db_value) self.mccdaq.set_attenuation_level(db_channel1=db_value)
stim = self.write_analog( stim = self.mccdaq.write_analog(
data, data,
[0, 0], [0, 0],
self.SAMPLERATE, self.SAMPLERATE,
ScanOption=uldaq.ScanOption.EXTTRIGGER, ScanOption=uldaq.ScanOption.EXTTRIGGER,
) )
readout = self.read_analog( readout = self.mccdaq.read_analog(
[0, 1], [0, 1],
self.DURATION, self.DURATION,
self.SAMPLERATE, self.SAMPLERATE,
ScanOption=uldaq.ScanOption.EXTTRIGGER, ScanOption=uldaq.ScanOption.EXTTRIGGER,
) )
self.digital_trigger() self.mccdaq.digital_trigger()
log.info(self.ao_device) log.info(self.mccdaq.ao_device)
ai_status = uldaq.ScanStatus.RUNNING ai_status = uldaq.ScanStatus.RUNNING
ao_status = uldaq.ScanStatus.RUNNING ao_status = uldaq.ScanStatus.RUNNING
@@ -119,10 +130,10 @@ class Calibration(MccDaq):
): ):
# log.debug("Scanning") # log.debug("Scanning")
time.time_ns() time.time_ns()
ai_status = self.ai_device.get_scan_status()[0] ai_status = self.mccdaq.ai_device.get_scan_status()[0]
ao_status = self.ao_device.get_scan_status()[0] ao_status = self.mccdaq.ao_device.get_scan_status()[0]
self.write_bit(channel=0, bit=0) self.mccdaq.write_bit(channel=0, bit=0)
log.debug( log.debug(
f"Status Analog_output {ao_status}\n, Status Analog_input {ai_status}" f"Status Analog_output {ao_status}\n, Status Analog_input {ai_status}"
) )
@@ -130,7 +141,7 @@ class Calibration(MccDaq):
channel1 = np.array(readout[::2]) channel1 = np.array(readout[::2])
channel2 = np.array(readout[1::2]) channel2 = np.array(readout[1::2])
stim_data = block.create_data_array( stim_data = nix_block.create_data_array(
f"stimulus_{db_value}", f"stimulus_{db_value}",
"nix.regular_sampled", "nix.regular_sampled",
shape=data.shape, shape=data.shape,
@@ -143,7 +154,7 @@ class Calibration(MccDaq):
label="time", label="time",
unit="s", unit="s",
) )
fish_data = block.create_data_array( fish_data = nix_block.create_data_array(
f"fish_{db_value}", f"fish_{db_value}",
"Array", "Array",
shape=data.shape, shape=data.shape,
@@ -157,7 +168,72 @@ class Calibration(MccDaq):
unit="s", unit="s",
) )
self.set_analog_to_zero() time.time_ns()
self.mccdaq.set_analog_to_zero()
def plot(self, figure, block):
self.figure = figure
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)
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 = 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 = 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 = 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(power, ref_power=1.0, min_power=1e-20): def decibel(power, ref_power=1.0, min_power=1e-20):

View File

View File

@@ -22,9 +22,7 @@ class Repro:
def __init__(self) -> None: def __init__(self) -> None:
pass pass
def run_repro( def run_repro(self, name: str, file: pathlib.Path, *args, **kwargs) -> None:
self, nix_file: nix.File, name: str, file: pathlib.Path, *args, **kwargs
) -> None:
spec = importlib.util.spec_from_file_location("rep", file) spec = importlib.util.spec_from_file_location("rep", file)
if not spec: if not spec:
log.error("Could not load the file") log.error("Could not load the file")
@@ -40,14 +38,19 @@ class Repro:
log.error(f"{spec.loader} is None") log.error(f"{spec.loader} is None")
if hasattr(module, name): if hasattr(module, name):
rep_class = getattr(module, name) rep_class = getattr(module, name)
rep_class.run(nix_file) rep_class.run(*args, **kwargs)
else: else:
raise AttributeError(f"{file.name} has no {name} class") raise AttributeError(f"{file.name} has no {name} class")
def names_of_repros(self) -> Tuple[list, list]: def names_of_repros(self, include_repros: list[str]) -> Tuple[list, list]:
""" """
Searches for class names in the repro folder in all python files Searches for class names in the repro folder in all python files
Parameters
----------
include_repros : list[str]
List of repros to include in the pyrelacs instance
Returns Returns
------- -------
Tuple[list, list] Tuple[list, list]
@@ -73,4 +76,7 @@ class Repro:
repro_names.extend(class_name) repro_names.extend(class_name)
file_names.append(python_file) file_names.append(python_file)
file.close() file.close()
repro_names = [r for r in repro_names if r in include_repros]
file_names = [f for r, f in zip(repro_names, file_names) if r in include_repros]
return repro_names, file_names return repro_names, file_names

View File

@@ -0,0 +1,17 @@
import nixio
from pyrelacs.util.logging import config_logging
log = config_logging()
class Sinus:
def __init__(self) -> None:
pass
@staticmethod
def run(config, mccdaq, nix_block: nixio.Block, figure) -> None:
log.debug(config)
log.debug(mccdaq)
log.debug(nix_block)
log.debug(figure)

View File

@@ -1,10 +1,14 @@
import time import time
from pathlib import Path as path from pathlib import Path as path
from datetime import datetime
from dataclasses import asdict
from PyQt6.QtGui import QAction, QIcon, QKeySequence from PyQt6.QtGui import QAction, QIcon, QKeySequence
from PyQt6.QtCore import Qt, QSize, QThreadPool, QMutex, QTimer from PyQt6.QtCore import Qt, QSize, QThreadPool, QMutex
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QGridLayout, QGridLayout,
QPushButton,
QTabWidget,
QToolBar, QToolBar,
QWidget, QWidget,
QMainWindow, QMainWindow,
@@ -13,18 +17,20 @@ from PyQt6.QtWidgets import (
QStatusBar, QStatusBar,
) )
import uldaq
import nixio as nix import nixio
import pyqtgraph as pg import pyqtgraph as pg
import numpy as np import quantities as pq
from pyrelacs.devices.mccdaq import MccDaq
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.dataio.daq_producer import DaqProducer from pyrelacs.dataio.daq_producer import DaqProducer
from pyrelacs.dataio.nix_writer import NixWriter from pyrelacs.dataio.nix_writer import NixWriter
from pyrelacs.dataio.sin_producer import SinProducer from pyrelacs.dataio.sin_producer import SinProducer
from pyrelacs.worker import Worker from pyrelacs.worker import Worker
from pyrelacs.repros.repros import Repro from pyrelacs.repros.repros import Repro
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.ui.about import AboutDialog from pyrelacs.ui.about import AboutDialog
from pyrelacs.ui.plots.calibration import CalibrationPlot from pyrelacs.ui.plots.calibration import CalibrationPlot
@@ -39,71 +45,81 @@ from IPython import embed
class PyRelacs(QMainWindow): class PyRelacs(QMainWindow):
def __init__(self): def __init__(self, config):
super().__init__() super().__init__()
# loaded config
self.config = config
# check if daq is connencted else None
if self.config.settings.daq:
start = time.time()
self.mccdaq = MccDaq()
end = time.time()
log.debug(f"Connection to DAQ took {end - start}")
else:
self.mccdaq = None
self.repros = Repro()
self.setToolButtonStyle( self.setToolButtonStyle(
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.mutex = QMutex() self.mutex = QMutex()
self.timer = QTimer(self)
self.timer.setInterval(200)
self.figure = pg.GraphicsLayoutWidget() 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)
# filename = path.joinpath(path.cwd(), "calibration.nix")
# self.nix_file = nix.File.open(str(filename), nix.FileMode.Overwrite)
#
# self.calibration_plot = CalibrationPlot(self.figure, self.nix_file)
# self.nix_file.close()
self.threadpool = QThreadPool() self.threadpool = QThreadPool()
self.repros = Repro()
self.text = QPlainTextEdit() self.text = QPlainTextEdit()
self.text.setReadOnly(True) self.text.setReadOnly(True)
self.setMenuBar(QMenuBar(self)) SAMPLERATE = pq.Quantity(
self.setStatusBar(QStatusBar(self)) self.config.pyrelacs.data.input.inputsamplerate,
self.create_actions() self.config.pyrelacs.data.input.inputsamplerateunit,
self.create_toolbars() ).rescale("Hz")
layout = QGridLayout() INPUTTRACECAPACITY = pq.Quantity(
layout.addWidget(self.figure, 0, 0, 2, 2) self.config.pyrelacs.data.input.inputtracecapacity,
layout.addWidget(self.text, 2, 0, 1, 2) self.config.pyrelacs.data.input.inputtracecapacityunit,
).rescale("s")
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
SAMPLERATE = 40_000
start = time.time() start = time.time()
BUFFERSIZE = SAMPLERATE * 10 * 60 BUFFERSIZE = (SAMPLERATE * INPUTTRACECAPACITY).simplified
end = time.time() end = time.time()
log.debug(f"Buffer allocation took {end - start}") log.debug(f"Buffer allocation took {end - start}")
self.buffer = CircBuffer( self.buffer = CircBuffer(
size=BUFFERSIZE, samplerate=SAMPLERATE, mutex=self.mutex size=int(BUFFERSIZE.base),
samplerate=float(SAMPLERATE.base),
channels=2,
mutex=self.mutex,
) )
self.continously_plot = Continously(self.figure, self.buffer) self.continously_plot = Continously(self.figure, self.buffer)
self.continously_plot.plot() # self.continously_plot.plot()
start = time.time() if self.mccdaq:
self.connect_dac()
end = time.time()
log.debug(f"Connection to DAQ took {end - start}")
if hasattr(PyRelacs, "daq_device"):
log.debug("Creating Daq Generator") log.debug("Creating Daq Generator")
self.daq_producer = DaqProducer(self.buffer, self.daq_device, [1, 1]) self.daq_producer = DaqProducer(self.buffer, self.mccdaq.daq_device, [4, 5])
else: log.debug("Creating Sinus Generator")
log.debug("Creating Sinus Generator") self.sinus_producer = SinProducer(self.buffer)
self.sinus_producer = SinProducer(self.buffer)
self.nix_writer = NixWriter(self.buffer) self.nix_writer = NixWriter(self.buffer, self.config)
self.setMenuBar(QMenuBar(self))
self.setStatusBar(QStatusBar(self))
self.create_actions()
self.create_toolbars()
self.repro_tabs = QTabWidget()
self.create_repros_tabs()
layout = QGridLayout()
layout.addWidget(self.figure, 0, 0, 2, 2)
layout.addWidget(self.repro_tabs, 2, 0, 2, 2)
layout.addWidget(self.text, 4, 0, 1, 1)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
def create_actions(self): def create_actions(self):
self._rlx_exitaction = QAction(QIcon(":/icons/exit.png"), "Exit", self) self._rlx_exitaction = QAction(QIcon(":/icons/exit.png"), "Exit", self)
@@ -119,16 +135,17 @@ class PyRelacs(QMainWindow):
self._daq_connectaction = QAction( self._daq_connectaction = QAction(
QIcon(":icons/connect.png"), "Connect DAQ", self QIcon(":icons/connect.png"), "Connect DAQ", self
) )
self._daq_connectaction.setStatusTip("Connect to daq device") if self.mccdaq:
# self._daq_connectaction.setShortcut(QKeySequence("Alt+d")) self._daq_connectaction.setStatusTip("Connect to daq device")
self._daq_connectaction.triggered.connect(self.connect_dac) # self._daq_connectaction.setShortcut(QKeySequence("Alt+d"))
self._daq_connectaction.triggered.connect(self.mccdaq.connect_dac)
self._daq_disconnectaction = QAction( self._daq_disconnectaction = QAction(
QIcon(":/icons/disconnect.png"), "Disconnect DAQ", self QIcon(":/icons/disconnect.png"), "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.mccdaq.disconnect_daq)
# self._daq_calibaction = QAction( # self._daq_calibaction = QAction(
# QIcon(":/icons/calibration.png"), "Plot calibration", self # QIcon(":/icons/calibration.png"), "Plot calibration", self
@@ -137,7 +154,7 @@ class PyRelacs(QMainWindow):
# # self._daq_calibaction.setShortcut(QKeySequence("Alt+d")) # # self._daq_calibaction.setShortcut(QKeySequence("Alt+d"))
# self._daq_calibaction.triggered.connect(self.calibration_plot.plot) # self._daq_calibaction.triggered.connect(self.calibration_plot.plot)
self._run_action = QAction(QIcon(":/icons/record.png"), "Run", self) self._run_action = QAction(QIcon(":/icons/record.png"), "RunDAQ", self)
self._run_action.triggered.connect(self.run_daq) self._run_action.triggered.connect(self.run_daq)
self._run_sinus_action = QAction(QIcon(":/icons/record.png"), "Sinus", self) self._run_sinus_action = QAction(QIcon(":/icons/record.png"), "Sinus", self)
@@ -167,8 +184,9 @@ class PyRelacs(QMainWindow):
file_menu.addAction(self._rlx_aboutaction) file_menu.addAction(self._rlx_aboutaction)
if device_menu is not None: if device_menu is not None:
device_menu.addAction(self._daq_connectaction) if self.config.settings.daq:
device_menu.addAction(self._daq_disconnectaction) device_menu.addAction(self._daq_connectaction)
device_menu.addAction(self._daq_disconnectaction)
device_menu.addSeparator() device_menu.addSeparator()
# device_menu.addAction(self._daq_calibaction) # device_menu.addAction(self._daq_calibaction)
device_menu.addAction(self._run_action) device_menu.addAction(self._run_action)
@@ -189,8 +207,9 @@ class PyRelacs(QMainWindow):
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, rlx_toolbar) self.addToolBar(Qt.ToolBarArea.TopToolBarArea, rlx_toolbar)
daq_toolbar = QToolBar("DAQ") daq_toolbar = QToolBar("DAQ")
daq_toolbar.addAction(self._daq_connectaction) if self.config.settings.daq:
daq_toolbar.addAction(self._daq_disconnectaction) daq_toolbar.addAction(self._daq_connectaction)
daq_toolbar.addAction(self._daq_disconnectaction)
# daq_toolbar.addAction(self._daq_calibaction) # daq_toolbar.addAction(self._daq_calibaction)
daq_toolbar.addAction(self._run_action) daq_toolbar.addAction(self._run_action)
daq_toolbar.addAction(self._run_sinus_action) daq_toolbar.addAction(self._run_sinus_action)
@@ -200,16 +219,53 @@ class PyRelacs(QMainWindow):
daq_toolbar.addAction(self._record) daq_toolbar.addAction(self._record)
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, daq_toolbar) self.addToolBar(Qt.ToolBarArea.TopToolBarArea, daq_toolbar)
repro_toolbar = QToolBar("Repros") def create_repros_tabs(self):
repro_names, file_names = self.repros.names_of_repros() repro_names, file_names = self.repros.names_of_repros(
include_repros=self.config.settings.repros
)
figures_repros = {rep: pg.GraphicsLayoutWidget() for rep in repro_names}
for rep, fn in zip(repro_names, file_names): for rep, fn in zip(repro_names, file_names):
repro_action = QAction(rep, self) tab = QWidget()
repro_action.setStatusTip(rep) tab_layout = QGridLayout()
repro_action.triggered.connect(
lambda checked, n=rep, f=fn: self.run_repro(n, f) run_repro_button = QPushButton(f"Run {rep}")
run_repro_button.setCheckable(True)
run_repro_button.clicked.connect(
lambda checked, n=rep, f=fn: self.run_repro(
n,
f,
figures_repros,
self.mccdaq,
self.config,
)
) )
repro_toolbar.addAction(repro_action) tab_layout.addWidget(run_repro_button, 0, 0, 1, 0)
self.addToolBar(Qt.ToolBarArea.BottomToolBarArea, repro_toolbar) tab_layout.addWidget(figures_repros[rep], 1, 0, 1, 1)
tab.setLayout(tab_layout)
self.repro_tabs.addTab(tab, f"{rep}")
def run_repro(
self,
name_of_repro: str,
file_of_repro: str,
figures,
*args,
):
self.text.appendPlainText(f"started Repro {name_of_repro}, {file_of_repro}")
figure_repro = figures[name_of_repro]
worker = Worker(
self.repros.run_repro,
name_of_repro,
file_of_repro,
figure_repro,
*args[-1:],
)
worker.signals.result.connect(self.print_output)
worker.signals.finished.connect(self.thread_complete)
worker.signals.progress.connect(self.progress_fn)
self.threadpool.start(worker)
def recenter_continously_plot(self): def recenter_continously_plot(self):
self.continously_plot.refresh() self.continously_plot.refresh()
@@ -240,12 +296,35 @@ class PyRelacs(QMainWindow):
self.continously_plot.plot() self.continously_plot.plot()
def record(self): def record(self):
nix_writer = Worker(self.nix_writer.write_nix) self.data_array_analog1, self.data_array_analog2 = (
self.nix_writer.create_nix_file(f"{_root}/test/", self.config.metadata)
)
log.debug("Created nix file")
nix_writer = Worker(
self.nix_writer.write_nix,
data_array=self.data_array_analog1,
mutex=self.mutex,
channel=0,
chunk_size=1000,
)
nix_writer.signals.result.connect(self.print_output) nix_writer.signals.result.connect(self.print_output)
nix_writer.signals.finished.connect(self.thread_complete) nix_writer.signals.finished.connect(self.thread_complete)
nix_writer.signals.progress.connect(self.progress_fn) nix_writer.signals.progress.connect(self.progress_fn)
self.threadpool.start(nix_writer) self.threadpool.start(nix_writer)
nix_writer2 = Worker(
self.nix_writer.write_nix,
data_array=self.data_array_analog2,
mutex=self.mutex,
channel=0,
chunk_size=1000,
)
nix_writer2.signals.result.connect(self.print_output)
nix_writer2.signals.finished.connect(self.thread_complete)
nix_writer2.signals.progress.connect(self.progress_fn)
self.threadpool.start(nix_writer2)
def stop_recording(self): def stop_recording(self):
self.add_to_textfield("Stopping the recording") self.add_to_textfield("Stopping the recording")
self.continously_plot.stop_plotting() self.continously_plot.stop_plotting()
@@ -258,37 +337,10 @@ class PyRelacs(QMainWindow):
except AttributeError: except AttributeError:
log.debug("Did not generate Sinus") log.debug("Did not generate Sinus")
if hasattr(PyRelacs, "daq_device"): if self.config.settings.daq:
log.debug("Stopping DAQ") log.debug("Stopping DAQ")
self.daq_producer.stop_aquisition() self.daq_producer.stop_aquisition()
def connect_dac(self):
devices = uldaq.get_daq_device_inventory(uldaq.InterfaceType.USB)
try:
self.daq_device = uldaq.DaqDevice(devices[0])
log.debug(f"Found daq devices {len(devices)}, connecting to the first one")
self.daq_device.connect()
log.debug("connected")
except IndexError:
log.info("DAQ is not connected")
log.info("Please connect a DAQ device to the system")
def disconnect_dac(self):
try:
self.daq_device.disconnect()
self.daq_device.release()
except AttributeError:
log.debug("DAQ was not connected")
def run_repro(self, n, fn):
self.text.appendPlainText(f"started Repro {n}, {fn}")
worker = Worker(self.repros.run_repro, self.nix_calibration, n, fn)
worker.signals.result.connect(self.print_output)
worker.signals.finished.connect(self.thread_complete)
worker.signals.progress.connect(self.progress_fn)
self.threadpool.start(worker)
def add_to_textfield(self, s: str): def add_to_textfield(self, s: str):
self.text.appendPlainText(s) self.text.appendPlainText(s)
@@ -296,7 +348,8 @@ class PyRelacs(QMainWindow):
log.info("exit button!") log.info("exit button!")
self.stop_recording() self.stop_recording()
self.add_to_textfield("exiting") self.add_to_textfield("exiting")
self.disconnect_dac() if self.mccdaq:
self.mccdaq.disconnect_daq()
log.info("closing GUI") log.info("closing GUI")
self.close() self.close()

View File

@@ -1,3 +1,5 @@
import time
import pyqtgraph as pg import pyqtgraph as pg
from IPython import embed from IPython import embed
import numpy as np import numpy as np
@@ -13,6 +15,9 @@ class Continously:
def __init__(self, figure: pg.GraphicsLayoutWidget, buffer: CircBuffer): def __init__(self, figure: pg.GraphicsLayoutWidget, buffer: CircBuffer):
self.figure = figure self.figure = figure
self.buffer = buffer self.buffer = buffer
self.last_plotted_index_analog_in_0 = 0
self.last_plotted_index_analog_in_1 = 0
self.timer = QTimer()
def plot(self, *args, **kwargs): def plot(self, *args, **kwargs):
self.figure.setBackground("w") self.figure.setBackground("w")
@@ -20,56 +25,118 @@ class Continously:
prev_plot = self.figure.getItem(row=0, col=0) prev_plot = self.figure.getItem(row=0, col=0)
if prev_plot: if prev_plot:
self.figure.removeItem(prev_plot) self.figure.removeItem(prev_plot)
self.continous_ax = self.figure.addPlot(row=0, col=0) self.analog_in_0 = self.figure.addPlot(row=0, col=0)
self.analog_in_1 = self.figure.addPlot(row=1, col=0)
pen = pg.mkPen("red") pen = pg.mkPen("red")
self.time = np.zeros(self.buffer.size) self.time_analog_in_0 = np.zeros(self.buffer.size)
self.data = np.zeros(self.buffer.size) self.data_analog_in_0 = np.empty(self.buffer.size)
self.line = self.continous_ax.plot( self.line_analog_in_0 = self.analog_in_0.plot(
self.time, self.time_analog_in_0,
self.data, self.data_analog_in_0,
pen=pen,
# symbol="o",
)
pen = pg.mkPen("red")
self.time_analog_in_1 = np.zeros(self.buffer.size)
self.data_analog_in_1 = np.empty(self.buffer.size)
self.line_analog_in_1 = self.analog_in_1.plot(
self.time_analog_in_1,
self.data_analog_in_1,
pen=pen, pen=pen,
# symbol="o", # symbol="o",
) )
# self.plot_index = 0 # self.plot_index = 0
self.timer = QTimer() self.CHUNK_PLOT = int(self.buffer.samplerate / 6)
self.CHUNK_PLOT = 10_000 self.PLOT_HISTORY = 500_000 # The amount of data you want to keep on screen
self.timer.setInterval(200) self.timer.setInterval(150)
self.timer.timeout.connect(self.update_plot) self.timer.timeout.connect(self.update_plot)
self.timer.timeout.connect(self.update_plot_1)
self.timer.start() self.timer.start()
def update_plot(self): def update_plot(self):
# log.debug(self.buffer.totalcount()) current_index = self.buffer.write_index()
if self.buffer.totalcount() > self.CHUNK_PLOT: total_count = self.buffer.totalcount(channel=0)
log.debug(self.buffer.totalcount())
start_time = time.time()
if total_count - self.last_plotted_index_analog_in_0 >= self.CHUNK_PLOT:
try: try:
times, items = self.buffer.read( times, items = self.buffer.read(
self.buffer.write_index() - self.CHUNK_PLOT - 1_000, self.last_plotted_index_analog_in_0,
extend=self.CHUNK_PLOT, extend=self.CHUNK_PLOT,
channel=0,
) )
except IndexError as e: self.time_analog_in_0 = np.concatenate((self.time_analog_in_0, times))[
items = np.zeros(self.CHUNK_PLOT) -self.PLOT_HISTORY :
times = np.zeros(self.CHUNK_PLOT) ]
log.debug("No Data Available") self.data_analog_in_0 = np.concatenate((self.data_analog_in_0, items))[
log.debug(f"Index Error {e}") -self.PLOT_HISTORY :
]
self.line_analog_in_0.setData(
self.time_analog_in_0,
self.data_analog_in_0,
)
self.last_plotted_index_analog_in_0 += self.CHUNK_PLOT
except IndexError:
log.error("Could not acces the data from the buffer for plotting")
end_time = time.time()
log.debug(f"total time for plotting {end_time - start_time}")
else:
pass
# self.time = np.roll(self.time, -len(items)) def update_plot_1(self):
# self.data = np.roll(self.data, -len(items)) total_count = self.buffer.totalcount(channel=1)
#
# self.time[-len(times) :] = times
# self.data[-len(items) :] = items
self.line.setData( start_time = time.time()
times, if total_count - self.last_plotted_index_analog_in_1 >= self.CHUNK_PLOT:
items, try:
) times, items = self.buffer.read(
# self.plot_index += len(items) self.last_plotted_index_analog_in_1,
extend=self.CHUNK_PLOT,
channel=1,
)
self.time_analog_in_1 = np.concatenate((self.time_analog_in_0, times))[
-self.PLOT_HISTORY :
]
self.data_analog_in_1 = np.concatenate((self.data_analog_in_0, items))[
-self.PLOT_HISTORY :
]
self.line_analog_in_1.setData(
self.time_analog_in_1,
self.data_analog_in_1,
)
self.last_plotted_index_analog_in_1 += self.CHUNK_PLOT
except IndexError:
log.error("Could not acces the data from the buffer for plotting")
end_time = time.time()
log.debug(f"total time for plotting {end_time - start_time}")
else: else:
pass pass
def stop_plotting(self): def stop_plotting(self):
self.timer.stop() self.timer.stop()
if self.last_plotted_index_analog_in_0 > 0:
total_count = self.buffer.totalcount()
times, items = self.buffer.read(
self.last_plotted_index_analog_in_0,
extend=total_count - self.last_plotted_index_analog_in_0,
)
self.time_analog_in_0 = np.concatenate((self.time_analog_in_0, times))[
-self.PLOT_HISTORY :
]
self.data_analog_in_0 = np.concatenate((self.data_analog_in_0, items))[
-self.PLOT_HISTORY :
]
self.line_analog_in_0.setData(
self.time_analog_in_0,
self.data_analog_in_0,
)
self.last_plotted_index_analog_in_0 += (
total_count - self.last_plotted_index_analog_in_0
)
def refresh(self): def refresh(self):
self.continous_ax.enableAutoRange() self.analog_in_0.enableAutoRange()
self.analog_in_1.enableAutoRange()