first proper prototype

This commit is contained in:
2021-05-19 14:34:07 +02:00
parent 62e8cea4d6
commit 678b1f48a7
5 changed files with 273 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
from collections import deque
from time import time
class Cacher:
def __init__(self):
self.caches = {}
def add(self, name, size=100):
self.caches[name] = cache = Cache(size=size)
return cache
class Cache:
def __init__(self, size=100):
self.times = deque(maxlen=size)
self.values = deque(maxlen=size)
def append_callback(self, value=None, **kwargs):
self.append(value)
def append(self, value):
self.times.append(time())
self.values.append(value)
@property
def xy(self):
return self.times, self.values
cacher = Cacher()
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python
from random import choice
from bokeh.plotting import curdoc
from bokeh.layouts import column
from cacher import cacher
from plot0d import Plot0D
from plot2d import Plot2D
from sources import Camera, Source
name0 = "MTEST:RAND0"
src0 = Source(name0)
cache0 = cacher.add(name0, size=100)
cache0.append(src0.get())
src0.add_callback(cache0.append_callback)
name2 = "MTEST:CHAN-IMAGE:FPICTURE"
src2 = Camera(name2)
cache2 = cacher.add(name2, size=1)
cache2.append(src2.get())
src2.add_callback(cache2.append_callback)
p0 = Plot0D()
p2 = Plot2D()
def update():
p0.set(*cache0.xy)
p2.set(cache2.values[0])
doc = curdoc()
doc.title = choice("👺👹") + " kabuki"
doc.add_periodic_callback(update, 1000)
plt = column(
p0.fig,
p2.fig
)
doc.add_root(plt)
+74
View File
@@ -0,0 +1,74 @@
import numpy as np
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.models import ColumnDataSource
class Plot0D:
def __init__(self):
self.timeseries = timeseries = TimeSeries()
self.histo = histo = Histo()
self.fig = row(timeseries.fig, histo.fig)
def set(self, times, values):
self.timeseries.set(times, values)
self.histo.set(values)
class TimeSeries:
def __init__(self):
data = {
"x": [],
"y": []
}
self.source = source = ColumnDataSource(data=data)
self.fig = fig = figure()
fig.step(x="x", y="y", source=source, mode="after")
fig.circle(x="x", y="y", source=source)
def set(self, times, values):
data = {
"x": times,
"y": values
}
self.source.data.update(data)
class Histo:
def __init__(self):
data = {
"left": [],
"right": [],
"top": []
}
self.source = source = ColumnDataSource(data)
self.fig = fig = figure()
fig.quad(left="left", right="right", top="top", bottom=0, source=source)
def set(self, values):
data = hist(values)
self.source.data.update(data)
def hist(values, bins="auto"):
counts, edges = np.histogram(values, bins="auto")
left = edges[:-1]
right = edges[1:]
return dict(left=left, right=right, top=counts)
+24
View File
@@ -0,0 +1,24 @@
import numpy as np
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
class Plot2D:
def __init__(self, *args, **kwargs):
data = {
"image": [np.zeros((1, 1))]
}
self.source = source = ColumnDataSource(data=data)
self.fig = fig = figure()
fig.image(source=source, x=0, y=0, dw=1, dh=1, palette="Spectral11")
def set(self, image):
self.source.data.update(image=[image])
+81
View File
@@ -0,0 +1,81 @@
from functools import wraps
from types import SimpleNamespace
from epics import PV
class Source(PV):
"""
PV, but waits for connection on creation
"""
def __init__(self, pvname, *args, **kwargs):
self.name = pvname
super().__init__(pvname, *args, **kwargs)
self.wait_for_connection()
class PVCollection:
"""
Creates all PVs, then waits for connection
Disconnects all PVs
"""
def __init__(self, name, **kwargs):
self.name = name
# self.pvnames = SimpleNamespace(**kwargs)
self.pv_dict = pv_dict = {k: PV(v) for k, v in kwargs.items()}
for pv in pv_dict.values():
pv.wait_for_connection()
self.pvs = SimpleNamespace(**pv_dict)
def __str__(self):
return self.name
def disconnect(self):
for pv in self.pv_dict.values(): #TODO is it better to use: pv_dict -> pvs.__dict__
pv.disconnect()
class Camera(PVCollection):
"""
Expects: pvname = "NAME:FPICTURE"
Also creates NAME:WIDTH and NAME:HEIGHT
"""
#TODO: could also accept base as argument, and also append ":FPICTURE"
def __init__(self, pvname):
assert pvname.endswith(":FPICTURE")
base = pvname.rsplit(":", 1)[0]
super().__init__(
base,
image = pvname,
width = base + ":WIDTH",
height = base + ":HEIGHT"
)
self.pvs.image.auto_monitor = True
def get(self):
data = self.pvs.image.get()
data.shape = self.get_shape()
return data
def get_shape(self):
width = self.pvs.width.get()
height = self.pvs.height.get()
width = int(round(width))
height = int(round(height))
shape = (height, width)
return shape
def add_callback(self, callback, **kwargs):
@wraps(callback)
def wrapper(*args, value=None, **kwargs):
value.shape = self.get_shape()
return callback(*args, value=value, **kwargs)
return self.pvs.image.add_callback(callback=wrapper, with_ctrlvars=False, **kwargs)