Skip to content
Snippets Groups Projects
Commit cb0f2487 authored by Tobias Hangleiter's avatar Tobias Hangleiter
Browse files

Merge branch 'enh/acquisition_and_update' into 'main'

Improve responsiveness during acquisition

See merge request !60
parents 21108414 76109187
No related branches found
No related tags found
1 merge request!60Improve responsiveness during acquisition
Pipeline #1638704 waiting for manual action
# .dockerignore
__pycache__
*.pyc
*.pyo
*.pyd
.env
venv/
\ No newline at end of file
......@@ -6,6 +6,7 @@
/doc/build/**
/doc/source/_autogen/**
/dist/
/.run/
.pytest_cache
pytest_report.xml
.coverage
FROM ghcr.io/astral-sh/uv:python3.9-bookworm
# Set environment variables to prevent Python from writing .pyc files and buffering stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# To mimmick CI for tests
ENV GITLAB_CI=True
RUN apt-get update && \
apt-get install -y --no-install-recommends \
portaudio19-dev \
neovim \
&& rm -rf /var/lib/apt/lists/*
RUN uv pip install --system hatch
WORKDIR /home/root/
RUN git clone -b master --single-branch https://git.rwth-aachen.de/qutech/qutil.git qutil
WORKDIR qutil
RUN uv pip install --system -e .[complete]
WORKDIR /home/root/
RUN git clone -b main --single-branch https://git.rwth-aachen.de/qutech/python-spectrometer.git pyspeck
WORKDIR pyspeck
RUN uv pip install --system -e .[complete]
# Set the entry point to run pytest
CMD ["pytest --doctest-modules"]
......@@ -93,5 +93,44 @@ or to check if everything works for a clean install (requires hatch to be instal
python -m hatch run tests:run
```
### Docker
0. Make sure docker is installed and running:
1. `pamac install docker docker-buildx`
2. `(sudo) docker buildx install`
2. `(sudo) systemctl status docker`
Example output:
```sh
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; preset: disabled)
Active: active (running) since Tue 2025-02-11 20:09:55 CET; 1 week 1 day ago
Invocation: 609d5a409daf4e99b7b3b8da9305776d
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 54128 (dockerd)
Tasks: 22
Memory: 38.8M (peak: 1.2G, swap: 21.3M, swap peak: 23.9M)
CPU: 1min 2.133s
CGroup: /system.slice/docker.service
└─54128 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
```
1. Build the docker image:
```sh
(sudo) docker build -t pyspeck-dev .
```
2. Run the image...
1. ... either running the tests and exiting:
```sh
(sudo) docker run --rm pyspeck-dev
```
2. ... or entering an interactive console:
```sh
(sudo) docker run --rm -it pyspeck-dev /bin/bash
```
## Releases
Releases on Gitlab, PyPI, and Zenodo are automatically created and pushed whenever a commit is tagged matching [CalVer](https://calver.org/) in the form `vYYYY.MM.MICRO` or `vYYYY.0M.MICRO`.
......@@ -24,6 +24,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Physics",
"Topic :: Utilities",
]
......@@ -104,5 +105,7 @@ max-line-length = 99
addopts = "-ra"
filterwarnings = [
"ignore:simanneal:UserWarning",
"ignore:Qutip:UserWarning"
"ignore:Qutip:UserWarning",
# plt.pause() in headless mode
'ignore:FigureCanvasAgg is non-interactive:UserWarning'
]
......@@ -115,7 +115,8 @@ In this short demonstration, we reproduce the example from
... yield x + self.rng.normal(scale=np.sqrt(noise_power),
... size=time.shape)
>>> spect = Spectrometer(MyDAQ(), savepath=tempfile.mkdtemp(),
... plot_cumulative=True, plot_amplitude=False)
... plot_cumulative=True, plot_amplitude=False,
... threaded_acquisition=False)
>>> spect.take('2 Vrms', fs=10e3, n_pts=1e5, nperseg=1024,
... amp=2*np.sqrt(2))
......
""" This module contains methods for using auditory channels to interface to humans """
import os
import queue
import threading
import time
......@@ -20,10 +20,16 @@ def _waveform_playback_target(waveform_queue: queue.Queue, stop_flag: threading.
pyaudio_instance = pyaudio.PyAudio()
try:
stream = pyaudio_instance.open(format=pyaudio.paFloat32,
channels=1,
rate=bitrate,
output=True)
except OSError:
if os.environ.get('GITLAB_CI', 'false').lower() == 'true':
return pyaudio_instance.terminate()
else:
raise
last_waveform = None
repeats = 0
......
......@@ -2,17 +2,16 @@
import contextlib
import os
import warnings
from functools import cached_property
import weakref
from itertools import compress
from typing import (Dict, Any, Optional, Mapping, Tuple, ContextManager, Literal, Iterable,
Union, List)
from typing import (Dict, Any, Optional, Mapping, Tuple, ContextManager, Iterable, Union, List,
Literal)
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import gridspec, scale
from qutil.misc import filter_warnings
from qutil.plotting import assert_interactive_figure
from qutil.typecheck import check_literals
from scipy import integrate, signal
_keyT = Union[int, str, Tuple[int, str]]
......@@ -21,16 +20,19 @@ _styleT = Union[None, _styleT, List[_styleT]]
class PlotManager:
__instances = weakref.WeakSet()
# TODO: blit?
PLOT_TYPES = ('main', 'cumulative', 'time')
LINE_TYPES = ('processed', 'raw')
TIMER_INTERVAL: int = 20 # ms
def __init__(self, data: Dict[_keyT, Any], plot_raw: bool = False,
plot_timetrace: bool = False, plot_cumulative: bool = False,
plot_negative_frequencies: bool = True, plot_absolute_frequencies: bool = True,
plot_amplitude: bool = True, plot_density: bool = True,
plot_cumulative_normalized: bool = False, plot_style: _styleT = 'fast',
plot_update_mode: str = 'never', plot_dB_scale: bool = False, prop_cycle=None,
plot_dB_scale: bool = False, threaded_acquisition: bool = True, prop_cycle=None,
raw_unit: str = 'V', processed_unit: Optional[str] = None,
uses_windowed_estimator: bool = True, complex_data: Optional[bool] = False,
figure_kw: Optional[Mapping] = None, subplot_kw: Optional[Mapping] = None,
......@@ -48,8 +50,8 @@ class PlotManager:
self._plot_density = plot_density
self._plot_cumulative_normalized = plot_cumulative_normalized
self._plot_style = plot_style
self._plot_update_mode = plot_update_mode
self._plot_dB_scale = plot_dB_scale
self._threaded_acquisition = threaded_acquisition
self._processed_unit = processed_unit if processed_unit is not None else raw_unit
# For dB scale plots, default to the first spectrum acquired.
......@@ -60,7 +62,9 @@ class PlotManager:
self.uses_windowed_estimator = uses_windowed_estimator
self._complex_data = complex_data
self._fig = None
self._leg = None
self._timer = None
self.axes = {key: dict.fromkeys(self.LINE_TYPES) for key in self.PLOT_TYPES}
self.lines = dict()
self.figure_kw = figure_kw or dict()
......@@ -68,29 +72,69 @@ class PlotManager:
self.gridspec_kw = gridspec_kw or dict()
self.legend_kw = legend_kw or dict()
self.legend_kw.setdefault('loc', 'upper right')
self.figure_kw.setdefault('num', f'Spectrometer {len(self.__instances) + 1}')
if not any('layout' in key for key in self.figure_kw.keys()):
self.figure_kw['layout'] = 'tight'
if self.subplot_kw.pop('sharex', None) is False:
# depending on the matplotlib version, this setting is either
# layout='tight' or tight_layout=True
self.figure_kw.setdefault('layout', 'tight')
if self.subplot_kw.pop('sharex', None) is not None:
warnings.warn('sharex in subplot_kw not negotiable, dropping', UserWarning)
@cached_property
# Keep track of instances that are alive for figure counting
self.__instances.add(self)
# TODO: this somehow never executes
weakref.finalize(self, self.__instances.discard, self)
def is_fig_open(self) -> bool:
"""Is the figure currently open, pending all events?"""
# Need to flush possible close events before we can be sure!
if self._fig is not None:
self._fig.canvas.flush_events()
return self._fig is not None and plt.fignum_exists(self.figure_kw['num'])
@property
def fig(self):
"""The figure hosting the plots."""
if self.is_fig_open():
return self._fig
try:
fig = plt.figure(**self.figure_kw)
self._fig = plt.figure(**self.figure_kw)
except TypeError:
if layout := self.figure_kw.pop('layout', False):
# matplotlib < 3.5 doesn't support layout kwarg yet
self.figure_kw[f'{layout}_layout'] = True
elif layout is False:
raise
fig = plt.figure(**self.figure_kw)
self._fig = plt.figure(**self.figure_kw)
assert_interactive_figure(self._fig)
def on_close(event):
self._fig = None
# If the window is closed, remove the figure from the cache so that it can be recreated
fig.canvas.mpl_connect('close_event', lambda _: self.__dict__.pop('fig', None))
if self._timer is not None:
self._timer.stop()
self._timer = None
assert_interactive_figure(fig)
return fig
# Clean up possible leftovers from before.
self.destroy_axes()
self.update_line_attrs(self.plots_to_draw, self.lines_to_draw, self.shown, stale=True)
# If the window is closed, remove the figure from the cache so that it can be recreated and
# stop the timer to delete any remaining callbacks
self._fig.canvas.mpl_connect('close_event', on_close)
self.setup_figure()
return self._fig
@property
def timer(self):
"""A timer object associated with the figure."""
if self._timer is None:
self._timer = self.fig.canvas.new_timer(self.TIMER_INTERVAL)
return self._timer
@property
def ax(self):
......@@ -112,7 +156,7 @@ class PlotManager:
@property
def shown(self) -> Tuple[Tuple[int, str], ...]:
return tuple(key for key, val in self.lines.items()
if not val['main']['processed']['hidden'])
if val['main']['processed']['hidden'] is False)
@property
def lines_to_draw(self) -> Tuple[str, ...]:
......@@ -140,7 +184,8 @@ class PlotManager:
if val != self._plot_raw:
self._plot_raw = val
self.update_line_attrs(self.plots_to_draw, ['raw'], stale=True, hidden=not val)
if 'fig' in self.__dict__:
if self.is_fig_open():
# Only update the figure if it's already been created
self.setup_figure()
@property
......@@ -154,7 +199,7 @@ class PlotManager:
if val != self._plot_cumulative:
self._plot_cumulative = val
self.update_line_attrs(['cumulative'], self.lines_to_draw, stale=True, hidden=not val)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
......@@ -170,7 +215,7 @@ class PlotManager:
if val != self._plot_timetrace:
self._plot_timetrace = val
self.update_line_attrs(['time'], self.lines_to_draw, stale=True, hidden=not val)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
......@@ -184,7 +229,7 @@ class PlotManager:
if val != self._plot_negative_frequencies:
self._plot_negative_frequencies = val
self.update_line_attrs(['main', 'cumulative'], self.lines_to_draw, stale=True)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
......@@ -206,7 +251,7 @@ class PlotManager:
keys=[key for key in self.shown if 'freq' in self._data[key]['settings']],
stale=True
)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
......@@ -225,7 +270,7 @@ class PlotManager:
if val != self._plot_amplitude:
self._plot_amplitude = val
self.update_line_attrs(['main', 'cumulative'], self.lines_to_draw, stale=True)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
......@@ -239,7 +284,7 @@ class PlotManager:
if val != self._plot_density:
self._plot_density = val
self.update_line_attrs(['main', 'cumulative'], self.lines_to_draw, stale=True)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
......@@ -253,7 +298,7 @@ class PlotManager:
if val != self._plot_cumulative_normalized:
self._plot_cumulative_normalized = val
self.update_line_attrs(['cumulative'], self.lines_to_draw, stale=True)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
......@@ -271,20 +316,9 @@ class PlotManager:
self._plot_style = val
self.destroy_axes()
self.update_line_attrs(self.plots_to_draw, self.lines_to_draw, stale=True)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
def plot_update_mode(self) -> str:
"""Setting influencing how often the matplotlib event queue is
flushed."""
return self._plot_update_mode
@plot_update_mode.setter
@check_literals
def plot_update_mode(self, mode: Literal['fast', 'always', 'never']):
self._plot_update_mode = mode
@property
def plot_dB_scale(self) -> bool:
"""Plot data as dB relative to a reference spectrum.
......@@ -298,9 +332,20 @@ class PlotManager:
if val != self._plot_dB_scale:
self._plot_dB_scale = val
self.update_line_attrs(['main', 'cumulative'], self.lines_to_draw, stale=True)
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
def threaded_acquisition(self) -> bool:
"""Acquire data in a separate thread."""
return self._threaded_acquisition
@threaded_acquisition.setter
def threaded_acquisition(self, val: bool):
val = bool(val)
if val != self._threaded_acquisition:
self._threaded_acquisition = val
@property
def reference_spectrum(self) -> Optional[Tuple[int, str]]:
"""Spectrum taken as a reference for the dB scale.
......@@ -310,6 +355,10 @@ class PlotManager:
return list(self._data)[0]
return self._reference_spectrum
@reference_spectrum.setter
def reference_spectrum(self, val: Tuple[int, str]):
self._reference_spectrum = val
@property
def processed_unit(self) -> str:
"""The unit displayed for processed data."""
......@@ -320,7 +369,7 @@ class PlotManager:
val = str(val)
if val != self._processed_unit:
self._processed_unit = val
if 'fig' in self.__dict__:
if self.is_fig_open():
self.setup_figure()
@property
......@@ -508,15 +557,7 @@ class PlotManager:
continue
def update_figure(self):
if 'fig' not in self.__dict__:
# Need to completely create/restore figure
self.__dict__.pop('fig', None)
self.destroy_axes()
self.update_line_attrs(self.plots_to_draw, self.lines_to_draw, self.shown, stale=True)
self.setup_figure()
# Flush out all idle events
if self.plot_update_mode in {'always'}:
# Flush out all idle events, necessary for some reason in sequential mode
self.fig.canvas.flush_events()
# First set new axis scales and x-limits, then update the lines (since the cumulative
......@@ -538,9 +579,7 @@ class PlotManager:
if self._leg is not None:
self._leg.remove()
# Needed to force update during a loop for instance
self.fig.canvas.draw_idle()
if self.plot_update_mode in {'always', 'fast'}:
self.fig.canvas.flush_events()
def update_lines(self):
......@@ -591,7 +630,7 @@ class PlotManager:
for k in self.shown
), default=None)
with filter_warnings('ignore', UserWarning):
with filter_warnings(action='ignore', category=UserWarning):
# ignore warnings issued for empty plots with log scales
self.axes['main']['processed'].set_xlim(left, right)
......
This diff is collapsed.
......@@ -8,6 +8,7 @@ Examples
... savepath=tempfile.mkdtemp())
>>> speck.take('a test', fs=10e3) #doctest: +ELLIPSIS
...
>>> speck.block_until_ready() # for doctest
Add an artificial time delay to mimick finite data acquisition time:
>>> speck.take('delayed', n_avg=3, delay=True) #doctest: +ELLIPSIS
......
......@@ -54,7 +54,7 @@ from typing import Any, Dict, Mapping, Optional, Type, Union
import numpy as np
from packaging import version
from qutil.domains import BoundedSet, DiscreteInterval
from qutil.domains import DiscreteInterval, ExponentialDiscreteInterval
from scipy.special import gamma
from zhinst import toolkit
......@@ -166,8 +166,7 @@ class ZurichInstrumentsMFLIDAQ(_ZurichInstrumentsDevice):
class MFLIDAQSettings(DAQSettings):
CLOCKBASE = self.device.clockbase()
# TODO: always the same for each instrument?
ALLOWED_FS = BoundedSet(CLOCKBASE / 70 / 2 ** np.arange(24),
precision=DAQSettings.PRECISION)
ALLOWED_FS = ExponentialDiscreteInterval(-23, 0, base=2, prefactor=CLOCKBASE / 70)
DEFAULT_FS = CLOCKBASE / 70 / 2**6
return MFLIDAQSettings
......@@ -411,7 +410,7 @@ class ZurichInstrumentsMFLIScope(_ZurichInstrumentsDevice):
CLOCKBASE = self.device.clockbase()
# TODO: always the same for each instrument?
ALLOWED_N_PTS = DiscreteInterval(2 ** 12, 2 ** 14, precision=DAQSettings.PRECISION)
ALLOWED_FS = BoundedSet(CLOCKBASE / 2 ** np.arange(17),
ALLOWED_FS = ExponentialDiscreteInterval(-16, 0, prefactor=CLOCKBASE, base=2,
precision=DAQSettings.PRECISION)
DEFAULT_FS = CLOCKBASE / 2 ** 8
......
......@@ -20,8 +20,9 @@ def import_or_mock_pyaudio():
def test_audio(import_or_mock_pyaudio):
spect = Spectrometer(QoptColoredNoise(), savepath=mkdtemp(),
play_sound=True)
play_sound=True, threaded_acquisition=False)
spect.take('with sound', f_max=20000, A=2e-4)
spect.block_until_ready()
assert 'audio_stream' in spect.__dict__
assert spect.audio_stream.playback_thread is not None
......
......@@ -21,24 +21,34 @@ def start_animation(fig):
fig.canvas.callbacks.process(
'draw_event', DrawEvent('draw_event', fig.canvas, fig.canvas.get_renderer())
)
else:
fig.canvas.flush_events()
def advance_frames(view: LiveViewT, n_frames: int):
# Need to manually trigger events in headless mode. Else does nothing.
if not is_using_mpl_gui_backend(view.fig):
# Start the animation
for _ in range(n_frames):
if not is_using_mpl_gui_backend(view.fig):
for fun, args, kwargs in view.animation.event_source.callbacks:
fun(*args, **kwargs)
else:
view.fig.canvas.flush_events()
def stop_view(view):
try:
view.stop()
except RuntimeError:
# Thread did not join. Try to trigger event loop once.
advance_frames(view, 1)
view.stop()
def close_figure(fig):
if not is_using_mpl_gui_backend(fig):
# Need to manually trigger the close event because the event loop isn't running
fig.canvas.callbacks.process('close_event', CloseEvent('close_event', fig.canvas))
else:
# Required to have a figure that's up-to-date
fig.canvas.flush_events()
# remove figure from registry
plt.close(fig)
......@@ -59,12 +69,12 @@ def spectrometer():
return Spectrometer(daq.QoptColoredNoise(), savepath=mkdtemp())
@pytest.fixture(params=[True, False])
@pytest.fixture(params=[True, False], ids=['timetrace', 'no_timetrace'])
def plot_timetrace(request):
return request.param
@pytest.fixture(params=[True, False])
@pytest.fixture(params=[True, False], ids=['in_child_process', 'in_main_process'])
def in_process(request):
return request.param
......@@ -98,22 +108,39 @@ def started(spectrometer, plot_timetrace, in_process, in_gitlab_ci):
@pytest.fixture
def stopped(started: list[LiveViewT], in_process: bool):
view = random.choice(started)
view.stop()
stop_view(view)
if not in_process and len(started) > 1:
view = started[1 - started.index(view)]
if is_using_mpl_gui_backend(view.fig):
view.fig.canvas.start_event_loop(100e-3)
else:
advance_frames(view, 1)
advance_frames(started[1 - started.index(view)], 1)
stopped = started
return stopped
def test_started(started, plot_timetrace, in_process):
@pytest.fixture
def closed(started: list[LiveViewT], in_process: bool):
view = random.choice(started)
if in_process:
view.close_event.set()
else:
close_figure(view.fig)
if not in_process and len(started) > 1:
advance_frames(started[1 - started.index(view)], 1)
else:
# Give the process time to process all events
time.sleep(250e-3)
closed = started
yield closed
def test_started(spectrometer, started, plot_timetrace, in_process):
views = started
assert spectrometer.acquiring
if plot_timetrace:
assert len(views) == 2
if not in_process:
......@@ -135,8 +162,12 @@ def test_started(started, plot_timetrace, in_process):
assert not statement_timed_out(lambda: not view.stop_event.is_set(), timeout=1)
def test_stopped(stopped):
views = stopped
@pytest.mark.parametrize("view_state", ["stopped", "closed"])
def test_finished(spectrometer, view_state, request, plot_timetrace, in_process, in_gitlab_ci):
"""Test the state of view(s) and spectrometer after stopping and closing."""
views = request.getfixturevalue(view_state)
assert not spectrometer.acquiring
for view in views:
assert not statement_timed_out(lambda: view.is_running() is None, timeout=10)
......
......@@ -2,13 +2,11 @@ import os
import pathlib
import random
import string
from pathlib import Path
from tempfile import mkdtemp
from typing import Any, Generator
import pytest
from python_spectrometer import Spectrometer, daq
from qutil.plotting import is_using_mpl_gui_backend
def remove_file_if_exists(file):
......@@ -25,22 +23,51 @@ def remove_dir_if_exists(d):
pass
def advance_frames(spect: Spectrometer, n_frames: int):
# Need to manually trigger events in headless mode. Else does nothing.
if not is_using_mpl_gui_backend(spect.fig):
# Run callbacks
for _ in range(n_frames):
for fun, args, kwargs in spect._plot_manager.timer.callbacks:
fun(*args, **kwargs)
@pytest.fixture(params=[True, False])
def relative_paths(request):
return request.param
@pytest.fixture(params=[True, False])
def spectrometer(monkeypatch, request) -> Generator[Spectrometer, Any, None]:
# patch input to answer overwrite queries with "yes"
def threaded_acquisition(request):
return request.param
@pytest.fixture(scope='function')
def spectrometer(monkeypatch, relative_paths: bool, threaded_acquisition: bool):
# patch input to answer overwrite queries with "yes". Disable this when debugging :)
monkeypatch.setattr('builtins.input', lambda: 'y')
speck = Spectrometer(daq.QoptColoredNoise(),
savepath=pathlib.Path(cwd := mkdtemp(), 'test_data'),
plot_cumulative=True,
relative_paths=request.param)
threaded_acquisition=threaded_acquisition,
relative_paths=relative_paths)
speck.savepath.mkdir(parents=True, exist_ok=True)
try:
os.chdir(speck.savepath)
speck.take('foo')
speck.take('baz', fs=1e3, nperseg=400)
# XXX: need to block to avoid timing issues when running tests
speck.take('foo', progress=False)
speck.block_until_ready()
if threaded_acquisition:
# Advance two frames in headless mode, otherwise data is not saved.
advance_frames(speck, 2)
speck.take('baz', fs=1e3, nperseg=400, progress=False)
speck.block_until_ready()
if threaded_acquisition:
advance_frames(speck, 2)
yield speck
finally:
......@@ -52,7 +79,7 @@ def spectrometer(monkeypatch, request) -> Generator[Spectrometer, Any, None]:
@pytest.fixture
def serialized(spectrometer: Spectrometer) -> Generator[Path, Any, None]:
def serialized(spectrometer: Spectrometer):
stem = ''.join(random.choices(string.ascii_letters, k=10))
try:
......@@ -69,9 +96,12 @@ def serialized(spectrometer: Spectrometer) -> Generator[Path, Any, None]:
remove_file_if_exists(spectrometer.savepath / f'{stem}{ext}')
def test_saving(spectrometer: Spectrometer):
def test_saving(spectrometer: Spectrometer, relative_paths: bool):
assert spectrometer.savepath.exists()
for file in spectrometer.files:
if relative_paths:
assert os.path.exists(spectrometer.savepath / file)
else:
assert os.path.exists(file)
......@@ -88,9 +118,9 @@ def test_serialization(spectrometer: Spectrometer):
def test_deserialization(serialized: pathlib.Path):
speck = Spectrometer.recall_from_disk(serialized)
for data, comment in zip(speck, ['foo', 'baz']):
deserialized = Spectrometer.recall_from_disk(serialized)
for data, comment in zip(deserialized, ['foo', 'baz']):
assert data['comment'] == comment
assert speck['baz']['settings']['fs'] == 1e3
assert speck['baz']['settings']['nperseg'] == 400
assert speck.plot_cumulative is True
assert deserialized['baz']['settings']['fs'] == 1e3
assert deserialized['baz']['settings']['nperseg'] == 400
assert deserialized.plot_cumulative is True
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment