37 Commits

Author SHA1 Message Date
wendtalexander
25d16cc5fd [ui] removing unessassery stuff 2024-10-10 09:59:42 +02:00
wendtalexander
523f5dc346 [ui/plot] rewriting continous plot, plotting chucks of the buffer 2024-10-10 09:58:54 +02:00
wendtalexander
b6bd9e23a0 [dataio/sinus] commenting debugging 2024-10-10 09:57:46 +02:00
wendtalexander
e100dac5ea [dataio/buffer] adding time for both channels, reading range alows for overflows reads 2024-10-10 09:57:03 +02:00
wendtalexander
22b899e723 [ui] adding stop button 2024-10-09 17:15:58 +02:00
wendtalexander
3ca48d11fe [ui/plot] plotting chuncks of the data 2024-10-09 17:15:33 +02:00
wendtalexander
32c79ff47b [dataio] adding stop button 2024-10-09 17:14:56 +02:00
wendtalexander
e3ed301252 [ui/plot] adding plot, but plots after while loop is finished 2024-10-09 08:58:18 +02:00
wendtalexander
0b36e31135 [ui] changing samplerate 2024-10-09 08:02:57 +02:00
wendtalexander
b7d92259a9 [producer] adding more time 2024-10-09 07:58:56 +02:00
wendtalexander
8378add874 [time] adding time to the buffer and retruning it with the item 2024-10-09 07:58:41 +02:00
wendtalexander
aaa42db2ae [dataio/test] adding a sin producer for testing 2024-10-07 13:51:46 +02:00
wendtalexander
52a0821601 [project] adding pglive 2024-10-04 15:33:27 +02:00
wendtalexander
5ec5cc8644 [ui] adding pglive, trying to plot continious data 2024-10-04 15:32:57 +02:00
wendtalexander
bf72f90009 [ui/plots] moving continous plot outside 2024-10-04 15:32:16 +02:00
wendtalexander
949767c45e [refactoring] spelling 2024-10-04 15:31:51 +02:00
wendtalexander
b251ee13c4 [refactoring] spelling 2024-10-04 15:31:41 +02:00
wendtalexander
cd936b1ed1 [ui] updating plot for continously plotting 2024-10-04 11:50:05 +02:00
wendtalexander
85fd70f8ca [dataio] adding chunking for reading daq input 2024-10-04 11:49:33 +02:00
wendtalexander
8910305262 [dataio] adding samplerate to circular buffer 2024-10-04 11:48:59 +02:00
wendtalexander
e43bb16bd4 [ui] adding plot that reads after pressing run the input of the daqproducer 2024-10-03 14:06:23 +02:00
wendtalexander
e555573f09 [daq] adding a producer that reads from one channel and saves it to the buffer 2024-10-03 14:05:41 +02:00
wendtalexander
4d53a0f51d [buffer] adding samplerate 2024-10-03 14:05:02 +02:00
wendtalexander
d127750e7b [buffer] removing logging, adding samplerate, adding check for getting the item at current index 2024-10-03 08:52:25 +02:00
wendtalexander
b18f870a6b [buffer] adding True for fixing test 2024-10-02 15:55:00 +02:00
wendtalexander
0b067df69c [project] adding pytest 2024-10-02 15:52:41 +02:00
wendtalexander
e3ed2fcc75 [test] adding testcases for get method of buffer 2024-10-02 15:52:29 +02:00
wendtalexander
ff84d63fe1 [buffer] adding edge cases 2024-10-02 15:51:57 +02:00
19c6b90d5c [test] add buffer test stub 2024-10-02 10:28:50 +02:00
a7b62c5b3a [ringbuffer] first implementation 2024-10-02 10:28:36 +02:00
wendtalexander
54f0d61fc9 [dataio] commenting out databuffer 2024-10-02 08:10:08 +02:00
wendtalexander
815838eab7 [dataio] adding class for buffer 2024-10-02 08:09:45 +02:00
wendtalexander
314e609472 [refactoring] removing imports 2024-10-01 18:16:33 +02:00
wendtalexander
2719d49eb0 [refacorting] adding DataBuffer 2024-10-01 12:04:47 +02:00
wendtalexander
a0c524326d [refactroring] renaming class 2024-10-01 12:04:06 +02:00
wendtalexander
c2285f3750 [project] adding buffer class 2024-10-01 12:03:41 +02:00
wendtalexander
9327d1bac9 [refactoring] renaming file 2024-10-01 12:03:25 +02:00
11 changed files with 844 additions and 37 deletions

