2
0
forked from awendt/pyrelacs

Compare commits

...

101 Commits
main ... main

Author SHA1 Message Date
8f6c9b1e5e [ui/structure] updating repros to tab bar with individual plots and nix blocks 2024-10-23 11:09:44 +02:00
574e9a8110 [ui/plots] fixing error if nothing has been plotted, the gui could not close 2024-10-23 11:08:54 +02:00
75619cf1c8 [repro/class] provides with repros with args and kwargs 2024-10-23 11:07:55 +02:00
02911e57f8 [repros] moving to sperate folders 2024-10-23 11:07:19 +02:00
557535ffa4 [repros] moving to sperate folder, writing run method 2024-10-23 11:06:51 +02:00
110629dae0 [calibration] moving calibration to own folder, rewriting run method 2024-10-23 11:06:26 +02:00
7c4b5098c1 [mccdaq] printing exception error 2024-10-23 11:05:41 +02:00
e2b7ed3a61 [ui] creates a nix file with pressing the record button 2024-10-22 10:11:07 +02:00
038327bfeb [ui/continously] updating plot to plot the last chunk 2024-10-22 10:10:40 +02:00
4f7ebbe8c3 [dataio/nix] adding mutex, writing data_array, and the last chunk 2024-10-22 10:10:09 +02:00
12e82dceee [dataio] adding time sleep for performance 2024-10-21 17:44:02 +02:00
e4e86cbc49 [ui/plot] updating plot 2024-10-21 10:29:38 +02:00
e36db5e7b0 [dataio/producer] removing comments 2024-10-21 10:28:46 +02:00
33f046c072 [dataio/nix] adding sleep while writing 2024-10-21 10:28:25 +02:00
6d2eb09c65 [ui/plot] changing chunk size of the plot depending on the sampling rate 2024-10-18 16:46:33 +02:00
4029034174 [config] adding bool daq, hints 2024-10-18 16:32:05 +02:00
9fd4892325 [ui/mainwindow] organising file 2024-10-18 16:31:45 +02:00
85b5a71ccb [ui/plots] updating plot 2024-10-18 16:31:22 +02:00
5d62cb0384 [repros] adding example sinuns 2024-10-18 16:31:03 +02:00
64cd1b00ad [repros] updating names of repros 2024-10-18 16:30:46 +02:00
f703687ed7 [repros] renaming file 2024-10-18 16:30:27 +02:00
4e0a1f0ac5 [devices] spelling 2024-10-18 16:30:09 +02:00
4864538213 [dataio/sinus] removing code 2024-10-18 16:29:27 +02:00
28dd0b7080 [dataio/nix] removing log messages 2024-10-18 16:28:53 +02:00
b94078634d [dataio/daq] fixing daq_buffer to int 2024-10-18 16:28:24 +02:00
e3c867f4fd [dataio] changing samplerate from int to float 2024-10-18 16:27:55 +02:00
13897f29e3 [config] adding bool for daq 2024-10-18 16:27:32 +02:00
afc37adb1a [project] adding quantities for unit conversion 2024-10-16 08:52:49 +02:00
b87e7f1ffa [app] adding config 2024-10-16 08:52:27 +02:00
f576f33cc5 [ui/mainwindow] adding unitconversion 2024-10-16 08:52:16 +02:00
e16211b988 [config] adding config loading function 2024-10-16 08:51:59 +02:00
b22ef04317 [project] adding dacite for config loading 2024-10-16 07:57:09 +02:00
d2f7d0e966 [config] fixing config 2024-10-16 07:56:46 +02:00
cc9089f503 [config] fixing config loader 2024-10-16 07:56:32 +02:00
351850e05c [config] remaping values into dict of value and unit 2024-10-15 07:58:02 +02:00
e44e021982 [config] converting config into dataclasses 2024-10-15 07:57:33 +02:00
93fe2951cd [project] adding pyyaml as dependecy 2024-10-14 08:41:57 +02:00
452c3dcecb [config] adding more fields from relacs 2024-10-14 08:41:33 +02:00
e999d25951 [app] adding config 2024-10-14 08:41:12 +02:00
30bd705e1a [dataio/nix] removing embed 2024-10-14 08:41:03 +02:00
38123cdff3 [config] adding dataclass for config 2024-10-14 08:40:52 +02:00
0e0aaf9f04 [project] setting python to 3.11 for tomllib 2024-10-11 14:11:15 +02:00
1332e067c6 [info] using python internal tomllib 2024-10-11 14:10:43 +02:00
f7cfc96dde [config] adding simple config 2024-10-11 14:10:07 +02:00
71837dffc9 [app] removing logging 2024-10-11 14:09:47 +02:00
1b889312ce [ui] adding recording button, which writes to a nix file 2024-10-11 14:09:31 +02:00
042826721b [dataio/sinus] refactoring 2024-10-11 14:08:58 +02:00
7510d04f8d [dataio/nix] adding a simple nix writer 2024-10-11 14:08:35 +02:00
0f09c948ec [dataio/buffer] fixing bug in time buffer 2024-10-11 14:08:20 +02:00
ff2d8ebb50 [ui] adding checks, refactoring 2024-10-10 15:11:40 +02:00
57d3a83243 [ui/plot] refactoring 2024-10-10 15:11:15 +02:00
2539fa7e25 [dataio/daq] adding stop signal from gui, commentin out 2024-10-10 15:10:50 +02:00
b5f8d7663d [ui] updating autorang button 2024-10-10 14:25:50 +02:00
fe3e142452 [ui/plots] removing plotitem if the function gets called again, setting autorang 2024-10-10 14:22:03 +02:00
bbc8def460 [dataio/buffer] setting mutex lock while appending 2024-10-10 14:19:41 +02:00
25d16cc5fd [ui] removing unessassery stuff 2024-10-10 09:59:42 +02:00
523f5dc346 [ui/plot] rewriting continous plot, plotting chucks of the buffer 2024-10-10 09:58:54 +02:00
b6bd9e23a0 [dataio/sinus] commenting debugging 2024-10-10 09:57:46 +02:00
e100dac5ea [dataio/buffer] adding time for both channels, reading range alows for overflows reads 2024-10-10 09:57:03 +02:00
22b899e723 [ui] adding stop button 2024-10-09 17:15:58 +02:00
3ca48d11fe [ui/plot] plotting chuncks of the data 2024-10-09 17:15:33 +02:00
32c79ff47b [dataio] adding stop button 2024-10-09 17:14:56 +02:00
e3ed301252 [ui/plot] adding plot, but plots after while loop is finished 2024-10-09 08:58:18 +02:00
0b36e31135 [ui] changing samplerate 2024-10-09 08:02:57 +02:00
b7d92259a9 [producer] adding more time 2024-10-09 07:58:56 +02:00
8378add874 [time] adding time to the buffer and retruning it with the item 2024-10-09 07:58:41 +02:00
aaa42db2ae [dataio/test] adding a sin producer for testing 2024-10-07 13:51:46 +02:00
52a0821601 [project] adding pglive 2024-10-04 15:33:27 +02:00
5ec5cc8644 [ui] adding pglive, trying to plot continious data 2024-10-04 15:32:57 +02:00
bf72f90009 [ui/plots] moving continous plot outside 2024-10-04 15:32:16 +02:00
949767c45e [refactoring] spelling 2024-10-04 15:31:51 +02:00
b251ee13c4 [refactoring] spelling 2024-10-04 15:31:41 +02:00
cd936b1ed1 [ui] updating plot for continously plotting 2024-10-04 11:50:05 +02:00
85fd70f8ca [dataio] adding chunking for reading daq input 2024-10-04 11:49:33 +02:00
8910305262 [dataio] adding samplerate to circular buffer 2024-10-04 11:48:59 +02:00
e43bb16bd4 [ui] adding plot that reads after pressing run the input of the daqproducer 2024-10-03 14:06:23 +02:00
e555573f09 [daq] adding a producer that reads from one channel and saves it to the buffer 2024-10-03 14:05:41 +02:00
4d53a0f51d [buffer] adding samplerate 2024-10-03 14:05:02 +02:00
d127750e7b [buffer] removing logging, adding samplerate, adding check for getting the item at current index 2024-10-03 08:52:25 +02:00
b18f870a6b [buffer] adding True for fixing test 2024-10-02 15:55:00 +02:00
0b067df69c [project] adding pytest 2024-10-02 15:52:41 +02:00
e3ed2fcc75 [test] adding testcases for get method of buffer 2024-10-02 15:52:29 +02:00
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
54f0d61fc9 [dataio] commenting out databuffer 2024-10-02 08:10:08 +02:00
815838eab7 [dataio] adding class for buffer 2024-10-02 08:09:45 +02:00
314e609472 [refactoring] removing imports 2024-10-01 18:16:33 +02:00
2719d49eb0 [refacorting] adding DataBuffer 2024-10-01 12:04:47 +02:00
a0c524326d [refactroring] renaming class 2024-10-01 12:04:06 +02:00
c2285f3750 [project] adding buffer class 2024-10-01 12:03:41 +02:00
9327d1bac9 [refactoring] renaming file 2024-10-01 12:03:25 +02:00
565d6e5318 [plotting] move calibrtion plot outside of main window 2024-09-30 16:22:24 +02:00
b35a9212ac [plotting] move calibration plot outside of mainwindow.py 2024-09-30 16:22:00 +02:00
56c8b59ccd [project] changing to absolut imports 2024-09-30 12:10:54 +02:00
cbc86598b0 [ui] adding information to text field 2024-09-30 12:10:28 +02:00
031b5098d5 [plotting calibration] fixing power spectrum 2024-09-30 12:09:39 +02:00
c33e4cc32f Merge branch 'main' into calibration 2024-09-30 10:13:48 +02:00
00f6b11740 [project] removing data.nix file 2024-09-30 10:10:32 +02:00
e0491c5917 [repos/calibration] fixing plots 2024-09-30 10:10:12 +02:00
9c80091d16 [repos/calibration] removing plots 2024-09-30 10:09:44 +02:00
21 changed files with 1753 additions and 367 deletions

279
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"
@ -142,6 +142,19 @@ files = [
docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
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]]
name = "decorator"
version = "5.1.1"
@ -169,53 +182,59 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth
[[package]]
name = "fonttools"
version = "4.53.1"
version = "4.54.1"
description = "Tools to manipulate font files"
optional = false
python-versions = ">=3.8"
files = [
{file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"},
{file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"},
{file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"},
{file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"},
{file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"},
{file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"},
{file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"},
{file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"},
{file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"},
{file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"},
{file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"},
{file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"},
{file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"},
{file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"},
{file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"},
{file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"},
{file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"},
{file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"},
{file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"},
{file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"},
{file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"},
{file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"},
{file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"},
{file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"},
{file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"},
{file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"},
{file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"},
{file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"},
{file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"},
{file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"},
{file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"},
{file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"},
{file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"},
{file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"},
{file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"},
{file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"},
{file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"},
{file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"},
{file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"},
{file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"},
{file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"},
{file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"},
{file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"},
{file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"},
{file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"},
{file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"},
{file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"},
{file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"},
{file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"},
{file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"},
{file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"},
{file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"},
{file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"},
{file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"},
{file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"},
{file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"},
{file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"},
{file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"},
{file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"},
{file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"},
{file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"},
{file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"},
{file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"},
{file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"},
{file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"},
{file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"},
{file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"},
{file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"},
{file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"},
{file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"},
{file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"},
{file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"},
{file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"},
{file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"},
{file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"},
{file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"},
{file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"},
{file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"},
{file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"},
{file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"},
{file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"},
{file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"},
{file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"},
{file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"},
{file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"},
{file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"},
{file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"},
{file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"},
{file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"},
{file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"},
]
[package.extras]
@ -270,15 +289,26 @@ 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"
version = "8.28.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.10"
files = [
{file = "ipython-8.27.0-py3-none-any.whl", hash = "sha256:f68b3cb8bde357a5d7adc9598d57e22a45dfbea19eb6b98286fa3b288c9cd55c"},
{file = "ipython-8.27.0.tar.gz", hash = "sha256:0b99a2dc9f15fd68692e898e5568725c6d49c527d36a9fb5960ffbdeaa82ff7e"},
{file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"},
{file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"},
]
[package.dependencies]
@ -291,6 +321,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 +692,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 +804,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"
@ -847,16 +908,16 @@ PyQt6-sip = ">=13.8,<14"
[[package]]
name = "pyqt6-qt6"
version = "6.7.2"
version = "6.7.3"
description = "The subset of a Qt installation needed by PyQt6."
optional = false
python-versions = "*"
files = [
{file = "PyQt6_Qt6-6.7.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:065415589219a2f364aba29d6a98920bb32810286301acbfa157e522d30369e3"},
{file = "PyQt6_Qt6-6.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7f817efa86a0e8eda9152c85b73405463fbf3266299090f32bbb2266da540ead"},
{file = "PyQt6_Qt6-6.7.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:05f2c7d195d316d9e678a92ecac0252a24ed175bd2444cc6077441807d756580"},
{file = "PyQt6_Qt6-6.7.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:fc93945eaef4536d68bd53566535efcbe78a7c05c2a533790a8fd022bac8bfaa"},
{file = "PyQt6_Qt6-6.7.2-py3-none-win_amd64.whl", hash = "sha256:b2d7e5ddb1b9764cd60f1d730fa7bf7a1f0f61b2630967c81761d3d0a5a8a2e0"},
{file = "PyQt6_Qt6-6.7.3-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:f517a93b6b1a814d4aa6587adc312e812ebaf4d70415bb15cfb44268c5ad3f5f"},
{file = "PyQt6_Qt6-6.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8551732984fb36a5f4f3db51eafc4e8e6caf18617365830285306f2db17a94c2"},
{file = "PyQt6_Qt6-6.7.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:50c7482bcdcf2bb78af257fb10ed8b582f8daf91d829782393bc50ac5a0a900c"},
{file = "PyQt6_Qt6-6.7.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:cb525fdd393332de60887953029276a44de480fce1d785251ae639580f5e7246"},
{file = "PyQt6_Qt6-6.7.3-py3-none-win_amd64.whl", hash = "sha256:36ea0892b8caeb983af3f285f45fb8dfbb93cfd972439f4e01b7efb2868f6230"},
]
[[package]]
@ -903,6 +964,26 @@ 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\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.5,<2"
[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"
@ -917,15 +998,95 @@ files = [
[package.dependencies]
six = ">=1.5"
[[package]]
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{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]]
name = "rich"
version = "13.8.1"
version = "13.9.2"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.7.0"
python-versions = ">=3.8.0"
files = [
{file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"},
{file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"},
{file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"},
{file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"},
]
[package.dependencies]
@ -1104,5 +1265,5 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "b1076b7f750e8f7e66542918ec746e74544fbfdb376875158f083fb573d18107"
python-versions = ">=3.11, <3.13"
content-hash = "31433ed1dc0cc83dd7e781546dad5b8f068d59eaf692b4d3c12e07c604b1a6dd"

View File

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

View File

@ -2,15 +2,16 @@ import sys
from PyQt6.QtCore import QSettings
from PyQt6.QtWidgets import QApplication
from IPython import embed
from pyrelacs import info
from pyrelacs.config.config_loader import load_config
from pyrelacs.ui.mainwindow import PyRelacs
from pyrelacs.util.logging import config_logging
import resources
log = config_logging()
from pyrelacs import (
resources,
) # best created with pyside6-rcc resources.qrc -o resources.py (rcc produces an error...)
from . import resources # best created with pyside6-rcc resources.qrc -o resources.py (rcc produces an error...)
def main():
app = QApplication(sys.argv)
@ -26,7 +27,11 @@ def main():
x = int(settings.value("app/pos_x", 100))
y = int(settings.value("app/pos_y", 100))
window = PyRelacs()
# load the config
config = load_config()
# start the app
window = PyRelacs(config)
window.setMinimumWidth(200)
window.setMinimumHeight(200)
window.resize(width, height)

76
pyrelacs/config.yaml Normal file
View File

@ -0,0 +1,76 @@
settings:
# If true daq should be used, else starts without daq
daq: False
# class names of the repros to run
repros: [Calibration, Sinus]
path: ~/projects/pyrelacs/test/
metadata:
SetupName : Setup1
Maintainer : Your name
Creator : Whoever
SetupLocation : virtual
Lab : XYZ-Lab
Institute : Your institute
University : Your university
Address : Your institute's address
pyrelacs:
data:
input:
inputsamplerate : 20
# Unit is rescaled to Hz
inputsamplerateunit : kHz
# BufferSize
inputtracecapacity : 600
# Unit is rescaled to s
inputtracecapacityunit : s
inputunipolar : false
inputtraceid : [ V-1, EOD, LocalEOD-1, GlobalEFieldStimulus ]
inputtracescale : [ 100, 1, 10, 1 ]
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:
outputtraceid : [ GlobalEField, GlobalEFieldAM, LocalEField, I ]
outputtracedevice : [ ao-1, ao-1, ao-1, ao-1 ]
outputtracechannel : [ 0, 1, 2, 3 ]
outputtracescale : [ 1, 1, 1, 1 ]
outputtraceunit : [ V, V, V, V ]
outputtracemaxrate : [ 40, 40, 40, 40]
outputtracemaxrateunit : [kHz, kHz, kHz, kHz]
outputtracemodality : [ electric, electric, electric, current ]
sinus:
inputsamplerate : 20
inputsamplerateunit : kHz
# BufferSize
inputtracecapacity : 600
inputtracecapacityunit : s
outputtraceid : [ Sinus ]
outputtracedevice : [ ao-0 ]
outputtracechannel : [ 0 ]
outputtracescale : [ 1 ]
outputtraceunit : [ V ]
outputtracemaxrate : [ 40 ]
outputtracemaxrateunit : [kHz]
outputtracemodality : [ electric ]
devices:
DAQFlexCore:
analogoutputpins : [0, 1]
analoginputpinshigh : [0, 1, 2, 3,4,5,6,7]
analoginputpinslow : [1,2]
digitalpins : [0,1,2,3]
CS3310DIO:
ident : attdev-1
strobepin : 6
datainpin : 5
dataoutpin : -1
cspin : 4
mutepin : 7
zcenpin : -1

View File

@ -0,0 +1,132 @@
from typing import TypedDict, Union
from dataclasses import dataclass
import pathlib
import dacite
import yaml
from dacite import from_dict
from IPython import embed
from pyrelacs.util.logging import config_logging
log = config_logging()
@dataclass
class ValueUnit:
value: int
unit: str
@dataclass
class Settings:
daq: bool
repros: list[str]
path: str
@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:
config = from_dict(data_class=Config, data=data)
return config
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()

Binary file not shown.

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,192 @@
from typing import Tuple
import numpy as np
from IPython import embed
from pyqtgraph.Qt.QtCore import QMutex
class CircBuffer:
def __init__(
self,
size: int,
channels: int = 1,
samplerate: float = 40_000.0,
mutex: QMutex = QMutex(),
):
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.mutex = mutex
@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.mutex.lock()
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
self.mutex.unlock()
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[channel, 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[channel, 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,175 @@
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
self.stop = False
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 = int(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 not self.stop:
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. "
def stop_aquisition(self):
self.stop = True
# 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,72 @@
import time
from IPython import embed
from PyQt6.QtCore import QMutex
import nixio
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.util.logging import config_logging
log = config_logging()
class NixWriter:
def __init__(self, buffer: CircBuffer) -> None:
self.buffer = buffer
def write_nix(
self,
data_array: nixio.DataArray,
mutex: QMutex,
channel: int = 0,
chunk_size=1000,
*args,
**kwargs,
):
index = 0
log.debug("Starting the writing")
self.write = True
while self.write:
total_count = self.buffer.totalcount(channel=channel)
if total_count - index >= chunk_size:
mutex.lock()
log.debug(index)
try:
_, 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
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(f"Samples written {index}")
def _write_header(self):
self.nix_file = nixio.File.open(path="data.nix", mode=nixio.FileMode.Overwrite)
self.block = self.nix_file.create_block("recording", "testfile")
self.data_array = self.block.create_data_array(
"Analog1", "ndarray", shape=(1000,), dtype=nixio.DataType.Double
)
def stop_writing(self):
self.write = False

View File

@ -0,0 +1,50 @@
import time
import numpy as np
from IPython import embed
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.util.logging import config_logging
log = config_logging()
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}")
log.debug(f"Total samples produced {self.buffer.totalcount()}")
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.
@ -41,12 +41,16 @@ class MccDac:
try:
self.daq_device.connect()
except uldaq.ul_exception.ULException:
self.disconnect_dac()
self.disconnect_daq()
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()
log.debug("Connected")
log.debug("Connected to MccDaq")
log.debug("Activating the Attenuator")
self.activate_attenuator()
def connect_dac(self):
"""
@ -193,7 +197,7 @@ class MccDac:
except Exception as e:
print(f"{e}")
self.set_analog_to_zero()
self.disconnect_dac()
self.disconnect_daq()
return data_analog_output
@ -220,10 +224,10 @@ class MccDac:
uldaq.AOutListFlag.DEFAULT,
[0, 0],
)
except Exception as e:
log.error("f{e}")
except Exception as er:
log.error(f"{er}")
log.error("disconnection dac")
self.disconnect_dac()
# self.disconnect_daq()
def digital_trigger(self, ch: int = 0) -> None:
"""
@ -280,7 +284,9 @@ class MccDac:
bit = self.dio_device.d_bit_in(uldaq.DigitalPortType.AUXPORT, channel)
return bit
def disconnect_dac(self):
def disconnect_daq(self):
log.debug("Disconnecting DAQ")
self.deactivate_attenuator()
self.daq_device.disconnect()
self.daq_device.release()
@ -328,12 +334,12 @@ class MccDac:
except uldaq.ul_exception.ULException:
log.debug("Operation timed out")
self.write_bit(channel=0, bit=0)
self.disconnect_dac()
self.disconnect_daq()
self.connect_dac()
self.set_analog_to_zero()
finally:
self.write_bit(channel=0, bit=0)
self.disconnect_dac()
self.disconnect_daq()
self.connect_dac()
self.set_analog_to_zero()

View File

@ -1,29 +1,26 @@
import tomlkit
import tomllib
import pathlib
def load_project_settings(project_root):
print(project_root)
# Read the pyproject.toml file
with open(pathlib.Path.joinpath(project_root, "pyproject.toml"), "r") as f:
pyproject_content = f.read()
pyproject_path = pathlib.Path.joinpath(project_root, "pyproject.toml")
with open(pyproject_path, "rb") as f:
pyproject_content = tomllib.load(f)
# Parse the toml content
pyproject = tomlkit.parse(pyproject_content)
# Access project settings
return {
"name": pyproject["tool"]["poetry"]["name"],
"version": pyproject["tool"]["poetry"]["version"],
"description": pyproject["tool"]["poetry"]["description"],
"authors": pyproject["tool"]["poetry"]["authors"],
"readme": pyproject["tool"]["poetry"]["authors"],
"licence": pyproject["tool"]["poetry"]["license"],
"organization": pyproject["project"]["organization"],
"classifiers": pyproject["tool"]["poetry"]["classifiers"],
"copyright": pyproject["project"]["copyright"],
"repository": pyproject["tool"]["poetry"]["repository"],
info_dict = {
"name": pyproject_content["tool"]["poetry"]["name"],
"version": pyproject_content["tool"]["poetry"]["version"],
"description": pyproject_content["tool"]["poetry"]["description"],
"authors": pyproject_content["tool"]["poetry"]["authors"],
"readme": pyproject_content["tool"]["poetry"]["authors"],
"licence": pyproject_content["tool"]["poetry"]["license"],
"organization": pyproject_content["project"]["organization"],
"classifiers": pyproject_content["tool"]["poetry"]["classifiers"],
"copyright": pyproject_content["project"]["copyright"],
"repository": pyproject_content["tool"]["poetry"]["repository"],
}
return info_dict
_root = pathlib.Path(__file__).parent.parent

View File

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

View File

View File

@ -22,9 +22,7 @@ class Repro:
def __init__(self) -> None:
pass
def run_repro(
self, nix_file: nix.File, name: str, file: pathlib.Path, *args, **kwargs
) -> None:
def run_repro(self, name: str, file: pathlib.Path, *args, **kwargs) -> None:
spec = importlib.util.spec_from_file_location("rep", file)
if not spec:
log.error("Could not load the file")
@ -40,14 +38,19 @@ class Repro:
log.error(f"{spec.loader} is None")
if hasattr(module, name):
rep_class = getattr(module, name)
rep_class.run(nix_file)
rep_class.run(*args, **kwargs)
else:
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
Parameters
----------
include_repros : list[str]
List of repros to include in the pyrelacs instance
Returns
-------
Tuple[list, list]
@ -73,4 +76,7 @@ class Repro:
repro_names.extend(class_name)
file_names.append(python_file)
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

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,27 +1,43 @@
import time
from pathlib import Path as path
from datetime import datetime
from dataclasses import asdict
from PyQt6.QtGui import QAction, QIcon, QKeySequence
from PyQt6.QtCore import Qt, QSize, QThreadPool
from PyQt6.QtCore import Qt, QSize, QThreadPool, QMutex
from PyQt6.QtWidgets import (
QGridLayout,
QPushButton,
QTabWidget,
QToolBar,
QVBoxLayout,
QWidget,
QMainWindow,
QPlainTextEdit,
QMenuBar,
QStatusBar,
)
import uldaq
import numpy as np
import nixio as nix
import nixio
import pyqtgraph as pg
import quantities as pq
from pathlib import Path as path
from scipy.signal import welch, find_peaks
from pyrelacs.devices.mccdaq import MccDaq
from pyrelacs.dataio.circbuffer import CircBuffer
from pyrelacs.dataio.daq_producer import DaqProducer
from pyrelacs.dataio.nix_writer import NixWriter
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.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
@ -30,17 +46,32 @@ from IPython import embed
class PyRelacs(QMainWindow):
def __init__(self):
def __init__(self, config):
super().__init__()
# loaded config
self.config = config
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(
Qt.ToolButtonStyle.ToolButtonTextBesideIcon
) # Ensure icons are displayed with text
self.setWindowTitle("PyRelacs")
self.beat_plot = pg.PlotWidget()
self.power_plot = pg.PlotWidget()
self.create_nix_file(f"{_root}/test.nix", self.config.metadata)
self.mutex = QMutex()
self.figure = pg.GraphicsLayoutWidget()
self.threadpool = QThreadPool()
self.repros = Repro()
self.text = QPlainTextEdit()
self.text.setReadOnly(True)
@ -48,20 +79,48 @@ class PyRelacs(QMainWindow):
self.setMenuBar(QMenuBar(self))
self.setStatusBar(QStatusBar(self))
self.create_actions()
self.create_buttons()
self.create_toolbars()
self.repro_tabs = QTabWidget()
self.create_repros_tabs()
layout = QGridLayout()
layout.addWidget(self.beat_plot, 0, 0, 1, 2)
layout.addWidget(self.power_plot, 1, 0, 1, 2)
layout.addWidget(self.text, 2, 0, 1, 2)
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)
filename = path.joinpath(path.cwd(), "data")
self.nix_file = nix.File.open(str(filename), nix.FileMode.Overwrite)
SAMPLERATE = pq.Quantity(
self.config.pyrelacs.data.input.inputsamplerate,
self.config.pyrelacs.data.input.inputsamplerateunit,
).rescale("Hz")
INPUTTRACECAPACITY = pq.Quantity(
self.config.pyrelacs.data.input.inputtracecapacity,
self.config.pyrelacs.data.input.inputtracecapacityunit,
).rescale("s")
start = time.time()
BUFFERSIZE = (SAMPLERATE * INPUTTRACECAPACITY).simplified
end = time.time()
log.debug(f"Buffer allocation took {end - start}")
self.buffer = CircBuffer(
size=int(BUFFERSIZE.base),
samplerate=float(SAMPLERATE.base),
mutex=self.mutex,
)
self.continously_plot = Continously(self.figure, self.buffer)
# self.continously_plot.plot()
if self.mccdaq:
log.debug("Creating Daq Generator")
self.daq_producer = DaqProducer(self.buffer, self.mccdaq.daq_device, [1, 1])
log.debug("Creating Sinus Generator")
self.sinus_producer = SinProducer(self.buffer)
self.nix_writer = NixWriter(self.buffer)
def create_actions(self):
self._rlx_exitaction = QAction(QIcon(":/icons/exit.png"), "Exit", self)
@ -74,20 +133,44 @@ class PyRelacs(QMainWindow):
self._rlx_aboutaction.setEnabled(True)
self._rlx_aboutaction.triggered.connect(self.on_about)
self._daq_connectaction = QAction(QIcon(":icons/connect.png"), "Connect DAQ", self)
self._daq_connectaction.setStatusTip("Connect to daq device")
# self._daq_connectaction.setShortcut(QKeySequence("Alt+d"))
self._daq_connectaction.triggered.connect(self.connect_dac)
self._daq_connectaction = QAction(
QIcon(":icons/connect.png"), "Connect DAQ", self
)
if self.mccdaq:
self._daq_connectaction.setStatusTip("Connect to daq device")
# self._daq_connectaction.setShortcut(QKeySequence("Alt+d"))
self._daq_connectaction.triggered.connect(self.mccdaq.connect_dac)
self._daq_disconnectaction = QAction(
QIcon(":/icons/disconnect.png"), "Disconnect DAQ", self
)
self._daq_disconnectaction.setStatusTip("Disconnect the DAQ device")
# self._daq_connectaction.setShortcut(QKeySequence("Alt+d"))
self._daq_disconnectaction.triggered.connect(self.mccdaq.disconnect_daq)
# self._daq_calibaction = QAction(
# QIcon(":/icons/calibration.png"), "Plot calibration", self
# )
# 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"), "RunDAQ", 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._recenter_plot = QAction("Recenter", self)
self._recenter_plot.triggered.connect(self.recenter_continously_plot)
self._recenter_plot.setShortcut(QKeySequence("Alt+r"))
self._daq_disconnectaction = QAction(QIcon(":/icons/disconnect.png"), "Disconnect DAQ", self)
self._daq_disconnectaction.setStatusTip("Disconnect the DAQ device")
# self._daq_connectaction.setShortcut(QKeySequence("Alt+d"))
self._daq_disconnectaction.triggered.connect(self.disconnect_dac)
self._record = QAction(QIcon(":/icons/record.png"), "Record", self)
self._record.triggered.connect(self.record)
self._daq_calibaction = QAction(QIcon(":/icons/calibration.png"), "Plot calibration", self)
self._daq_calibaction.setStatusTip("Calibrate the attenuator device")
# self._daq_calibaction.setShortcut(QKeySequence("Alt+d"))
self._daq_calibaction.triggered.connect(self.plot_calibration)
self.create_menu()
def create_menu(self):
@ -102,10 +185,12 @@ class PyRelacs(QMainWindow):
file_menu.addAction(self._rlx_aboutaction)
if device_menu is not None:
device_menu.addAction(self._daq_connectaction)
device_menu.addAction(self._daq_disconnectaction)
if self.config.settings.daq:
device_menu.addAction(self._daq_connectaction)
device_menu.addAction(self._daq_disconnectaction)
device_menu.addSeparator()
device_menu.addAction(self._daq_calibaction)
# device_menu.addAction(self._daq_calibaction)
device_menu.addAction(self._run_action)
if help_menu is not None:
help_menu.addSeparator()
@ -123,140 +208,171 @@ class PyRelacs(QMainWindow):
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, rlx_toolbar)
daq_toolbar = QToolBar("DAQ")
daq_toolbar.addAction(self._daq_connectaction)
daq_toolbar.addAction(self._daq_disconnectaction)
daq_toolbar.addAction(self._daq_calibaction)
if self.config.settings.daq:
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)
daq_toolbar.addAction(self._recenter_plot)
daq_toolbar.addSeparator()
daq_toolbar.addAction(self._record)
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, daq_toolbar)
repro_toolbar = QToolBar("Repros")
repro_names, file_names = self.repros.names_of_repros()
def create_repros_tabs(self):
repro_names, file_names = self.repros.names_of_repros(
include_repros=self.config.settings.repros
)
nix_blocks = {
rep: self.nix_file.create_block(f"{rep}", "Data Repro")
for rep in repro_names
}
figures_repros = {rep: pg.GraphicsLayoutWidget() for rep in repro_names}
for rep, fn in zip(repro_names, file_names):
repro_action = QAction(rep, self)
repro_action.setStatusTip(rep)
repro_action.triggered.connect(
lambda checked, n=rep, f=fn: self.run_repro(n, f)
tab = QWidget()
tab_layout = QGridLayout()
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,
nix_blocks,
figures_repros,
self.mccdaq,
self.config,
)
)
repro_toolbar.addAction(repro_action)
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, 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)
self.daq_disconnect_button = QPushButton("Disconnect Daq")
self.daq_disconnect_button.setCheckable(True)
self.daq_disconnect_button.clicked.connect(self.disconnect_dac)
self.plot_calibration_button = QPushButton("Plot Calibration")
self.plot_calibration_button.setCheckable(True)
self.plot_calibration_button.clicked.connect(self.plot_calibration)
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):
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")
except IndexError:
log.error("DAQ is not connected")
log.error("Please connect a DAQ device to the system")
tab_layout.addWidget(run_repro_button, 0, 0, 1, 0)
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,
nix_block,
figures,
*args,
):
self.text.appendPlainText(f"started Repro {name_of_repro}, {file_of_repro}")
nix_block_repro = nix_block[name_of_repro]
figure_repro = figures[name_of_repro]
worker = Worker(
self.repros.run_repro,
name_of_repro,
file_of_repro,
nix_block_repro,
figure_repro,
*args[-2:],
)
worker.signals.result.connect(self.print_output)
worker.signals.finished.connect(self.thread_complete)
worker.signals.progress.connect(self.progress_fn)
if hasattr(PyRelacs, "daq_device"):
try:
self.daq_device.connect()
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
self.threadpool.start(worker)
def disconnect_dac(self):
def create_nix_file(self, file_path, metadata):
self.nix_file = nixio.File.open(
path=f"{file_path}", 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
)
def recenter_continously_plot(self):
self.continously_plot.refresh()
def plot_continously(self):
plot_con = Worker(self.continously_plot.plot)
plot_con.signals.result.connect(self.print_output)
plot_con.signals.finished.connect(self.thread_complete)
plot_con.signals.progress.connect(self.progress_fn)
self.threadpool.start(plot_con)
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)
self.threadpool.start(read_daq)
self.continously_plot.plot()
def run_sinus(self):
sinus_pro = Worker(self.sinus_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)
self.continously_plot.plot()
def record(self):
self.create_nix_file("test.nix", 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.finished.connect(self.thread_complete)
nix_writer.signals.progress.connect(self.progress_fn)
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):
self.add_to_textfield("Stopping the recording")
self.continously_plot.stop_plotting()
self.nix_writer.stop_writing()
log.debug("Stopping acquisiton")
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)
self.sinus_producer.stop_request()
log.debug("Stopping Sinus")
except AttributeError:
log.debug("DAQ was not connected")
log.debug("Did not generate Sinus")
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.signals.result.connect(self.print_output)
worker.signals.finished.connect(self.thread_complete)
worker.signals.progress.connect(self.progress_fn)
if hasattr(PyRelacs, "daq_device"):
log.debug("Stopping DAQ")
self.daq_producer.stop_aquisition()
self.threadpool.start(worker)
def add_to_textfield(self, s: str):
self.text.appendPlainText(s)
def on_exit(self):
print("exit button!")
log.info("exit button!")
self.stop_recording()
self.add_to_textfield("exiting")
if self.mccdaq:
self.mccdaq.disconnect_daq()
log.info("closing GUI")
self.close()
def on_about(self, e):
@ -264,10 +380,12 @@ class PyRelacs(QMainWindow):
about.show()
def print_output(self, s):
print(s)
log.info(s)
self.add_to_textfield(s)
def thread_complete(self):
print("THREAD COMPLETE!")
log.info("Thread complete!")
self.add_to_textfield("Thread complete!")
def progress_fn(self, 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

View File

@ -0,0 +1,88 @@
import time
import pyqtgraph as pg
from IPython import embed
import numpy as np
from pyqtgraph.Qt.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
self.last_plotted_index = 0
self.timer = QTimer()
def plot(self, *args, **kwargs):
self.figure.setBackground("w")
prev_plot = self.figure.getItem(row=0, col=0)
if prev_plot:
self.figure.removeItem(prev_plot)
self.continous_ax = self.figure.addPlot(row=0, col=0)
pen = pg.mkPen("red")
self.time = np.zeros(self.buffer.size)
self.data = np.empty(self.buffer.size)
self.line = self.continous_ax.plot(
self.time,
self.data,
pen=pen,
# symbol="o",
)
# self.plot_index = 0
self.CHUNK_PLOT = int(self.buffer.samplerate / 6)
self.PLOT_HISTORY = 500_000 # The amount of data you want to keep on screen
self.timer.setInterval(150)
self.timer.timeout.connect(self.update_plot)
self.timer.start()
def update_plot(self):
current_index = self.buffer.write_index()
total_count = self.buffer.totalcount()
start_time = time.time()
if total_count - self.last_plotted_index >= self.CHUNK_PLOT:
try:
times, items = self.buffer.read(
self.last_plotted_index,
extend=self.CHUNK_PLOT,
)
self.time = np.concatenate((self.time, times))[-self.PLOT_HISTORY :]
self.data = np.concatenate((self.data, items))[-self.PLOT_HISTORY :]
self.line.setData(
self.time,
self.data,
)
self.last_plotted_index += 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
def stop_plotting(self):
self.timer.stop()
if self.last_plotted_index > 0:
total_count = self.buffer.totalcount()
times, items = self.buffer.read(
self.last_plotted_index,
extend=total_count - self.last_plotted_index,
)
self.time = np.concatenate((self.time, times))[-self.PLOT_HISTORY :]
self.data = np.concatenate((self.data, items))[-self.PLOT_HISTORY :]
self.line.setData(
self.time,
self.data,
)
self.last_plotted_index += total_count - self.last_plotted_index
def refresh(self):
self.continous_ax.enableAutoRange()

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()