added nicegui plotting

This commit is contained in:
2024-06-06 19:21:24 +02:00
parent 409ddd22e8
commit 653eeafd2c
3 changed files with 200 additions and 33 deletions
+72 -32
View File
@@ -13,9 +13,16 @@ import numpy as np
from bsread import dispatcher, source
import plotext as plt
import epics
try:
from nicegui import ui, app, context, run
except ImportError as e:
print("nicegui not available, falling back to console plot.")
print(e)
from plots import ConsolePlot, PlotlyPlot
def clean_name(fname):
"""Returns the filename with all special characters but / . _ - #
@@ -156,6 +163,18 @@ def yield_pv_data(channel_names: list[str]):
yield channel_data
def yield_test_data(channel_names: list[str]):
"""Yields test data from the given channels with timestamp."""
import random
import time
while True:
channel_data = {
channel: {"value": random.random(), "timestamp": time.time()} for channel in channel_names
}
yield channel_data
def roundrobin(*iterables):
"Visit input iterables in a cycle until each is exhausted."
# roundrobin('ABC', 'D', 'EF') → A D E B F C
@@ -174,7 +193,10 @@ def yield_data(channel_names: list, only_pv=False):
"""
bs_channels = dispatcher.get_current_channels()
# separate into BS and PV channels
# separate into BS, PV and test channels
test_list = [ch for ch in channel_names if ch.startswith("TEST_CHANNEL")]
for test_ch in test_list:
channel_names.remove(test_ch)
if only_pv:
pv_list = channel_names
@@ -183,7 +205,9 @@ def yield_data(channel_names: list, only_pv=False):
bs_list = [ch for ch in channel_names if ch in bs_channels]
pv_list = [ch for ch in channel_names if ch not in bs_list]
for channel_data in roundrobin(yield_bs_data(bs_list), yield_pv_data(pv_list)):
for channel_data in roundrobin(
yield_bs_data(bs_list), yield_pv_data(pv_list), yield_test_data(test_list)
):
yield channel_data
@@ -203,34 +227,16 @@ def collect_data(channels: dict):
yield channels
def plot(channels, start_time=0, in_subplots=False, marker="hd"):
"""Plots channels on the console."""
# plt.clf()
plt.limit_size(False, False)
plt.theme("pro")
plt.xlabel("Timestamp")
plt.ylabel("Value")
num_channels = len(channels)
if in_subplots:
plt.subplots(num_channels, 1)
plt.main().plot_size(plt.tw(), plt.th() - 1)
plt.clear_data()
for i, channel in enumerate(channels.values()):
if in_subplots:
plt.subplot(i + 1, 1)
plt.plot(channel.x - start_time, channel.y, label=channel.name, marker=marker)
plt.show()
async def runner(
channels: dict, acquire_interval=0.1, plot_interval=1, in_subplots=False, abort=False, relative_time=True
channels: dict,
plot_interface,
acquire_interval=0.1,
plot_interval=1,
in_subplots=False,
abort=False,
relative_time=True,
):
"""
Loop for data collection and plotting.
@@ -248,6 +254,8 @@ async def runner(
last_collect_time = time.time()
last_plot_time = time.time()
plot = plot_interface
while not abort:
now = time.time()
elapsed_collect = now - last_collect_time
@@ -258,7 +266,7 @@ async def runner(
last_collect_time = time.time()
if elapsed_plot > plot_interval: # default: 1 Hz plot updates and save interval
plot(channels, in_subplots=in_subplots, start_time=start_time)
plot.update_plot(channels, in_subplots=in_subplots, start_time=start_time)
for channel in channels.values():
channel.trigger_save()
@@ -276,18 +284,50 @@ async def runner(
@click.option("--plot-interval", default=1, help="Interval in seconds for plot update.")
@click.option("--save-prefix", default=None, help="Prefix for the save filenames.")
@click.option("--in-subplots", default=False, type=bool, help="Whether to plot in subplots.")
@click.option(
"--web", is_flag=True, default=False, type=bool, help="Whether to use the local web interface as a GUI."
)
def main(
channel_names: Tuple[str, ...],
acquire_interval=0.1,
plot_interval=1,
save_prefix: Optional[str] = None,
in_subplots=False,
web=False,
):
""" Plot channels from BS (beam synchronous) or PV (EPICS) sources. """
"""Plot channels from BS (beam synchronous) or PV (EPICS) sources."""
channels = {channel: ChannelData(channel, save_file_prefix=save_prefix) for channel in channel_names}
asyncio.run(runner(channels, acquire_interval=float(acquire_interval), plot_interval=plot_interval, in_subplots=in_subplots))
start_time = time.time()
if not web: # console plot version
plot = ConsolePlot(channels, start_time=start_time, in_subplots=in_subplots)
asyncio.run(
runner(
channels,
plot,
acquire_interval=float(acquire_interval),
plot_interval=plot_interval,
in_subplots=in_subplots,
)
)
else: # nicegui version
plot = PlotlyPlot(channels, start_time=start_time, in_subplots=in_subplots)
async def run_async():
await runner(
channels,
plot,
acquire_interval=float(acquire_interval),
plot_interval=plot_interval,
in_subplots=in_subplots,
)
app.on_startup(run_async)
ui.run(reload=False, port=8004)
if __name__ == "__main__":
main()