96
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "asttokens"
@@ -153,6 +153,20 @@ files = [
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "executing"
version = "2.1.0"
@@ -270,6 +284,17 @@ files = [
[package.dependencies]
numpy = ">=1.19.3"
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "ipython"
version = "8.27.0"
@@ -284,6 +309,7 @@ files = [
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
decorator = "*"
exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
jedi = ">=0.16"
matplotlib-inline = "*"
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""}
@@ -291,6 +317,7 @@ prompt-toolkit = ">=3.0.41,<3.1.0"
pygments = ">=2.4.0"
stack-data = "*"
traitlets = ">=5.13.0"
typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""}
[package.extras]
all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"]
@@ -661,6 +688,21 @@ files = [
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
name = "pglive"
version = "0.7.6"
description = "Live plot for PyqtGraph"
optional = false
python-versions = "<3.13,>=3.9"
files = [
{file = "pglive-0.7.6-py3-none-any.whl", hash = "sha256:6203e377954725d6602ba5a08055f92bef1688d2236c76cf4e1b35c9bb896ff4"},
{file = "pglive-0.7.6.tar.gz", hash = "sha256:5e3a91a0bb800a8c9c8513a595d742eb0aba5b8b783a6f1bbb51ab7b0d2528ec"},
]
[package.dependencies]
numpy = ">=1.26.0,<2.0.0"
pyqtgraph = ">=0.13.3,<0.14.0"
[[package]]
name = "pillow"
version = "10.4.0"
@@ -758,6 +800,21 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
typing = ["typing-extensions"]
xmp = ["defusedxml"]
[[package]]
name = "pluggy"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "prompt-toolkit"
version = "3.0.48"
@@ -903,6 +960,28 @@ files = [
[package.dependencies]
numpy = ">=1.22.0"
[[package]]
name = "pytest"
version = "8.3.3"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.5,<2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@@ -1026,6 +1105,17 @@ pure-eval = "*"
[package.extras]
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "tomlkit"
version = "0.13.2"
@@ -1104,5 +1194,5 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "b1076b7f750e8f7e66542918ec746e74544fbfdb376875158f083fb573d18107"
python-versions = ">=3.10, <3.13"
content-hash = "e5e345e3e4d28ac0e7de7727be130454201be1949be08158e744cdc6089a3f66"

View File

@@ -21,7 +21,7 @@ include = [
]
[tool.poetry.dependencies]
python = "^3.10"
python = ">=3.10, <3.13"
uldaq = "^1.2.3"
typer = "^0.12.5"
matplotlib = "^3.9.2"
@@ -31,6 +31,8 @@ tomlkit = "^0.13.2"
scipy = "^1.14.1"
nixio = "^1.5.3"
pyqtgraph = "^0.13.7"
pytest = "^8.3.3"
pglive = "^0.7.6"
[tool.poetry.scripts]
pyrelacs = "pyrelacs.app:main"

81
pyrelacs/dataio/buffer.py Normal file
View File

@@ -0,0 +1,81 @@
import time
import faulthandler
from collections import deque
from pyqtgraph import transformToArray
import uldaq
import numpy as np
from IPython import embed
import matplotlib.pyplot as plt
from pyrelacs.util.logging import config_logging
log = config_logging()
faulthandler.enable()
class DataBuffer:
def __init__(self, channels, samples):
self.channels = channels
self.samples = samples
def read_analog_continously(
self,
device: uldaq.DaqDevice,
samplerate: float = 40_000.0,
):
data_array = []
max_len_buffer = self.channels * self.samples
self.buffer = deque(maxlen=max_len_buffer)
samples_per_channel = 40_000
self.device = device
self.ai_device = self.device.get_ai_device()
data_analog_input = uldaq.create_float_buffer(
self.channels, samples_per_channel
)
er = self.ai_device.a_in_scan(
0,
1,
uldaq.AiInputMode.SINGLE_ENDED,
uldaq.Range.BIP10VOLTS,
samples_per_channel,
samplerate,
uldaq.ScanOption.CONTINUOUS,
uldaq.AInScanFlag.DEFAULT,
data=data_analog_input,
)
daq_status = uldaq.ScanStatus.IDLE
while daq_status == uldaq.ScanStatus.IDLE:
daq_status = self.ai_device.get_scan_status()[0]
prev_count = 0
prev_index = 0
while daq_status != uldaq.ScanStatus.IDLE:
daq_status, transfer_status = self.ai_device.get_scan_status()
# The index into the data buffer immediately following the last sample transferred.
curren_index = transfer_status.current_index
# total samples since start of the scan
total_samples = transfer_status.current_total_count
# The number of samples per channel transferred since the scan started
channel_samples = transfer_status.current_scan_count
self.ai_device.scan_stop()
if __name__ == "__main__":
devices = uldaq.get_daq_device_inventory(uldaq.InterfaceType.USB)
log.debug(f"Found daq devices {len(devices)}, connecting to the first one")
try:
daq_device = uldaq.DaqDevice(devices[0])
except uldaq.ul_exception.ULException as e:
log.error("Did not found daq devices, please connect one")
raise e
daq_device.connect()
buf = DataBuffer(channels=2, samples=100_000)
buf.read_analog_continously(daq_device)

View File

@@ -0,0 +1,183 @@
from typing import Tuple
import numpy as np
from IPython import embed
class CircBuffer:
def __init__(self, size: int, channels: int = 1, samplerate: int = 40_000):
self.__size = size
self.__channels = channels
self.__samplereate = samplerate
self.__buffer = np.zeros(
(channels, size), dtype=np.double
) # or dtype of your choice
self.__time = np.zeros((channels, size), dtype=np.double)
self.__index = [0 for i in range(channels)]
self.__is_full = [False for i in range(channels)]
self.__totalcount = [0 for i in range(channels)]
self.__overflows = [0 for i in range(channels)]
self.__read_increment = samplerate * 0.1
@property
def size(self):
return self.__size
@property
def samplerate(self):
return self.__samplereate
@property
def channel_count(self):
return self.__channels
def totalcount(self, channel: int = 0):
return self.__totalcount[channel]
def is_full(self, channel: int = 0):
return self.__is_full[channel]
def write_index(self, channel: int = 0):
return self.__index[channel]
def append(self, item, channel: int = 0):
self.__buffer[channel, self.write_index(channel)] = item
self.__index[channel] = (self.write_index(channel) + 1) % self.__size
self.__totalcount[channel] += 1
self.__time[channel, self.write_index(channel)] = (
self.__time[channel, self.write_index(channel) - 1] + 1 / self.__samplereate
)
if self.__index[channel] == 0:
self.__is_full[channel] = True
self.__overflows[channel] += 1
def get_all(self, channel: int = 0):
"""
Return all valid values from the specified channel
"""
if self.__is_full[channel]:
return np.concatenate(
(
self.__buffer[channel, self.__index[channel] :],
self.__buffer[channel, : self.__index[channel]],
)
)
else:
return self.__buffer[channel, : self.__index[channel]]
def has_value(self, index, channel):
if index <= 0 and self.is_full(channel):
return True
elif index < 0 and not self.is_full(channel):
return False
if index >= self.size:
return False
# test if the ring buffer is at the start but
# and the index is greater than the write index
if index > self.write_index(channel) and self.is_full(channel):
return True
elif index >= self.write_index(channel) and not self.is_full(channel):
raise IndexError("Index has no value, not written")
if index == self.write_index(channel) and self.__totalcount[channel] == 0:
return False
return True
def valid_range(self, channel: int = 0) -> Tuple[int, int]:
"""
Return the start index and the extend that are valid within the buffer
Parameters
----------
channel : int
channel of the buffer
Returns
-------
Tuple[int, int]
start, extend of the valid range
"""
start = 0
extend = 0
if self.__totalcount[channel] == 0:
return start, extend
if not self.is_full(channel):
extend = self.__totalcount[channel]
else:
extend = self.size
return start, extend
def get(self, index: int = -1, channel: int = 0) -> Tuple[np.double, float]:
# easy case first, we can spare the effort of further checking
if index >= 0 and index <= self.write_index(channel):
if self.has_value(index, channel):
return (self.__buffer[channel, index], self.__time[index])
else:
raise IndexError(
f"Invalid index {index} on ring buffer for channel{channel}"
)
if index < 0:
index = self.write_index() - 1
if self.has_value(index, channel):
return (self.__buffer[channel, index], self.__time[index])
else:
raise IndexError(
f"Invalid index {index} on ring buffer for channel{channel}"
)
def read(self, start, extend=1, channel=0):
"""Reads a numpy array from buffer"""
if extend < 0:
raise IndexError(f"Invalid extend ({extend}) for channel {channel}")
if not self.is_full(channel):
if start < 0:
raise IndexError(f"Invalid start ({start}) for channel {channel}")
else:
if start < 0:
start = start + self.size
if extend == 1:
return np.array(self.get(start, channel))
vs, vc = self.valid_range(channel)
if start > self.__totalcount[channel]:
raise IndexError(
f"Invalid start index {start} is invalid with totalcount {self.__totalcount[channel]} for channel{channel}"
)
if start > self.size:
raise IndexError(
f"Invalid start index {start} for buffer with size {self.size}"
)
if extend > vc:
extend = vc
if (start + extend) > self.__totalcount[channel]:
raise IndexError(
f" Invalid range, extended over the totalcount of the buffer {self.__totalcount[channel]}"
)
if (start + extend) < self.size:
return (
self.__time[channel, start : start + extend],
self.__buffer[channel, start : start + extend],
)
else:
return (
np.concatenate(
(
self.__time[channel, start:],
self.__time[channel, : extend - self.size + start],
)
),
np.concatenate(
(
self.__buffer[channel, start:],
self.__buffer[channel, : extend - self.size + start],
)
),
)

View File

@@ -0,0 +1,170 @@
import time
import faulthandler
import uldaq
import numpy as np
from IPython import embed
import matplotlib.pyplot as plt
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.util.logging import config_logging
log = config_logging()
faulthandler.enable()
class DaqProducer:
def __init__(
self, buffer: CircBuffer, device: uldaq.DaqDevice, channels: list[int]
):
self.buffer = buffer
self.device = device
self.ai_device = self.device.get_ai_device()
self.channels = channels
def read_analog_continously(
self,
*args,
**kwargs,
):
log.debug("starting acquisition")
if self.channels[0] == self.channels[1]:
channel_range = np.arange(1)
else:
channel_range = np.arange(self.channels[0], self.channels[1] + 1)
assert channel_range.size == self.buffer.channel_count, ValueError(
f"Missmatch in channel count,\n daq_channel: "
f"{channel_range.size}\n buffer_channel: {self.buffer.channel_count}"
)
# let the buffer for the daq device hold 5 seconds of data
daq_buffer_size = self.buffer.samplerate * 5
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 {self.buffer.size}")
er = self.ai_device.a_in_scan(
self.channels[0],
self.channels[1],
uldaq.AiInputMode.SINGLE_ENDED,
uldaq.Range.BIP10VOLTS,
daq_buffer_size,
self.buffer.samplerate,
uldaq.ScanOption.CONTINUOUS,
uldaq.AInScanFlag.DEFAULT,
data=data_in,
)
chunk_size = int(daq_buffer_size / 10)
wrote_chunk = False
start_time = time.time()
daq_status = uldaq.ScanStatus.IDLE
while daq_status == uldaq.ScanStatus.IDLE:
daq_status = self.ai_device.get_scan_status()[0]
while daq_status != uldaq.ScanStatus.IDLE:
prev_count = 0
prev_index = 0
while time.time() - start_time < 10:
daq_status, transfer_status = self.ai_device.get_scan_status()
# The index into the data buffer immediately following the last sample transferred.
current_index = transfer_status.current_index
# total samples since start of the scan
total_samples = transfer_status.current_total_count
# The number of samples per channel transferred since the scan started
channel_samples = transfer_status.current_scan_count
new_data_count = total_samples - prev_count
# check if counts if new data is bigger than the buffer
# if that happends stop the acquisition
if new_data_count > len(data_in):
self.ai_device.scan_stop()
log.error("A Buffer overrun occurred")
break
if new_data_count > chunk_size:
wrote_chunk = True
# index wraps around the buffer
if prev_index + chunk_size > len(data_in) - 1:
log.debug("Chunk wraps around buffersize")
first_chunk = len(data_in) - prev_index
[
self.buffer.append(data_in[prev_index + i])
for i in range(first_chunk)
]
second_chunk = chunk_size - first_chunk
[
self.buffer.append(data_in[i])
for i in range(second_chunk)
]
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])
if total_samples - prev_count > len(data_in):
self.ai_device.scan_stop()
log.error("A Buffer overrun occurred")
break
else:
wrote_chunk = False
if wrote_chunk:
prev_count += chunk_size
prev_index += chunk_size
prev_index %= daq_buffer_size
self.ai_device.scan_stop()
daq_status, transfer_status = self.ai_device.get_scan_status()
current_index = transfer_status.current_index
log.debug(daq_status)
log.debug(transfer_status.current_index)
log.debug(transfer_status.current_total_count)
log.debug(transfer_status.current_scan_count)
log.debug(self.buffer.totalcount())
log.debug("Appending last chunk")
if prev_index + chunk_size > len(data_in) - 1:
log.debug("Chunk wraps around buffersize")
first_chunk = len(data_in) - prev_index
[
self.buffer.append(data_in[prev_index + i])
for i in range(first_chunk)
]
second_chunk = chunk_size - first_chunk
[self.buffer.append(data_in[i]) for i in range(second_chunk)]
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])
log.info("stopping")
break
break
return "Done. "
if __name__ == "__main__":
devices = uldaq.get_daq_device_inventory(uldaq.InterfaceType.USB)
log.debug(f"Found daq devices {len(devices)}, connecting to the first one")
try:
daq_device = uldaq.DaqDevice(devices[0])
except uldaq.ul_exception.ULException as e:
log.error("Did not found daq devices, please connect one")
raise e
daq_device.connect()
buf = CircBuffer(size=1_000_000, samplerate=100)
producer = DaqProducer(buf, daq_device, [1, 1])
producer.read_analog_continously()

