ultrasound: reworked after tests
- new classes in frappy_psi/ultrasound.py and frappy_psi/adq.mr.py - add signal plottter - move clients to bin/ directory Change-Id: I8db8e5ebc082c346278f09e0e54504e070655f14
This commit is contained in:
parent
322cd39e0a
commit
6f547f0781
46
bin/peus-plot
Normal file
46
bin/peus-plot
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
from frappy.client.interactive import Client
|
||||||
|
from frappy_psi.iqplot import Plot
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print('Usage: python peusplot.py <maxY>')
|
||||||
|
|
||||||
|
|
||||||
|
def get_modules(name):
|
||||||
|
return list(filter(None, (globals().get(name % i) for i in range(10))))
|
||||||
|
|
||||||
|
|
||||||
|
secnode = Client('localhost:5000')
|
||||||
|
time_size = {'time', 'size'}
|
||||||
|
int_mods = [u] + get_modules('roi%d')
|
||||||
|
t_rois = get_modules('roi%d')
|
||||||
|
i_rois = get_modules('roi%di')
|
||||||
|
q_rois = get_modules('roi%dq')
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
maxy = float(sys.argv[1])
|
||||||
|
else:
|
||||||
|
maxy = 0.02
|
||||||
|
|
||||||
|
|
||||||
|
iqplot = Plot(maxy)
|
||||||
|
|
||||||
|
for i in range(99):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
curves = np.array(u.get_curves())
|
||||||
|
iqplot.plot(curves,
|
||||||
|
rois=[(r.time, r.time + r.size) for r in int_mods],
|
||||||
|
average=([r.time for r in t_rois],
|
||||||
|
[r.value for r in i_rois],
|
||||||
|
[r.value for r in q_rois]))
|
||||||
|
plt.pause(0.5)
|
||||||
|
if iqplot.fig is None: # graph window closed
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
iqplot.close()
|
60
bin/us-plot
Normal file
60
bin/us-plot
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
from frappy.client.interactive import Client
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
Client('pc13252:5000')
|
||||||
|
|
||||||
|
|
||||||
|
def plot(array, ax, style, xs):
|
||||||
|
xaxis = np.arange(len(array)) * xs
|
||||||
|
return ax.plot(xaxis, array, style)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def update(array, line, xs):
|
||||||
|
xaxis = np.arange(len(array)) * xs
|
||||||
|
line.set_data(np.array([xaxis, array]))
|
||||||
|
|
||||||
|
def on_close(event):
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# inp_signal.read()
|
||||||
|
# out_signal.read()
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("""
|
||||||
|
Usage: python3 sig.py <end> [<start> [<npoints>]]
|
||||||
|
|
||||||
|
end: end of window [ns]
|
||||||
|
start: start of window [n2], default: 0
|
||||||
|
npoints: number fo points (default 1000)
|
||||||
|
""")
|
||||||
|
else:
|
||||||
|
start = 0
|
||||||
|
end = float(sys.argv[1])
|
||||||
|
npoints = 1000
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
start = float(sys.argv[2])
|
||||||
|
if len(sys.argv) > 3:
|
||||||
|
npoints = float(sys.argv[3])
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(15,3))
|
||||||
|
fig.canvas.mpl_connect('close_event', on_close)
|
||||||
|
try:
|
||||||
|
get_signal = iq.get_signal
|
||||||
|
print('plotting RUS signal')
|
||||||
|
except NameError:
|
||||||
|
get_signal = u.get_signal
|
||||||
|
print('plotting PE signal')
|
||||||
|
|
||||||
|
xs, signal = get_signal(start, end, npoints)
|
||||||
|
|
||||||
|
lines = [plot(s, ax, '-', xs) for s in signal]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
plt.pause(0.5)
|
||||||
|
plt.draw()
|
||||||
|
xs, signal = get_signal(start, end, npoints)
|
||||||
|
for line, sig in zip(lines, signal):
|
||||||
|
update(sig, line, xs)
|
87
cfg/PEUS_cfg.py
Normal file
87
cfg/PEUS_cfg.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
Node('pulse_echo.cfg',
|
||||||
|
'ultrasound, pulse_echo configuration',
|
||||||
|
interface='5000',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('u',
|
||||||
|
'frappy_psi.ultrasound.PulseEcho',
|
||||||
|
'ultrasound acquisition loop',
|
||||||
|
freq='f',
|
||||||
|
# pollinterval=0.1,
|
||||||
|
time=900.0,
|
||||||
|
size=5000.0,
|
||||||
|
nr=500,
|
||||||
|
sr=32768,
|
||||||
|
bw=1e7,
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('fio',
|
||||||
|
'frappy_psi.ultrasound.FreqStringIO', '',
|
||||||
|
uri='serial:///dev/ttyS1?baudrate=57600',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('f',
|
||||||
|
'frappy_psi.ultrasound.Frequency',
|
||||||
|
'writable for frequency',
|
||||||
|
output='R', # L for LF (bnc), R for RF (type N)
|
||||||
|
io='fio',
|
||||||
|
amp=0.5, # VPP
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('fdif',
|
||||||
|
'frappy_psi.ultrasound.FrequencyDif',
|
||||||
|
'writable for frequency minus base frequency',
|
||||||
|
freq='f',
|
||||||
|
base=41490200.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mod('curves',
|
||||||
|
# 'frappy_psi.ultrasound.Curves',
|
||||||
|
# 't, I, Q and pulse arrays for plot',
|
||||||
|
# )
|
||||||
|
|
||||||
|
def roi(name, time, size, components='iqpa', enable=True, control=False, freq=None, **kwds):
|
||||||
|
description = 'I/Q of region {name}'
|
||||||
|
if freq:
|
||||||
|
kwds.update(cls='frappy_psi.ultrasound.ControlRoi',
|
||||||
|
description=f'{description} as control loop',
|
||||||
|
freq=freq, **kwds)
|
||||||
|
else:
|
||||||
|
kwds.update(cls='frappy_psi.ultrasound.Roi',
|
||||||
|
description=description, **kwds)
|
||||||
|
kwds.update({c: name + c for c in components})
|
||||||
|
Mod(name,
|
||||||
|
main='u',
|
||||||
|
time=time,
|
||||||
|
size=size,
|
||||||
|
enable=enable,
|
||||||
|
**kwds,
|
||||||
|
)
|
||||||
|
for c in components:
|
||||||
|
Mod(name + c,
|
||||||
|
'frappy.modules.Readable',
|
||||||
|
f'{name}{c} component',
|
||||||
|
)
|
||||||
|
|
||||||
|
# control loop
|
||||||
|
roi('roi0', 2450, 300, freq='f', maxstep=100000, minstep=4000)
|
||||||
|
# other rois
|
||||||
|
roi('roi1', 5950, 300)
|
||||||
|
roi('roi2', 9475, 300)
|
||||||
|
roi('roi3', 12900, 300)
|
||||||
|
#roi('roi4', 400, 30, False)
|
||||||
|
#roi('roi5', 400, 30, False)
|
||||||
|
#roi('roi6', 400, 30, False)
|
||||||
|
#roi('roi7', 400, 30, False)
|
||||||
|
#roi('roi8', 400, 30, False)
|
||||||
|
#roi('roi9', 400, 30, False)
|
||||||
|
|
||||||
|
Mod('delay',
|
||||||
|
'frappy_psi.dg645.Delay',
|
||||||
|
'delay line with 2 channels',
|
||||||
|
uri='serial:///dev/ttyS2',
|
||||||
|
on1=1e-09,
|
||||||
|
on2=1e-09,
|
||||||
|
off1=4e-07,
|
||||||
|
off2=6e-07,
|
||||||
|
)
|
39
cfg/RUS_cfg.py
Normal file
39
cfg/RUS_cfg.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
Node(equipment_id = 'r_ultrasound.psi.ch',
|
||||||
|
description = 'resonant ultra sound setup',
|
||||||
|
interface = 'tcp://5000',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('iq',
|
||||||
|
cls = 'frappy_psi.ultrasound.RUS',
|
||||||
|
description = 'ultrasound iq mesurement',
|
||||||
|
imod = 'i',
|
||||||
|
qmod = 'q',
|
||||||
|
freq='f',
|
||||||
|
input_range=10, # VPP
|
||||||
|
input_delay = 0,
|
||||||
|
periods = 163,
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('freqio',
|
||||||
|
'frappy_psi.ultrasound.FreqStringIO',
|
||||||
|
' ',
|
||||||
|
uri = 'serial:///dev/ttyS1?baudrate=57600',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('f',
|
||||||
|
cls = 'frappy_psi.ultrasound.Frequency',
|
||||||
|
description = 'ultrasound frequency',
|
||||||
|
io='freqio',
|
||||||
|
output='L', # L for LF (bnc), R for RF (type N)
|
||||||
|
target=10000,
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('i',
|
||||||
|
cls='frappy.modules.Readable',
|
||||||
|
description='I component',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('q',
|
||||||
|
cls='frappy.modules.Readable',
|
||||||
|
description='Q component',
|
||||||
|
)
|
@ -50,6 +50,27 @@ GHz = 1e9
|
|||||||
RMS_TO_VPP = 2 * np.sqrt(2)
|
RMS_TO_VPP = 2 * np.sqrt(2)
|
||||||
|
|
||||||
|
|
||||||
|
class Timer:
|
||||||
|
def __init__(self):
|
||||||
|
self.data = [(time.time(), 'start')]
|
||||||
|
|
||||||
|
def __call__(self, text=''):
|
||||||
|
now = time.time()
|
||||||
|
prev = self.data[-1][0]
|
||||||
|
self.data.append((now, text))
|
||||||
|
return now - prev
|
||||||
|
|
||||||
|
def summary(self):
|
||||||
|
return ' '.join(f'{txt} {tim:.3f}' for tim, txt in self.data[1:])
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
first = prev = self.data[0][0]
|
||||||
|
print('---', first)
|
||||||
|
for tim, txt in self.data[1:]:
|
||||||
|
print(f'{(tim - first) * 1000:9.3f} {(tim - prev) * 1000:9.3f} ms {txt}')
|
||||||
|
prev = tim
|
||||||
|
|
||||||
|
|
||||||
class Adq:
|
class Adq:
|
||||||
sample_rate = 2 * GHz
|
sample_rate = 2 * GHz
|
||||||
max_number_of_channels = 2
|
max_number_of_channels = 2
|
||||||
@ -161,7 +182,6 @@ class Adq:
|
|||||||
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
|
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
|
||||||
ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num)
|
ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num)
|
||||||
self.data = data
|
self.data = data
|
||||||
self.starttime = time.time()
|
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
"""get new data if available"""
|
"""get new data if available"""
|
||||||
@ -173,12 +193,14 @@ class Adq:
|
|||||||
|
|
||||||
if ADQAPI.ADQ_GetAcquiredAll(self.adq_cu, self.adq_num):
|
if ADQAPI.ADQ_GetAcquiredAll(self.adq_cu, self.adq_num):
|
||||||
ready = True
|
ready = True
|
||||||
|
data.timer('ready')
|
||||||
else:
|
else:
|
||||||
if self.trigger == SW_TRIG:
|
if self.trigger == SW_TRIG:
|
||||||
ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num)
|
ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num)
|
||||||
if not ready:
|
if not ready:
|
||||||
self.busy = True
|
self.busy = True
|
||||||
return None
|
return None
|
||||||
|
self.data = None
|
||||||
t = time.time()
|
t = time.time()
|
||||||
# Get data from ADQ
|
# Get data from ADQ
|
||||||
if not ADQAPI.ADQ_GetData(
|
if not ADQAPI.ADQ_GetData(
|
||||||
@ -187,7 +209,6 @@ class Adq:
|
|||||||
0, self.number_of_records, ADQ_CHANNELS_MASK,
|
0, self.number_of_records, ADQ_CHANNELS_MASK,
|
||||||
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL):
|
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL):
|
||||||
raise RuntimeError('no success from ADQ_GetDATA')
|
raise RuntimeError('no success from ADQ_GetDATA')
|
||||||
self.data = None
|
|
||||||
data.retrieve(self)
|
data.retrieve(self)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -197,12 +218,20 @@ class PEdata:
|
|||||||
self.sample_rate = adq.sample_rate
|
self.sample_rate = adq.sample_rate
|
||||||
self.samp_freq = self.sample_rate / GHz
|
self.samp_freq = self.sample_rate / GHz
|
||||||
self.number_of_records = adq.number_of_records
|
self.number_of_records = adq.number_of_records
|
||||||
|
self.timer = Timer()
|
||||||
|
|
||||||
|
def retrieve(self, adq):
|
||||||
data = []
|
data = []
|
||||||
|
rawsignal = []
|
||||||
for ch in range(2):
|
for ch in range(2):
|
||||||
onedim = np.frombuffer(adq.target_buffers[ch].contents, dtype=np.int16)
|
onedim = np.frombuffer(adq.target_buffers[ch].contents, dtype=np.int16)
|
||||||
data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2**14)) # 14 bits ADC
|
rawsignal.append(onedim[:adq.samples_per_record])
|
||||||
|
# convert 16 bit int to a value in the range -1 .. 1
|
||||||
|
data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2 ** 15))
|
||||||
# Now this is an array with all records, but the time is artificial
|
# Now this is an array with all records, but the time is artificial
|
||||||
self.data = data
|
self.data = data
|
||||||
|
self.rawsignal = rawsignal
|
||||||
|
self.timer('retrieved')
|
||||||
|
|
||||||
def sinW(self, sig, freq, ti, tf):
|
def sinW(self, sig, freq, ti, tf):
|
||||||
# sig: signal array
|
# sig: signal array
|
||||||
@ -232,6 +261,7 @@ class PEdata:
|
|||||||
wave1 = sigout * (a * np.cos(2*np.pi*freq*t) + b * np.sin(2*np.pi*freq*t))
|
wave1 = sigout * (a * np.cos(2*np.pi*freq*t) + b * np.sin(2*np.pi*freq*t))
|
||||||
wave2 = sigout * (a * np.sin(2*np.pi*freq*t) - b * np.cos(2*np.pi*freq*t))
|
wave2 = sigout * (a * np.sin(2*np.pi*freq*t) - b * np.cos(2*np.pi*freq*t))
|
||||||
return wave1, wave2
|
return wave1, wave2
|
||||||
|
|
||||||
def averageiq(self, data, freq, ti, tf):
|
def averageiq(self, data, freq, ti, tf):
|
||||||
"""Average over records"""
|
"""Average over records"""
|
||||||
iorq = np.array([self.mix(data[0][i], data[1][i], freq, ti, tf) for i in range(self.number_of_records)])
|
iorq = np.array([self.mix(data[0][i], data[1][i], freq, ti, tf) for i in range(self.number_of_records)])
|
||||||
@ -254,24 +284,32 @@ class PEdata:
|
|||||||
|
|
||||||
def gates_and_curves(self, freq, pulse, roi, bw_cutoff):
|
def gates_and_curves(self, freq, pulse, roi, bw_cutoff):
|
||||||
"""return iq values of rois and prepare plottable curves for iq"""
|
"""return iq values of rois and prepare plottable curves for iq"""
|
||||||
self.ndecimate = int(round(self.sample_rate / freq))
|
self.timer('gates')
|
||||||
# times = []
|
try:
|
||||||
# times.append(('aviq', time.time()))
|
self.ndecimate = int(round(self.sample_rate / freq))
|
||||||
|
except TypeError as e:
|
||||||
|
raise TypeError(f'{self.sample_rate}/{freq} {e}')
|
||||||
iq = self.averageiq(self.data, freq / GHz, *pulse)
|
iq = self.averageiq(self.data, freq / GHz, *pulse)
|
||||||
# times.append(('filtro', time.time()))
|
self.timer('aviq')
|
||||||
iqf = self.filtro(iq, bw_cutoff)
|
iqf = self.filtro(iq, bw_cutoff)
|
||||||
m = len(iqf[0]) // self.ndecimate
|
self.timer('filtro')
|
||||||
|
m = max(1, len(iqf[0]) // self.ndecimate)
|
||||||
ll = m * self.ndecimate
|
ll = m * self.ndecimate
|
||||||
iqf = [iqfx[0:ll] for iqfx in iqf]
|
iqf = [iqfx[0:ll] for iqfx in iqf]
|
||||||
# times.append(('iqdec', time.time()))
|
self.timer('iqf')
|
||||||
iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2)
|
iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2)
|
||||||
|
self.timer('avg')
|
||||||
t_axis = np.arange(m) * self.ndecimate / self.samp_freq
|
t_axis = np.arange(m) * self.ndecimate / self.samp_freq
|
||||||
pulsig = np.abs(self.data[0][0])
|
pulsig = np.abs(self.data[0][0])
|
||||||
# times.append(('pulsig', time.time()))
|
self.timer('pulsig')
|
||||||
pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1)
|
pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1)
|
||||||
self.curves = (t_axis, iqd[0], iqd[1], pulsig)
|
result = ([self.box(iqf, *r) for r in roi], # gates
|
||||||
# print(times)
|
(t_axis, iqd[0], iqd[1], pulsig)) # curves
|
||||||
return [self.box(iqf, *r) for r in roi]
|
self.timer('result')
|
||||||
|
# self.timer.show()
|
||||||
|
# ns = len(self.rawsignal[0]) * self.number_of_records
|
||||||
|
# print(f'{ns} {ns / 2e6} ms')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class Namespace:
|
class Namespace:
|
||||||
@ -290,24 +328,40 @@ class RUSdata:
|
|||||||
self.inp = Namespace(idx=0, name='input')
|
self.inp = Namespace(idx=0, name='input')
|
||||||
self.out = Namespace(idx=1, name='output')
|
self.out = Namespace(idx=1, name='output')
|
||||||
self.channels = (self.inp, self.out)
|
self.channels = (self.inp, self.out)
|
||||||
|
self.timer = Timer()
|
||||||
|
|
||||||
def retrieve(self, adq):
|
def retrieve(self, adq):
|
||||||
|
self.timer('start retrieve')
|
||||||
npts = self.samples_per_record - self.delay_samples
|
npts = self.samples_per_record - self.delay_samples
|
||||||
complex_sinusoid = np.exp(1j * 2 * np.pi * self.freq / self.sample_rate * np.arange(npts))
|
nbin = max(1, npts // (self.periods * 60)) # for performance reasons, do the binning first
|
||||||
|
nreduced = npts // nbin
|
||||||
|
ft = 2 * np.pi * self.freq * nbin / self.sample_rate * np.arange(nreduced)
|
||||||
|
self.timer('create time axis')
|
||||||
|
# complex_sinusoid = np.exp(1j * ft) # do not use this, below is 33 % faster
|
||||||
|
complex_sinusoid = 1j * np.sin(ft) + np.cos(ft)
|
||||||
|
self.timer('sinusoid')
|
||||||
|
|
||||||
|
rawsignal = [] # for raw plot
|
||||||
for chan in self.channels: # looping over input and output
|
for chan in self.channels: # looping over input and output
|
||||||
# although the ADC is only 14 bit it is representet as unsigend 16 bit numbers,
|
# although the ADC is only 14 bit it is represented as unsigend 16 bit numbers,
|
||||||
# and due to some calculations (calibration) the last 2 bits are not zero
|
# and due to some calculations (calibration) the last 2 bits are not zero
|
||||||
isignal = np.frombuffer(adq.target_buffers[chan.idx].contents, dtype=np.int16)[self.delay_samples:]
|
beg = self.delay_samples
|
||||||
# importatn convert to float first, before doing any calculations.
|
isignal = np.frombuffer(adq.target_buffers[chan.idx].contents, dtype=np.int16)[beg:beg+nreduced * nbin]
|
||||||
# calculations with int16 may silently overflow
|
self.timer('isignal')
|
||||||
chan.signal = isignal / float(2 ** 16) # in V -> peak to peak 1 V ~ +- 0.5 V
|
reduced = isignal.reshape((-1, nbin)).mean(axis=1) # this converts also int16 to float
|
||||||
chan.ovr_rate = (np.abs(isignal) > 32000).sum() / npts # fraction of points near end of range
|
self.timer('reduce')
|
||||||
|
rawsignal.append(reduced)
|
||||||
|
chan.signal = signal = reduced * 2 ** -16 # in V -> peak to peak 1 V ~ +- 0.5 V
|
||||||
|
self.timer('divide')
|
||||||
# calculate RMS * sqrt(2) -> peak sinus amplitude.
|
# calculate RMS * sqrt(2) -> peak sinus amplitude.
|
||||||
# may be higher than the input range by a factor 1.4 when heavily clipped
|
# may be higher than the input range by a factor 1.4 when heavily clipped
|
||||||
signal = chan.signal - np.median(chan.signal)
|
|
||||||
chan.amplitude = np.sqrt((signal ** 2).mean()) * RMS_TO_VPP
|
chan.amplitude = np.sqrt((signal ** 2).mean()) * RMS_TO_VPP
|
||||||
|
self.timer('amp')
|
||||||
chan.mixed = signal * complex_sinusoid
|
chan.mixed = signal * complex_sinusoid
|
||||||
|
self.timer('mix')
|
||||||
chan.mean = chan.mixed.mean()
|
chan.mean = chan.mixed.mean()
|
||||||
|
self.timer('mean')
|
||||||
|
self.rawsignal = rawsignal
|
||||||
if self.inp.mean:
|
if self.inp.mean:
|
||||||
self.iq = self.out.mean / self.inp.mean
|
self.iq = self.out.mean / self.inp.mean
|
||||||
else:
|
else:
|
||||||
@ -324,15 +378,19 @@ class RUSdata:
|
|||||||
the imaginary part indicates a turning phase (rad/sec)
|
the imaginary part indicates a turning phase (rad/sec)
|
||||||
the real part indicates changes in amplitude (0.01 ~= 1%/sec)
|
the real part indicates changes in amplitude (0.01 ~= 1%/sec)
|
||||||
"""
|
"""
|
||||||
|
self.timer('get_quality')
|
||||||
npts = len(self.channels[0].signal)
|
npts = len(self.channels[0].signal)
|
||||||
nper = npts // self.periods
|
nper = npts // self.periods
|
||||||
nbin = 50 # 50 samples, 25 ns
|
|
||||||
for chan in self.channels:
|
for chan in self.channels:
|
||||||
mean = chan.mixed.mean()
|
mean = chan.mixed.mean()
|
||||||
chan.reduced = chan.mixed[:self.periods * nper].reshape((-1, nper)).mean(axis=1) / mean
|
chan.reduced = chan.mixed[:self.periods * nper].reshape((-1, nper)).mean(axis=1) / mean
|
||||||
chan.binned = chan.signal[:npts // nbin * nbin].reshape((-1, nbin)).mean(axis=1)
|
|
||||||
|
|
||||||
timeaxis = np.arange(len(self.out.reduced)) * self.sample_rate / self.freq
|
timeaxis = np.arange(len(self.out.reduced)) * self.sample_rate / self.freq
|
||||||
return Namespace(
|
result = Namespace(
|
||||||
input_stddev = self.inp.reduced.std(),
|
input_stddev=self.inp.reduced.std(),
|
||||||
output_slope = np.polyfit(timeaxis, self.out.reduced, 1)[0])
|
output_slope=np.polyfit(timeaxis, self.out.reduced, 1)[0])
|
||||||
|
self.timer('got_quality')
|
||||||
|
self.timer.show()
|
||||||
|
ns = len(self.rawsignal[0])
|
||||||
|
print(f'{ns} {ns / 2e6} ms')
|
||||||
|
return result
|
||||||
|
@ -7,10 +7,12 @@ Created on Tue Feb 4 11:07:56 2020
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
NAN = float('nan')
|
||||||
|
|
||||||
|
|
||||||
def rect(x1, x2, y1, y2):
|
def rect(x1, x2, y1, y2):
|
||||||
return np.array([[x1,x2,x2,x1,x1],[y1,y1,y2,y2,y1]])
|
return np.array([[x1,x2,x2,x1,x1],[y1,y1,y2,y2,y1]])
|
||||||
|
|
||||||
NAN = float('nan')
|
|
||||||
|
|
||||||
def rects(intervals, y12):
|
def rects(intervals, y12):
|
||||||
result = [rect(*intervals[0], *y12)]
|
result = [rect(*intervals[0], *y12)]
|
||||||
@ -19,6 +21,7 @@ def rects(intervals, y12):
|
|||||||
result.append(rect(*x12, *y12))
|
result.append(rect(*x12, *y12))
|
||||||
return np.concatenate(result, axis=1)
|
return np.concatenate(result, axis=1)
|
||||||
|
|
||||||
|
|
||||||
class Plot:
|
class Plot:
|
||||||
def __init__(self, maxy):
|
def __init__(self, maxy):
|
||||||
self.lines = {}
|
self.lines = {}
|
||||||
@ -54,6 +57,9 @@ class Plot:
|
|||||||
self.fig = None
|
self.fig = None
|
||||||
self.first = True
|
self.first = True
|
||||||
|
|
||||||
|
def on_close(self, event):
|
||||||
|
self.fig = None
|
||||||
|
|
||||||
def plot(self, curves, rois=None, average=None):
|
def plot(self, curves, rois=None, average=None):
|
||||||
boxes = rects(rois[1:], self.yaxis[0])
|
boxes = rects(rois[1:], self.yaxis[0])
|
||||||
pbox = rect(*rois[0], *self.yaxis[1])
|
pbox = rect(*rois[0], *self.yaxis[1])
|
||||||
@ -68,6 +74,7 @@ class Plot:
|
|||||||
if self.first:
|
if self.first:
|
||||||
plt.ion()
|
plt.ion()
|
||||||
self.fig, axleft = plt.subplots(figsize=(15,7))
|
self.fig, axleft = plt.subplots(figsize=(15,7))
|
||||||
|
self.fig.canvas.mpl_connect('close_event', self.on_close)
|
||||||
plt.title("I/Q", fontsize=14)
|
plt.title("I/Q", fontsize=14)
|
||||||
axleft.set_xlim(0, curves[0][-1])
|
axleft.set_xlim(0, curves[0][-1])
|
||||||
self.ax = [axleft, axleft.twinx()]
|
self.ax = [axleft, axleft.twinx()]
|
||||||
|
@ -26,9 +26,11 @@ import numpy as np
|
|||||||
|
|
||||||
from frappy_psi.adq_mr import Adq, PEdata, RUSdata
|
from frappy_psi.adq_mr import Adq, PEdata, RUSdata
|
||||||
from frappy.core import Attached, BoolType, Done, FloatRange, HasIO, StatusType, \
|
from frappy.core import Attached, BoolType, Done, FloatRange, HasIO, StatusType, \
|
||||||
IntRange, Module, Parameter, Readable, Writable, Drivable, StringIO, StringType, \
|
IntRange, Module, Parameter, Readable, Writable, StatusType, StringIO, StringType, \
|
||||||
IDLE, BUSY, DISABLED, WARN, ERROR, TupleOf, ArrayOf, Command, Attached, EnumType
|
IDLE, BUSY, DISABLED, WARN, ERROR, TupleOf, ArrayOf, Command, Attached, EnumType ,\
|
||||||
|
Drivable
|
||||||
from frappy.properties import Property
|
from frappy.properties import Property
|
||||||
|
from frappy.lib import clamp
|
||||||
# from frappy.modules import Collector
|
# from frappy.modules import Collector
|
||||||
|
|
||||||
Collector = Readable
|
Collector = Readable
|
||||||
@ -46,11 +48,12 @@ def fname_from_time(t, extension):
|
|||||||
|
|
||||||
class Roi(Readable):
|
class Roi(Readable):
|
||||||
main = Attached()
|
main = Attached()
|
||||||
|
a = Attached(mandatory=False) # amplitude Readable
|
||||||
|
p = Attached(mandatory=False) # phase Readable
|
||||||
|
i = Attached(mandatory=False) # i Readable
|
||||||
|
q = Attached(mandatory=False) # amplitude Readable
|
||||||
|
|
||||||
value = Parameter('amplitude', FloatRange(), default=0)
|
value = Parameter('i, q', TupleOf(FloatRange(), FloatRange()), default=(0, 0))
|
||||||
phase = Parameter('phase', FloatRange(unit='deg'), default=0)
|
|
||||||
i = Parameter('in phase', FloatRange(), default=0)
|
|
||||||
q = Parameter('out of phase', FloatRange(), default=0)
|
|
||||||
time = Parameter('start time', FloatRange(unit='nsec'), readonly=False)
|
time = Parameter('start time', FloatRange(unit='nsec'), readonly=False)
|
||||||
size = Parameter('interval (symmetric around time)', FloatRange(unit='nsec'), readonly=False)
|
size = Parameter('interval (symmetric around time)', FloatRange(unit='nsec'), readonly=False)
|
||||||
enable = Parameter('calculate this roi', BoolType(), readonly=False, default=True)
|
enable = Parameter('calculate this roi', BoolType(), readonly=False, default=True)
|
||||||
@ -80,6 +83,49 @@ class Roi(Readable):
|
|||||||
return Done
|
return Done
|
||||||
|
|
||||||
|
|
||||||
|
class ControlRoi(Roi, Writable):
|
||||||
|
freq = Attached()
|
||||||
|
target = Parameter(datatype=Roi.value.datatype)
|
||||||
|
maxstep = Parameter('max frequency step', FloatRange(unit='Hz'), readonly=False,
|
||||||
|
default=10000)
|
||||||
|
minstep = Parameter('min frequency step for slope calculation', FloatRange(unit='Hz'),
|
||||||
|
readonly=False, default=4000)
|
||||||
|
slope = Parameter('inphase/frequency slope', FloatRange(), readonly=False,
|
||||||
|
default=1e6)
|
||||||
|
control_active = Parameter('are we controlling?', BoolType(), readonly=False)
|
||||||
|
|
||||||
|
_freq_target = None
|
||||||
|
_skipctrl = 2
|
||||||
|
_old = None
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
inphase = self.value[0]
|
||||||
|
freq = self.freq.target
|
||||||
|
if freq != self._freq_target:
|
||||||
|
self._freq_target = freq
|
||||||
|
# do no control 2 times after changing frequency
|
||||||
|
self._skipctrl = 2
|
||||||
|
if self.control_active:
|
||||||
|
if self._old:
|
||||||
|
newfreq = freq + inphase * self.slope
|
||||||
|
fdif = freq - self._old[0]
|
||||||
|
if abs(fdif) >= self.minstep:
|
||||||
|
idif = inphase - self._old[1]
|
||||||
|
self.slope = - fdif / idif
|
||||||
|
else:
|
||||||
|
# do a 'test' step
|
||||||
|
newfreq = freq + self.minstep
|
||||||
|
self.old = (freq, inphase)
|
||||||
|
if self._skipctrl > 0: # do no control for some time after changing frequency
|
||||||
|
self._skipctrl -= 1
|
||||||
|
elif self.control_active:
|
||||||
|
self._freq_target = self.freq.write_target(clamp(freq - self.maxstep, newfreq, freq + self.maxstep))
|
||||||
|
|
||||||
|
def write_target(self, value):
|
||||||
|
self.control_active = True
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class Pars(Module):
|
class Pars(Module):
|
||||||
description = 'relevant parameters from SEA'
|
description = 'relevant parameters from SEA'
|
||||||
|
|
||||||
@ -90,44 +136,75 @@ class Pars(Module):
|
|||||||
|
|
||||||
|
|
||||||
class FreqStringIO(StringIO):
|
class FreqStringIO(StringIO):
|
||||||
end_of_line = '\r'
|
end_of_line = '\r\n'
|
||||||
|
|
||||||
|
|
||||||
class Frequency(HasIO, Writable):
|
class Frequency(HasIO, Drivable):
|
||||||
value = Parameter('frequency', unit='Hz')
|
value = Parameter('frequency', unit='Hz')
|
||||||
amp = Parameter('amplitude (VPP)', FloatRange(unit='V'), readonly=False)
|
amp = Parameter('amplitude (VPP)', FloatRange(unit='V'), readonly=False)
|
||||||
output = Parameter('output: L or R', EnumType(L=1, R=0), readonly=False, default='L')
|
output = Parameter('output: L or R', EnumType(L=1, R=0), readonly=False, default='L')
|
||||||
|
|
||||||
last_change = 0
|
last_change = 0
|
||||||
ioClass = FreqStringIO
|
ioClass = FreqStringIO
|
||||||
dif = None
|
dif = None
|
||||||
_freq = None
|
_started = 0
|
||||||
|
_within_write_target = False
|
||||||
|
_nopoll_until = 0
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
super().doPoll()
|
||||||
|
if self.isBusy() and time.time() > self._started + 5:
|
||||||
|
self.status = WARN, 'acquisition timeout'
|
||||||
|
|
||||||
def register_dif(self, dif):
|
def register_dif(self, dif):
|
||||||
self.dif = dif
|
self.dif = dif
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
if self._freq is None:
|
if time.time() > self._nopoll_until or self.value == 0:
|
||||||
self._freq = float(self.communicate('FREQ?'))
|
self.value = float(self.communicate('FREQ?'))
|
||||||
return self._freq
|
if self.dif:
|
||||||
|
self.dif.read_value()
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def set_busy(self):
|
||||||
|
"""called by an acquisition module
|
||||||
|
|
||||||
|
from a callback on value within write_target
|
||||||
|
"""
|
||||||
|
if self._within_write_target:
|
||||||
|
self._started = time.time()
|
||||||
|
self.log.info('set busy')
|
||||||
|
self.status = BUSY, 'waiting for acquisition'
|
||||||
|
|
||||||
|
def set_idle(self):
|
||||||
|
if self.isBusy():
|
||||||
|
self.status = IDLE, ''
|
||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
self._freq = float(self.communicate('FREQ %.15g;FREQ?' % value))
|
self._nopoll_until = time.time() + 10
|
||||||
self.last_change = time.time()
|
try:
|
||||||
if self.dif:
|
self._within_write_target = True
|
||||||
self.dif.read_value()
|
# may trigger busy=True from an acquisition module
|
||||||
self.read_value()
|
self.value = float(self.communicate('FREQ %.15g;FREQ?' % value))
|
||||||
return self._freq
|
self.last_change = time.time()
|
||||||
|
if self.dif:
|
||||||
|
self.dif.read_value()
|
||||||
|
return self.value
|
||||||
|
finally:
|
||||||
|
self._within_write_target = False
|
||||||
|
|
||||||
def write_amp(self, amp):
|
def write_amp(self, amp):
|
||||||
reply = self.communicate(f'AMP{self.output.name} {amp} VPP;AMP{self.output.name}? VPP')
|
self._nopoll_until = time.time() + 10
|
||||||
return float(reply)
|
self.amp = float(self.communicate(f'AMP{self.output.name} {amp} VPP;AMP{self.output.name}? VPP'))
|
||||||
|
return self.amp
|
||||||
|
|
||||||
def read_amp(self):
|
def read_amp(self):
|
||||||
reply = self.communicate(f'AMP{self.output.name}? VPP')
|
if time.time() > self._nopoll_until or self.amp == 0:
|
||||||
return float(reply)
|
return float(self.communicate(f'AMP{self.output.name}? VPP'))
|
||||||
|
return self.amp
|
||||||
|
|
||||||
|
|
||||||
class FrequencyDif(Readable):
|
class FrequencyDif(Drivable):
|
||||||
freq = Attached(Frequency)
|
freq = Attached(Frequency)
|
||||||
base = Parameter('base frequency', FloatRange(unit='Hz'), default=0)
|
base = Parameter('base frequency', FloatRange(unit='Hz'), default=0)
|
||||||
value = Parameter('difference to base frequency', FloatRange(unit='Hz'), default=0)
|
value = Parameter('difference to base frequency', FloatRange(unit='Hz'), default=0)
|
||||||
@ -136,25 +213,54 @@ class FrequencyDif(Readable):
|
|||||||
super().initModule()
|
super().initModule()
|
||||||
self.freq.register_dif(self)
|
self.freq.register_dif(self)
|
||||||
|
|
||||||
|
def write_value(self, target):
|
||||||
|
self.freq.write_target(target + self.base)
|
||||||
|
return self.value # this was updated in Frequency
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
return self.freq - self.base
|
return self.freq.value - self.base
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
return self.freq.read_status()
|
||||||
|
|
||||||
|
|
||||||
class Base:
|
class Base:
|
||||||
freq = Attached()
|
freq = Attached()
|
||||||
sr = Parameter('samples per record', datatype=IntRange(1, 1E9), default=16384)
|
sr = Parameter('samples per record', datatype=IntRange(1, 1E9), default=16384)
|
||||||
adq = None
|
adq = None
|
||||||
|
_rawsignal = None
|
||||||
|
_fast_poll = 0.001
|
||||||
|
|
||||||
def shutdownModule(self):
|
def shutdownModule(self):
|
||||||
if self.adq:
|
if self.adq:
|
||||||
self.adq.deletecu()
|
self.adq.deletecu()
|
||||||
self.adq = None
|
self.adq = None
|
||||||
|
|
||||||
|
@Command(argument=TupleOf(FloatRange(unit='ns'), FloatRange(unit='ns'), IntRange(0,99999)),
|
||||||
|
result=TupleOf(FloatRange(),
|
||||||
|
ArrayOf(ArrayOf(IntRange(-0x7fff, 0x7fff), 0, 99999))))
|
||||||
|
def get_signal(self, start, end, npoints):
|
||||||
|
"""get signal
|
||||||
|
|
||||||
|
:param start: start time (ns)
|
||||||
|
:param end: end time (ns)
|
||||||
|
:param npoints: hint for number of data points
|
||||||
|
:return: (<time-step>, array of array of y)
|
||||||
|
|
||||||
|
for performance reasons the result data is rounded to int16
|
||||||
|
"""
|
||||||
|
# convert ns to samples
|
||||||
|
sr = self.adq.sample_rate * 1e-9
|
||||||
|
istart = round(start * sr)
|
||||||
|
iend = min(self.sr, round(end * sr))
|
||||||
|
nbin = max(1, round((iend - istart) / npoints))
|
||||||
|
iend = iend // nbin * nbin
|
||||||
|
return (nbin / sr,
|
||||||
|
[np.round(ch[istart:iend].reshape((-1, nbin)).mean(axis=1)) for ch in self._rawsignal])
|
||||||
|
|
||||||
|
|
||||||
class PulseEcho(Base):
|
class PulseEcho(Base, Readable):
|
||||||
value = Parameter("t, i, q, pulse curves",
|
value = Parameter(default=0)
|
||||||
TupleOf(*[ArrayOf(FloatRange(), 0, 16283) for _ in range(4)]), default=[[]] * 4)
|
|
||||||
nr = Parameter('number of records', datatype=IntRange(1, 9999), default=500)
|
nr = Parameter('number of records', datatype=IntRange(1, 9999), default=500)
|
||||||
bw = Parameter('bandwidth lowpassfilter', datatype=FloatRange(unit='Hz'), default=10E6)
|
bw = Parameter('bandwidth lowpassfilter', datatype=FloatRange(unit='Hz'), default=10E6)
|
||||||
control = Parameter('control loop on?', BoolType(), readonly=False, default=True)
|
control = Parameter('control loop on?', BoolType(), readonly=False, default=True)
|
||||||
@ -163,6 +269,8 @@ class PulseEcho(Base):
|
|||||||
size = Parameter('pulse length (starting from time)', FloatRange(unit='nsec'),
|
size = Parameter('pulse length (starting from time)', FloatRange(unit='nsec'),
|
||||||
readonly=False)
|
readonly=False)
|
||||||
pulselen = Parameter('adjusted pulse length (integer number of periods)', FloatRange(unit='nsec'), default=1)
|
pulselen = Parameter('adjusted pulse length (integer number of periods)', FloatRange(unit='nsec'), default=1)
|
||||||
|
# curves = Attached(mandatory=False)
|
||||||
|
pollinterval = Parameter('poll interval', datatype=FloatRange(0,120))
|
||||||
|
|
||||||
_starttime = None
|
_starttime = None
|
||||||
|
|
||||||
@ -171,6 +279,49 @@ class PulseEcho(Base):
|
|||||||
self.adq = Adq()
|
self.adq = Adq()
|
||||||
self.adq.init(self.sr, self.nr)
|
self.adq.init(self.sr, self.nr)
|
||||||
self.roilist = []
|
self.roilist = []
|
||||||
|
self.setFastPoll(True, self._fast_poll)
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
try:
|
||||||
|
data = self.adq.get_data()
|
||||||
|
except Exception as e:
|
||||||
|
self.status = ERROR, repr(e)
|
||||||
|
return
|
||||||
|
if data is None:
|
||||||
|
if self.adq.busy:
|
||||||
|
return
|
||||||
|
self.adq.start(PEdata(self.adq))
|
||||||
|
self.setFastPoll(True, self._fast_poll)
|
||||||
|
return
|
||||||
|
|
||||||
|
roilist = [r for r in self.roilist if r.enable]
|
||||||
|
freq = self.freq.read_value()
|
||||||
|
if not freq:
|
||||||
|
self.log.info('freq=0')
|
||||||
|
return
|
||||||
|
gates, curves = data.gates_and_curves(
|
||||||
|
freq, (self.time, self.time + self.size),
|
||||||
|
[(r.time, r.time + r.size) for r in roilist], self.bw)
|
||||||
|
for i, roi in enumerate(roilist):
|
||||||
|
a = gates[i][0]
|
||||||
|
b = gates[i][1]
|
||||||
|
roi.value = a, b
|
||||||
|
if roi.i:
|
||||||
|
roi.i.value = a
|
||||||
|
if roi.q:
|
||||||
|
roi.q.value = b
|
||||||
|
if roi.a:
|
||||||
|
roi.a.value = math.sqrt(a ** 2 + b ** 2)
|
||||||
|
if roi.p:
|
||||||
|
roi.p.value = math.atan2(a, b) * 180 / math.pi
|
||||||
|
self._curves = curves
|
||||||
|
self._rawsignal = data.rawsignal
|
||||||
|
|
||||||
|
@Command(result=TupleOf(*[ArrayOf(FloatRange(), 0, 99999)
|
||||||
|
for _ in range(4)]))
|
||||||
|
def get_curves(self):
|
||||||
|
"""retrieve curves"""
|
||||||
|
return self._curves
|
||||||
|
|
||||||
def write_nr(self, value):
|
def write_nr(self, value):
|
||||||
self.adq.init(self.sr, value)
|
self.adq.init(self.sr, value)
|
||||||
@ -184,46 +335,6 @@ class PulseEcho(Base):
|
|||||||
def register_roi(self, roi):
|
def register_roi(self, roi):
|
||||||
self.roilist.append(roi)
|
self.roilist.append(roi)
|
||||||
|
|
||||||
# TODO: fix
|
|
||||||
# def go(self):
|
|
||||||
# self._starttime = time.time()
|
|
||||||
# self.adq.start()
|
|
||||||
|
|
||||||
def read_value(self):
|
|
||||||
# TODO: data = self.get_data()
|
|
||||||
if self.get_rawdata(): # new data available
|
|
||||||
roilist = [r for r in self.roilist if r.enable]
|
|
||||||
freq = self.freq.value
|
|
||||||
gates = self.adq.gates_and_curves(self._data, freq,
|
|
||||||
(self.time, self.time + self.size),
|
|
||||||
[r.interval for r in roilist])
|
|
||||||
for i, roi in enumerate(roilist):
|
|
||||||
roi.i = a = gates[i][0]
|
|
||||||
roi.q = b = gates[i][1]
|
|
||||||
roi.value = math.sqrt(a ** 2 + b ** 2)
|
|
||||||
roi.phase = math.atan2(a, b) * 180 / math.pi
|
|
||||||
return self.adq.curves
|
|
||||||
|
|
||||||
# TODO: CONTROL
|
|
||||||
# inphase = self.roilist[0].i
|
|
||||||
# if self.control:
|
|
||||||
# newfreq = freq + inphase * self.slope - self.basefreq
|
|
||||||
# # step = sorted((-self.maxstep, inphase * self.slope, self.maxstep))[1]
|
|
||||||
# if self.old:
|
|
||||||
# fdif = freq - self.old[0]
|
|
||||||
# idif = inphase - self.old[1]
|
|
||||||
# if abs(fdif) >= self.minstep:
|
|
||||||
# self.slope = - fdif / idif
|
|
||||||
# else:
|
|
||||||
# fdif = 0
|
|
||||||
# idif = 0
|
|
||||||
# newfreq = freq + self.minstep
|
|
||||||
# self.old = (freq, inphase)
|
|
||||||
# if self.skipctrl > 0: # do no control for some time after changing frequency
|
|
||||||
# self.skipctrl -= 1
|
|
||||||
# elif self.control:
|
|
||||||
# self.freq = sorted((self.freq - self.maxstep, newfreq, self.freq + self.maxstep))[1]
|
|
||||||
|
|
||||||
|
|
||||||
CONTINUE = 0
|
CONTINUE = 0
|
||||||
GO = 1
|
GO = 1
|
||||||
@ -235,26 +346,21 @@ class RUS(Base, Collector):
|
|||||||
freq = Attached()
|
freq = Attached()
|
||||||
imod = Attached(mandatory=False)
|
imod = Attached(mandatory=False)
|
||||||
qmod = Attached(mandatory=False)
|
qmod = Attached(mandatory=False)
|
||||||
input_signal = Attached(mandatory=False)
|
|
||||||
output_signal = Attached(mandatory=False)
|
|
||||||
value = Parameter('averaged (I, Q) tuple', TupleOf(FloatRange(), FloatRange()))
|
value = Parameter('averaged (I, Q) tuple', TupleOf(FloatRange(), FloatRange()))
|
||||||
status = Parameter(datatype=StatusType(Readable, 'BUSY'))
|
status = Parameter(datatype=StatusType(Readable, 'BUSY'))
|
||||||
periods = Parameter('number of periods', IntRange(1, 9999), default=12)
|
periods = Parameter('number of periods', IntRange(1, 999999), default=12, readonly=False)
|
||||||
input_delay = Parameter('throw away everything before this time',
|
input_delay = Parameter('throw away everything before this time',
|
||||||
FloatRange(unit='ns'), default=10000, readonly=False)
|
FloatRange(unit='ns'), default=10000, readonly=False)
|
||||||
input_range = Parameter('input range (taking in to account attenuation)', FloatRange(unit='V'),
|
input_range = Parameter('input range (taking into account attenuation)', FloatRange(unit='V'),
|
||||||
default=10, readonly=False)
|
default=10, readonly=False)
|
||||||
output_range = Parameter('output range', FloatRange(unit='V'),
|
output_range = Parameter('output range', FloatRange(unit='V'),
|
||||||
default=1, readonly=False)
|
default=1, readonly=False)
|
||||||
input_phase_stddev = Parameter('input signal quality', FloatRange(unit='rad'), default=0)
|
|
||||||
output_phase_slope = Parameter('output signal phase slope', FloatRange(unit='rad/sec'), default=0)
|
|
||||||
output_amp_slope = Parameter('output signal amplitude change', FloatRange(unit='1/sec'), default=0)
|
|
||||||
input_amplitude = Parameter('input signal amplitude', FloatRange(unit='V'), default=0)
|
input_amplitude = Parameter('input signal amplitude', FloatRange(unit='V'), default=0)
|
||||||
output_amplitude = Parameter('output signal amplitude', FloatRange(unit='V'), default=0)
|
output_amplitude = Parameter('output signal amplitude', FloatRange(unit='V'), default=0)
|
||||||
phase = Parameter('phase', FloatRange(unit='deg'), default=0)
|
phase = Parameter('phase', FloatRange(unit='deg'), default=0)
|
||||||
amp = Parameter('amplitude', FloatRange(), default=0)
|
amp = Parameter('amplitude', FloatRange(), default=0)
|
||||||
continuous = Parameter('continuous mode', BoolType(), readonly=False, default=True)
|
continuous = Parameter('continuous mode', BoolType(), readonly=False, default=True)
|
||||||
pollinterval = Parameter(datatype=FloatRange(0, 120), default=1)
|
pollinterval = Parameter(datatype=FloatRange(0, 120), default=5)
|
||||||
|
|
||||||
_starttime = None
|
_starttime = None
|
||||||
_iq = 0
|
_iq = 0
|
||||||
@ -262,16 +368,26 @@ class RUS(Base, Collector):
|
|||||||
_action = CONTINUE # one of CONTINUE, GO, DONE_GO, WAIT_GO
|
_action = CONTINUE # one of CONTINUE, GO, DONE_GO, WAIT_GO
|
||||||
_status = IDLE, 'no data yet'
|
_status = IDLE, 'no data yet'
|
||||||
_busy = False # waiting for end of aquisition (not the same as self.status[0] == BUSY)
|
_busy = False # waiting for end of aquisition (not the same as self.status[0] == BUSY)
|
||||||
|
_requested_freq = None
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super().initModule()
|
super().initModule()
|
||||||
self.adq = Adq()
|
self.adq = Adq()
|
||||||
self._ovr_rate = {}
|
|
||||||
self.freq.addCallback('value', self.update_freq)
|
self.freq.addCallback('value', self.update_freq)
|
||||||
|
self.freq.addCallback('target', self.update_freq_target)
|
||||||
# self.write_periods(self.periods)
|
# self.write_periods(self.periods)
|
||||||
|
|
||||||
|
def update_freq_target(self, value):
|
||||||
|
self.go()
|
||||||
|
|
||||||
def update_freq(self, value):
|
def update_freq(self, value):
|
||||||
self.setFastPoll(True, 0.001)
|
self.setFastPoll(True, self._fast_poll)
|
||||||
|
self._requested_freq = value
|
||||||
|
self.freq.set_busy() # is only effective when the update was trigger within freq.write_target
|
||||||
|
|
||||||
|
def get_quality_info(self, data):
|
||||||
|
"""hook for RESqual"""
|
||||||
|
data.timer.show()
|
||||||
|
|
||||||
def doPoll(self):
|
def doPoll(self):
|
||||||
try:
|
try:
|
||||||
@ -283,22 +399,10 @@ class RUS(Base, Collector):
|
|||||||
self.wait_until = time.time() + 2
|
self.wait_until = time.time() + 2
|
||||||
return
|
return
|
||||||
|
|
||||||
self.setFastPoll(False)
|
|
||||||
if data: # this is new data
|
if data: # this is new data
|
||||||
self._data = data
|
self._busy = False
|
||||||
for chan in data.channels:
|
self.get_quality_info(data) # hook for RUSqual
|
||||||
if chan.ovr_rate:
|
self._rawsignal = data.rawsignal
|
||||||
self._ovr_rate[chan.name] = chan.ovr_rate * 100
|
|
||||||
else:
|
|
||||||
self._ovr_rate.pop(chan.name, None)
|
|
||||||
qual = data.get_quality()
|
|
||||||
if self.input_signal:
|
|
||||||
self.input_signal.value = np.round(data.channels[0].binned, 3)
|
|
||||||
if self.output_signal:
|
|
||||||
self.output_signal.value = np.round(data.channels[1].binned, 3)
|
|
||||||
self.input_phase_stddev = qual.input_stddev.imag
|
|
||||||
self.output_phase_slope = qual.output_slope.imag
|
|
||||||
self.output_amp_slope = qual.output_slope.real
|
|
||||||
self.input_amplitude = data.inp.amplitude * self.input_range
|
self.input_amplitude = data.inp.amplitude * self.input_range
|
||||||
self.output_amplitude = data.out.amplitude * self.output_range
|
self.output_amplitude = data.out.amplitude * self.output_range
|
||||||
self._iq = iq = data.iq * self.output_range / self.input_range
|
self._iq = iq = data.iq * self.output_range / self.input_range
|
||||||
@ -306,18 +410,33 @@ class RUS(Base, Collector):
|
|||||||
self.amp = np.abs(iq)
|
self.amp = np.abs(iq)
|
||||||
self.read_value()
|
self.read_value()
|
||||||
self.set_status(IDLE, '')
|
self.set_status(IDLE, '')
|
||||||
|
if self.freq.isBusy():
|
||||||
|
if data.freq == self._requested_freq:
|
||||||
|
self.log.info('set freq idle %.3f', time.time() % 1.0)
|
||||||
|
self.freq.set_idle()
|
||||||
|
else:
|
||||||
|
self.log.warn('freq does not match: requested %.14g, from data: %.14g',
|
||||||
|
self._requested_freq, data.freq)
|
||||||
|
else:
|
||||||
|
self.log.info('freq not busy %.3f', time.time() % 1.0)
|
||||||
|
if self._action == CONTINUE:
|
||||||
|
self.setFastPoll(False)
|
||||||
|
self.log.info('slow')
|
||||||
|
return
|
||||||
elif self._busy:
|
elif self._busy:
|
||||||
self._busy = False
|
|
||||||
if self._action == DONE_GO:
|
if self._action == DONE_GO:
|
||||||
|
self.log.info('busy')
|
||||||
self.set_status(BUSY, 'acquiring')
|
self.set_status(BUSY, 'acquiring')
|
||||||
else:
|
else:
|
||||||
self.set_status(IDLE, 'acquiring')
|
self.set_status(IDLE, 'acquiring')
|
||||||
return
|
return
|
||||||
if self._action == CONTINUE and self.continuous:
|
if self._action == CONTINUE and self.continuous:
|
||||||
|
print('CONTINUE')
|
||||||
self.start_acquisition()
|
self.start_acquisition()
|
||||||
self.set_status(IDLE, 'acquiring')
|
self.set_status(IDLE, 'acquiring')
|
||||||
return
|
return
|
||||||
if self._action == GO:
|
if self._action == GO:
|
||||||
|
print('pending GO')
|
||||||
self.start_acquisition()
|
self.start_acquisition()
|
||||||
self._action = DONE_GO
|
self._action = DONE_GO
|
||||||
self.set_status(BUSY, 'acquiring')
|
self.set_status(BUSY, 'acquiring')
|
||||||
@ -339,8 +458,6 @@ class RUS(Base, Collector):
|
|||||||
self.read_status()
|
self.read_status()
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self):
|
||||||
if self._ovr_rate and self._status[0] < WARN:
|
|
||||||
return WARN, 'overrange on %s' % ' and '.join(self._ovr_rate)
|
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
@ -352,7 +469,8 @@ class RUS(Base, Collector):
|
|||||||
|
|
||||||
@Command
|
@Command
|
||||||
def go(self):
|
def go(self):
|
||||||
"""start aquisition"""
|
"""start acquisition"""
|
||||||
|
self.log.info('go %.3f', time.time() % 1.0)
|
||||||
if self._busy:
|
if self._busy:
|
||||||
self._action = GO
|
self._action = GO
|
||||||
else:
|
else:
|
||||||
@ -362,54 +480,25 @@ class RUS(Base, Collector):
|
|||||||
self.read_status()
|
self.read_status()
|
||||||
|
|
||||||
def start_acquisition(self):
|
def start_acquisition(self):
|
||||||
|
self.log.info('start %.3f', time.time() % 1.0)
|
||||||
freq = self.freq.read_value()
|
freq = self.freq.read_value()
|
||||||
self.sr = round(self.periods * self.adq.sample_rate / freq)
|
self.sr = round(self.periods * self.adq.sample_rate / freq)
|
||||||
delay_samples = round(self.input_delay * self.adq.sample_rate * 1e-9)
|
delay_samples = round(self.input_delay * self.adq.sample_rate * 1e-9)
|
||||||
self.adq.init(self.sr + delay_samples, 1)
|
self.adq.init(self.sr + delay_samples, 1)
|
||||||
self.adq.start(RUSdata(self.adq, freq, self.periods, delay_samples))
|
self.adq.start(RUSdata(self.adq, freq, self.periods, delay_samples))
|
||||||
self._busy = True
|
self._busy = True
|
||||||
self.setFastPoll(True, 0.001)
|
self.setFastPoll(True, self._fast_poll)
|
||||||
|
|
||||||
|
|
||||||
class Signal(Readable):
|
class RUSqual(RUS):
|
||||||
value = Parameter('pulse', ArrayOf(FloatRange(), maxlen=9999))
|
"""version with additional info about quality of input and output signal"""
|
||||||
|
|
||||||
|
input_phase_stddev = Parameter('input signal quality', FloatRange(unit='rad'), default=0)
|
||||||
|
output_phase_slope = Parameter('output signal phase slope', FloatRange(unit='rad/sec'), default=0)
|
||||||
|
output_amp_slope = Parameter('output signal amplitude change', FloatRange(unit='1/sec'), default=0)
|
||||||
|
|
||||||
class ControlLoop(Module):
|
def get_quality_info(self, data):
|
||||||
roi = Attached(Roi)
|
qual = data.get_quality()
|
||||||
maxstep = Parameter('max frequency step', FloatRange(unit='Hz'), readonly=False,
|
self.input_phase_stddev = qual.input_stddev.imag
|
||||||
default=10000)
|
self.output_phase_slope = qual.output_slope.imag
|
||||||
minstep = Parameter('min frequency step for slope calculation', FloatRange(unit='Hz'),
|
self.output_amp_slope = qual.output_slope.real
|
||||||
readonly=False, default=4000)
|
|
||||||
slope = Parameter('inphase/frequency slope', FloatRange(), readonly=False,
|
|
||||||
default=1e6)
|
|
||||||
|
|
||||||
|
|
||||||
# class Frequency(HasIO, Readable):
|
|
||||||
# pars = Attached()
|
|
||||||
# curves = Attached(mandatory=False)
|
|
||||||
# maxy = Property('plot y scale', datatype=FloatRange(), default=0.5)
|
|
||||||
#
|
|
||||||
# value = Parameter('frequency@I,q', datatype=FloatRange(unit='Hz'), default=0)
|
|
||||||
# basefreq = Parameter('base frequency', FloatRange(unit='Hz'), readonly=False)
|
|
||||||
# nr = Parameter('number of records', datatype=IntRange(1,10000), default=500)
|
|
||||||
# sr = Parameter('samples per record', datatype=IntRange(1,1E9), default=16384)
|
|
||||||
# freq = Parameter('target frequency', FloatRange(unit='Hz'), readonly=False)
|
|
||||||
# bw = Parameter('bandwidth lowpassfilter', datatype=FloatRange(unit='Hz'),default=10E6)
|
|
||||||
# amp = Parameter('amplitude', FloatRange(unit='dBm'), readonly=False)
|
|
||||||
# control = Parameter('control loop on?', BoolType(), readonly=False, default=True)
|
|
||||||
# rusmode = Parameter('RUS mode on?', BoolType(), readonly=False, default=False)
|
|
||||||
# time = Parameter('pulse start time', FloatRange(unit='nsec'),
|
|
||||||
# readonly=False)
|
|
||||||
# size = Parameter('pulse length (starting from time)', FloatRange(unit='nsec'),
|
|
||||||
# readonly=False)
|
|
||||||
# pulselen = Parameter('adjusted pulse length (integer number of periods)', FloatRange(unit='nsec'), default=1)
|
|
||||||
# maxstep = Parameter('max frequency step', FloatRange(unit='Hz'), readonly=False,
|
|
||||||
# default=10000)
|
|
||||||
# minstep = Parameter('min frequency step for slope calculation', FloatRange(unit='Hz'),
|
|
||||||
# readonly=False, default=4000)
|
|
||||||
# slope = Parameter('inphase/frequency slope', FloatRange(), readonly=False,
|
|
||||||
# default=1e6)
|
|
||||||
# plot = Parameter('create plot images', BoolType(), readonly=False, default=True)
|
|
||||||
# save = Parameter('save data', BoolType(), readonly=False, default=True)
|
|
||||||
# pollinterval = Parameter(datatype=FloatRange(0,120))
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user