first proper prototype
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user