View File

@@ -0,0 +1,58 @@
from math import sin
import time
from PyQt6.QtGui import QAction
import numpy as np
import matplotlib.pyplot as plt
from IPython import embed
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.util.logging import config_logging
log = config_logging()
# stopbutton: QAction
class SinProducer:
def __init__(
self,
buffer: CircBuffer,
) -> None:
self.buffer = buffer
self.stop = False
def produce_sin(
self,
*args,
**kwargs,
) -> None:
AMPLITUDE = 2
FREQUENCY = 10
self.stop = False
log.debug("producing Sin")
start_time = time.time()
t = 0
while not self.stop:
s = AMPLITUDE * np.sin(2 * np.pi * FREQUENCY * t)
self.buffer.append(s)
t += 1 / self.buffer.samplerate
time.sleep(1 / self.buffer.samplerate)
end_time = time.time()
log.debug(f"duration sinus {end_time-start_time}")
log.debug(f"Stimulation time {t}")
# 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):
self.stop = True
if __name__ == "__main__":
buf = CircBuffer(1_000_000, 1, samplerate=10_000)
pro_sin = SinProducer(buf)
pro_sin.produce_sin()

View File

@@ -11,7 +11,7 @@ from pyrelacs.util.logging import config_logging
log = config_logging()
class MccDac:
class MccDaq:
"""
Represents the Digital/Analog Converter from Meassuring Computing.
provides methods for writing and reading the Analog / Digital input and output.
@@ -43,6 +43,7 @@ class MccDac:
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()

View File

@@ -6,10 +6,8 @@ import uldaq
from IPython import embed
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import welch
from scipy.signal import find_peaks
from pyrelacs.devices.mccdac import MccDac
from pyrelacs.devices.mccdaq import MccDaq
from pyrelacs.util.logging import config_logging
log = config_logging()
@@ -17,7 +15,7 @@ log = config_logging()
faulthandler.enable()
class Calibration(MccDac):
class Calibration(MccDaq):
def __init__(self) -> None:
super().__init__()
self.SAMPLERATE = 40_000.0

View File

@@ -1,8 +1,10 @@
import time
from pathlib import Path as path
from PyQt6.QtGui import QAction, QIcon, QKeySequence
from PyQt6.QtCore import Qt, QSize, QThreadPool
from PyQt6.QtWidgets import (
QGridLayout,
QPushButton,
QToolBar,
QWidget,
QMainWindow,
@@ -10,19 +12,24 @@ from PyQt6.QtWidgets import (
QMenuBar,
QStatusBar,
)
import uldaq
import numpy as np
import nixio as nix
import pyqtgraph as pg
import numpy as np
from pathlib import Path as path
from scipy.signal import welch, find_peaks
from pyrelacs.dataio.daq_producer import DaqProducer
from pyrelacs.dataio.sin_producer import SinProducer
from pyrelacs.worker import Worker
from pyrelacs.repros.repros import Repro
from pyrelacs.util.logging import config_logging
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.ui.about import AboutDialog
from pyrelacs.ui.plots.calibration import CalibrationPlot
from pyrelacs.ui.plots.continously import Continously
from pyrelacs.util.logging import config_logging
log = config_logging()
_root = path(__file__).parent.parent
@@ -43,8 +50,8 @@ class PyRelacs(QMainWindow):
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)
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)
@@ -57,7 +64,6 @@ class PyRelacs(QMainWindow):
self.setMenuBar(QMenuBar(self))
self.setStatusBar(QStatusBar(self))
self.create_actions()
self.create_buttons()
self.create_toolbars()
layout = QGridLayout()
@@ -68,6 +74,20 @@ class PyRelacs(QMainWindow):
widget.setLayout(layout)
self.setCentralWidget(widget)
SAMPLERATE = 40_000
start = time.time()
BUFFERSIZE = SAMPLERATE * 10 * 60
end = time.time()
log.debug(f"Buffer allocation took {end - start}")
self.buffer = CircBuffer(size=BUFFERSIZE, samplerate=SAMPLERATE)
# self.connect_dac()
# self.daq_producer = DaqProducer(self.buffer, self.daq_device, [1, 1])
self.sin_producer = SinProducer(self.buffer)
self.continously_plot = Continously(self.figure, self.buffer)
self.continously_plot.plot_daq()
def create_actions(self):
self._rlx_exitaction = QAction(QIcon(":/icons/exit.png"), "Exit", self)
self._rlx_exitaction.setStatusTip("Close relacs")
@@ -99,6 +119,16 @@ class PyRelacs(QMainWindow):
self._daq_calibaction.setStatusTip("Calibrate the attenuator device")
# self._daq_calibaction.setShortcut(QKeySequence("Alt+d"))
self._daq_calibaction.triggered.connect(self.calibration_plot.plot)
self._run_action = QAction(QIcon(":/icons/record.png"), "Run", self)
self._run_action.triggered.connect(self.run_daq)
self._run_sinus_action = QAction(QIcon(":/icons/record.png"), "Sinus", self)
self._run_sinus_action.triggered.connect(self.run_sinus)
self._stop_recording = QAction(QIcon(":/icons/stop.png"), "Stop", self)
self._stop_recording.triggered.connect(self.stop_recording)
self.create_menu()
def create_menu(self):
@@ -117,6 +147,7 @@ class PyRelacs(QMainWindow):
device_menu.addAction(self._daq_disconnectaction)
device_menu.addSeparator()
device_menu.addAction(self._daq_calibaction)
device_menu.addAction(self._run_action)
if help_menu is not None:
help_menu.addSeparator()
@@ -137,6 +168,9 @@ class PyRelacs(QMainWindow):
daq_toolbar.addAction(self._daq_connectaction)
daq_toolbar.addAction(self._daq_disconnectaction)
daq_toolbar.addAction(self._daq_calibaction)
daq_toolbar.addAction(self._run_action)
daq_toolbar.addAction(self._run_sinus_action)
daq_toolbar.addAction(self._stop_recording)
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, daq_toolbar)
repro_toolbar = QToolBar("Repros")
@@ -148,26 +182,57 @@ class PyRelacs(QMainWindow):
lambda checked, n=rep, f=fn: self.run_repro(n, f)
)
repro_toolbar.addAction(repro_action)
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, repro_toolbar)
self.addToolBar(Qt.ToolBarArea.BottomToolBarArea, repro_toolbar)
def create_buttons(self):
self.daq_connect_button = QPushButton("Connect Daq")
self.daq_connect_button.setCheckable(True)
self.daq_connect_button.clicked.connect(self.connect_dac)
def plot_continously(self):
plot_daq = Worker(self.continously_plot.plot_daq)
plot_daq.signals.result.connect(self.print_output)
plot_daq.signals.finished.connect(self.thread_complete)
plot_daq.signals.progress.connect(self.progress_fn)
self.threadpool.start(plot_daq)
self.daq_disconnect_button = QPushButton("Disconnect Daq")
self.daq_disconnect_button.setCheckable(True)
self.daq_disconnect_button.clicked.connect(self.disconnect_dac)
def run_daq(self):
read_daq = Worker(self.daq_producer.read_analog_continously)
read_daq.signals.result.connect(self.print_output)
read_daq.signals.finished.connect(self.thread_complete)
read_daq.signals.progress.connect(self.progress_fn)
# plot_daq = Worker(self.continously_plot.plot_daq)
# plot_daq.signals.result.connect(self.print_output)
# plot_daq.signals.finished.connect(self.thread_complete)
# plot_daq.signals.progress.connect(self.progress_fn)
self.threadpool.start(read_daq)
time.sleep(0.5)
self.continously_plot.plot_daq()
# self.threadpool.start(plot_daq)
self.plot_calibration_button = QPushButton("Plot Calibration")
self.plot_calibration_button.setCheckable(True)
self.plot_calibration_button.clicked.connect(self.calibration_plot.plot)
def run_sinus(self):
sinus_pro = Worker(self.sin_producer.produce_sin)
sinus_pro.signals.result.connect(self.print_output)
sinus_pro.signals.finished.connect(self.thread_complete)
sinus_pro.signals.progress.connect(self.progress_fn)
self.threadpool.start(sinus_pro)
# plot_daq = Worker(self.continously_plot.plot_daq)
# plot_daq.signals.result.connect(self.print_output)
# plot_daq.signals.finished.connect(self.thread_complete)
# plot_daq.signals.progress.connect(self.progress_fn)
# self.threadpool.start(plot_daq)
# self.continously_plot.plot_daq()
def stop_recording(self):
self.add_to_textfield("pressed")
# self._stop_recording.setEnabled(False)
self.sin_producer.stop_request()
self.continously_plot.stop_plotting()
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.error("DAQ is not connected")
log.error("Please connect a DAQ device to the system")
@@ -178,25 +243,17 @@ class PyRelacs(QMainWindow):
log.debug("Connected")
except uldaq.ul_exception.ULException as e:
log.error(f"Could not Connect to DAQ: {e}")
self.daq_connect_button.setDisabled(True)
else:
log.debug("Already handeld the error")
pass
def disconnect_dac(self):
try:
log.debug(f"{self.daq_device}")
self.daq_device.disconnect()
self.daq_device.release()
log.debug(f"{self.daq_device}")
self.daq_disconnect_button.setDisabled(True)
self.daq_connect_button.setEnabled(True)
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_file, 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)
@@ -208,7 +265,9 @@ class PyRelacs(QMainWindow):
def on_exit(self):
log.info("exit button!")
self.stop_recording()
self.add_to_textfield("exiting")
self.disconnect_dac()
self.close()
def on_about(self, e):

View File

@@ -0,0 +1,67 @@
import time
import pyqtgraph as pg
from IPython import embed
import numpy as np
from PyQt6.QtCore import QTimer
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.util.logging import config_logging
log = config_logging()
class Continously:
def __init__(self, figure: pg.GraphicsLayoutWidget, buffer: CircBuffer):
self.figure = figure
self.buffer = buffer
def plot_daq(self, *args, **kwargs):
self.figure.setBackground("w")
self.daq_plot = self.figure.addPlot(row=0, col=0)
pen = pg.mkPen("red")
self.time = np.zeros(self.buffer.size)
self.data = np.zeros(self.buffer.size)
self.line = self.daq_plot.plot(
self.time,
self.data,
pen=pen,
# symbol="o",
)
# self.plot_index = 0
self.CHUNK_PLOT = 1_000
self.timer = QTimer()
self.timer.setInterval(200)
self.timer.timeout.connect(self.update_plot)
self.timer.start()
def update_plot(self):
log.debug(self.buffer.totalcount())
if self.buffer.totalcount() > self.CHUNK_PLOT:
log.debug(self.buffer.totalcount())
try:
times, items = self.buffer.read(
self.buffer.write_index() - self.CHUNK_PLOT, extend=self.CHUNK_PLOT
)
except IndexError as e:
items = np.zeros(self.CHUNK_PLOT)
times = np.zeros(self.CHUNK_PLOT)
log.debug("No Data Available")
log.debug(f"Index Error {e}")
# self.time = np.roll(self.time, -len(items))
# self.data = np.roll(self.data, -len(items))
#
# self.time[-len(times) :] = times
# self.data[-len(items) :] = items
self.line.setData(
times,
items,
)
# self.plot_index += len(items)
else:
pass
def stop_plotting(self):
self.timer.stop()

98
test/test_buffer.py Normal file
View File

@@ -0,0 +1,98 @@
import pytest
import numpy as np
from IPython import embed
from pyrelacs.dataio.circbuffer import CircBuffer
def test_init():
buff = CircBuffer(1000, 2)
assert buff.size == 1000
assert buff.channel_count == 2
def test_hasvalue():
buff = CircBuffer(1000, 2)
assert buff.has_value(0, 0) == False
assert buff.has_value(-1, 0) == False
buff.append(10, 0)
assert buff.write_index(0) == 1
assert buff.write_index(1) == 0
assert buff.has_value(0, 0) == True
assert buff.has_value(0, 1) == False
buff.append(10, 1)
assert buff.write_index(1) == 1
assert buff.has_value(0, 1) == True
for i in range(1100):
buff.append(i, 0)
buff.append(i, 1)
assert buff.write_index(0) == buff.write_index(1)
assert buff.has_value(0, 0) == True
assert buff.has_value(0, 1) == True
assert buff.has_value(buff.write_index(0), 0) == True
assert buff.has_value(buff.write_index(1), 1) == True
def test_validrange():
buff = CircBuffer(1000, 2)
# without any values the range is (0, 0)
assert buff.valid_range() == (0, 0)
buff.append(0, 0)
assert buff.valid_range() == (0, 1)
for i in range(100):
buff.append(i, 0)
assert buff.valid_range() == (0, 101)
for i in range(1000):
buff.append(i, 0)
assert buff.valid_range() == (0, 1000)
def test_get():
buff = CircBuffer(1000, 2)
# with no items written to the buffer
with pytest.raises(IndexError):
item = buff.get(index=-1)
buff.append(10, 0)
item = buff.get(index=-1)
assert item == 10
# Check if index is not written jet
with pytest.raises(IndexError):
item = buff.get(index=10)
for i in range(1000):
buff.append(i, 0)
item = buff.get(index=-1)
# the first item should be 999.0 because of we append a value in the earlier test
assert item == 999.0
with pytest.raises(IndexError):
item = buff.get(10001)
def test_read():
pass
def test_write():
buff = CircBuffer(1000, 2)
samplecount = 1000
if __name__ == "__main__":
test_get()