refactor
This commit is contained in:
31
animation.py
Normal file
31
animation.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from matplotlib import pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
# expect from source:
|
||||||
|
# - get() -> value
|
||||||
|
# - name
|
||||||
|
# - add_callback(cb)
|
||||||
|
# - disconnect()
|
||||||
|
|
||||||
|
# plot_func takes result of source.get()
|
||||||
|
# returns Plot object
|
||||||
|
|
||||||
|
# Plot object has set(val)
|
||||||
|
|
||||||
|
|
||||||
|
class Animation:
|
||||||
|
|
||||||
|
def __init__(self, source, plot_func):
|
||||||
|
value = source.get()
|
||||||
|
self.plot = plot_func(value)
|
||||||
|
plt.suptitle(source.name)
|
||||||
|
source.add_callback(self.update)
|
||||||
|
plt.show()
|
||||||
|
source.disconnect()
|
||||||
|
|
||||||
|
def update(self, value=None, **kwargs):
|
||||||
|
self.plot.set(value)
|
||||||
|
plt.draw()
|
||||||
|
|
||||||
|
|
||||||
|
|
185
caplot.py
185
caplot.py
@ -11,193 +11,40 @@ parser.add_argument("pvname")
|
|||||||
clargs = parser.parse_args()
|
clargs = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
import numpy as np
|
from animation import Animation
|
||||||
from collections import deque
|
from plots import Plot0D, Plot1D, Plot2D
|
||||||
from time import sleep
|
from sources import Camera, Source
|
||||||
from functools import wraps
|
|
||||||
from types import SimpleNamespace
|
|
||||||
from matplotlib import pyplot as plt
|
|
||||||
from epics import PV
|
|
||||||
|
|
||||||
|
|
||||||
|
def decide_src_fun(pvname):
|
||||||
|
pvname = pvname.upper()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PVCollection:
|
|
||||||
|
|
||||||
def __init__(self, name, **kwargs):
|
|
||||||
self.name = name
|
|
||||||
# self.pvnames = SimpleNamespace(**kwargs)
|
|
||||||
pvs = {k: PV(v) for k, v in kwargs.items()}
|
|
||||||
for pv in pvs.values():
|
|
||||||
pv.wait_for_connection()
|
|
||||||
self.pvs = SimpleNamespace(**pvs)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Camera(PVCollection):
|
|
||||||
|
|
||||||
def __init__(self, pvname):
|
|
||||||
base = pvname.rsplit(":", 1)[0]
|
|
||||||
super().__init__(
|
|
||||||
base,
|
|
||||||
image = pvname,
|
|
||||||
width = base + ":WIDTH",
|
|
||||||
height = base + ":HEIGHT"
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
|
||||||
shape = (width, height)
|
|
||||||
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)
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
for pv in self.pvs.__dict__.values(): #TODO
|
|
||||||
pv.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Source(PV):
|
|
||||||
|
|
||||||
def __init__(self, pvname, *args, **kwargs):
|
|
||||||
self.name = pvname
|
|
||||||
super().__init__(pvname, *args, **kwargs)
|
|
||||||
self.wait_for_connection()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Animation:
|
|
||||||
|
|
||||||
def __init__(self, source, plot_func):
|
|
||||||
value = source.get()
|
|
||||||
self.plot = plot_func(value)
|
|
||||||
plt.suptitle(source.name)
|
|
||||||
source.add_callback(self.update)
|
|
||||||
plt.show()
|
|
||||||
source.disconnect()
|
|
||||||
|
|
||||||
def update(self, value=None, **kwargs):
|
|
||||||
self.plot.set(value)
|
|
||||||
plt.draw()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PlotScalar:
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.fig, axs = plt.subplots(1, 2)
|
|
||||||
self.ax_time, self.ax_hist = ax_time, ax_hist = axs
|
|
||||||
|
|
||||||
length = 100
|
|
||||||
# init = [0] + [np.nan] * (length-2) + [0]
|
|
||||||
# self.cache = cache = deque(init, maxlen=length)
|
|
||||||
self.cache = cache = deque(maxlen=length)
|
|
||||||
self.x = x = range(length)
|
|
||||||
|
|
||||||
# lines = ax_time.plot(x, cache)
|
|
||||||
lines = ax_time.plot([0], [0])
|
|
||||||
self.plot_time = lines[0] #TODO: wtf?
|
|
||||||
|
|
||||||
# plot_hist = ax_hist.bar(np.arange(0, 1, 100), [0] * 100)
|
|
||||||
# self.plot_hist = plot_hist
|
|
||||||
|
|
||||||
|
|
||||||
def set(self, value):
|
|
||||||
cache = self.cache
|
|
||||||
cache.append(value)
|
|
||||||
|
|
||||||
x = range(len(cache))
|
|
||||||
self.plot_time.set_data(x, cache)
|
|
||||||
nums = np.nan_to_num(cache)
|
|
||||||
# print(nums)
|
|
||||||
if min(nums) != max(nums):
|
|
||||||
self.ax_time.set_ylim(min(nums), max(nums))
|
|
||||||
|
|
||||||
self.ax_time.relim()
|
|
||||||
self.ax_time.autoscale_view()
|
|
||||||
|
|
||||||
|
|
||||||
nums = np.array(cache)
|
|
||||||
nums = nums[np.isfinite(nums)]
|
|
||||||
ys, xs = np.histogram(nums, bins="auto")
|
|
||||||
# print(ys)
|
|
||||||
# for rect, x, y in zip(self.plot_hist, xs, ys):
|
|
||||||
# rect.set_x(x)
|
|
||||||
# rect.set_height(y)
|
|
||||||
width = (xs[1:] - xs[:-1]).mean()
|
|
||||||
try:
|
|
||||||
self.plot_hist.remove()
|
|
||||||
color = self.plot_hist.patches[0].get_facecolor()
|
|
||||||
except:
|
|
||||||
color = None
|
|
||||||
|
|
||||||
plot_hist = self.ax_hist.bar(xs[:-1], ys, color=color,align="edge", width=width)
|
|
||||||
self.plot_hist = plot_hist
|
|
||||||
|
|
||||||
n = len(xs)
|
|
||||||
self.ax_hist.set_title(f"#bins: {n}")
|
|
||||||
|
|
||||||
self.ax_hist.relim()
|
|
||||||
self.ax_hist.autoscale_view()
|
|
||||||
|
|
||||||
|
|
||||||
class Plot1D:
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
lines = plt.plot(*args, **kwargs)
|
|
||||||
self.plot = lines[0] #TODO: wtf?
|
|
||||||
|
|
||||||
def set(self, value):
|
|
||||||
self.plot.set_ydata(value)
|
|
||||||
|
|
||||||
|
|
||||||
class Plot2D:
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.plot = plt.imshow(*args, **kwargs)
|
|
||||||
|
|
||||||
def set(self, value):
|
|
||||||
self.plot.set_data(value)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pvname = clargs.pvname
|
|
||||||
if pvname.endswith(":FPICTURE"):
|
if pvname.endswith(":FPICTURE"):
|
||||||
print("Camera", pvname)
|
print("Camera", pvname)
|
||||||
src = Camera(pvname)
|
src = Camera(pvname)
|
||||||
fun = Plot2D
|
fun = Plot2D
|
||||||
else:
|
return src, fun
|
||||||
|
|
||||||
print("Source", pvname)
|
print("Source", pvname)
|
||||||
src = Source(pvname)
|
src = Source(pvname)
|
||||||
print(src.nelm)
|
print(src.nelm)
|
||||||
if src.nelm == 0:
|
if src.nelm == 0:
|
||||||
raise SystemExit(f"{src}: {src.value}")
|
raise SystemExit(f"{src}: {src.value}")
|
||||||
|
|
||||||
if src.nelm == 1:
|
if src.nelm == 1:
|
||||||
print("Scalar")
|
print("Scalar")
|
||||||
fun = PlotScalar
|
fun = Plot0D
|
||||||
else:
|
else:
|
||||||
print("1D")
|
print("Curve")
|
||||||
fun = Plot1D
|
fun = Plot1D
|
||||||
|
|
||||||
|
return src, fun
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pvname = clargs.pvname
|
||||||
|
src, fun = decide_src_fun(pvname)
|
||||||
Animation(src, fun)
|
Animation(src, fun)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
94
plot0d.py
Normal file
94
plot0d.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
from collections import deque
|
||||||
|
import numpy as np
|
||||||
|
from matplotlib import pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Cache:
|
||||||
|
|
||||||
|
def __init__(self, size=100):
|
||||||
|
self.data = deque(maxlen=size)
|
||||||
|
|
||||||
|
def append(self, value):
|
||||||
|
self.data.append(value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def x(self):
|
||||||
|
length = len(self.data)
|
||||||
|
return np.arange(length)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def y(self):
|
||||||
|
return np.array(self.data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xy(self):
|
||||||
|
return self.x, self.y
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def hist(values, bins="auto"):
|
||||||
|
ys, xs = np.histogram(values, bins="auto")
|
||||||
|
width = (xs[1:] - xs[:-1]).mean()
|
||||||
|
xs = xs[:-1] # remove closing edge
|
||||||
|
length = len(xs)
|
||||||
|
return xs, ys, width, length
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Plot0D:
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.fig, axs = plt.subplots(1, 2)
|
||||||
|
self.ax_time, self.ax_hist = ax_time, ax_hist = axs
|
||||||
|
|
||||||
|
self.cache = Cache()
|
||||||
|
|
||||||
|
lines = ax_time.plot([0], [0])
|
||||||
|
self.plot_time = lines[0] #TODO: wtf?
|
||||||
|
|
||||||
|
self.color = None # this will store the color after the first hist
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def set(self, value):
|
||||||
|
cache = self.cache
|
||||||
|
cache.append(value)
|
||||||
|
x, y = cache.xy
|
||||||
|
|
||||||
|
|
||||||
|
self.plot_time.set_data(x, y)
|
||||||
|
|
||||||
|
ax_time = self.ax_time
|
||||||
|
|
||||||
|
ymin, ymax = min(y), max(y)
|
||||||
|
if ymin != ymax:
|
||||||
|
ax_time.set_ylim(ymin, ymax)
|
||||||
|
|
||||||
|
ax_time.relim()
|
||||||
|
ax_time.autoscale_view()
|
||||||
|
|
||||||
|
|
||||||
|
ax_hist = self.ax_hist
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.plot_hist.remove()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
xs, ys, width, length = hist(y)
|
||||||
|
|
||||||
|
plot_hist = ax_hist.bar(xs, ys, align="edge", width=width, color=self.color)
|
||||||
|
self.plot_hist = plot_hist
|
||||||
|
|
||||||
|
self.color = plot_hist.patches[0].get_facecolor()
|
||||||
|
|
||||||
|
ax_hist.set_title(f"#bins: {length}")
|
||||||
|
|
||||||
|
ax_hist.relim()
|
||||||
|
ax_hist.autoscale_view()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
15
plot1d.py
Normal file
15
plot1d.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from matplotlib import pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
class Plot1D:
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
lines = plt.plot(*args, **kwargs)
|
||||||
|
assert len(lines) == 1
|
||||||
|
self.plot = lines[0] #TODO: wtf?
|
||||||
|
|
||||||
|
def set(self, value):
|
||||||
|
self.plot.set_ydata(value)
|
||||||
|
|
||||||
|
|
||||||
|
|
13
plot2d.py
Normal file
13
plot2d.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from matplotlib import pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
class Plot2D:
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.plot = plt.imshow(*args, **kwargs)
|
||||||
|
|
||||||
|
def set(self, value):
|
||||||
|
self.plot.set_data(value)
|
||||||
|
|
||||||
|
|
||||||
|
|
6
plots.py
Normal file
6
plots.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
from plot0d import Plot0D
|
||||||
|
from plot1d import Plot1D
|
||||||
|
from plot2d import Plot2D
|
||||||
|
|
||||||
|
|
78
sources.py
Normal file
78
sources.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
shape = (width, height)
|
||||||
|
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