diff --git a/cfg/PEUS.cfg b/cfg/PEUS.cfg new file mode 100644 index 0000000..a1aba9d --- /dev/null +++ b/cfg/PEUS.cfg @@ -0,0 +1,126 @@ +[NODE] +id = ultrasound.psi.ch +description = ultrasound settings + +[INTERFACE] +uri = tcp://5000 + +[f] +class = secop_psi.ultrasound.Frequency +description = ultrasound frequency and acquisition loop +uri = serial:///dev/ttyS1 +pars = pars +pollinterval = 0.1 +# this is the start time: +time = 900 +size = 5000 +freq = 1.17568e+06 +basefreq = 4.14902e+07 +control = True +amp = 5.0 +nr = 1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #500 +sr = 32768 #16384 +plot = True +maxstep = 100000 +bw = 10E6 #butter worth filter bandwidth +# y scale for plot: +maxy = 0.7 +# module to transmit curves: +curves = curves + +[curves] +class = secop_psi.ultrasound.Curves +description = t, I, Q and pulse arrays for plot + +[roi0] +class = secop_psi.ultrasound.Roi +description = I/Q of region in the control loop. +# this is the center of roi: +time = 2450 +size = 300 +main = f + +[roi1] +class = secop_psi.ultrasound.Roi +description = I/Q of region 1 +time = 5950 +size = 300 +main = f + +[roi2] +class = secop_psi.ultrasound.Roi +description = I/Q of region 2 +# enable = False +time = 9475 +size = 300 +main = f + +[roi3] +class = secop_psi.ultrasound.Roi +description = I/Q of region 3 +#enable = False +time = 12900 +size = 300 +main = f + +[roi4] +class = secop_psi.ultrasound.Roi +description = I/Q of region 4 +enable = True +time = 16100 +size = 300 +main = f + +[roi5] +class = secop_psi.ultrasound.Roi +description = I/Q of region 5 +enable = False +time = 4000 +size = 30 +main = f + +[roi6] +class = secop_psi.ultrasound.Roi +description = I/Q of region 6 +enable = False +time = 4000 +size = 200 +main = f + +[roi7] +class = secop_psi.ultrasound.Roi +description = I/Q of region 7 +enable = False +time = 4000 +size = 200 +main = f + +[roi8] +class = secop_psi.ultrasound.Roi +description = I/Q of region 8 +enable = False +time = 4000 +size = 200 +main = f + +[roi9] +class = secop_psi.ultrasound.Roi +description = I/Q of region 9 +enable = False +time = 4000 +size = 200 +main = f + +[delay] +class = secop_psi.dg645.Delay +description = delay line with 2 channels +#uri = dil4-ts.psi.ch:3008 +uri = serial:///dev/ttyS2 +on1 = 1e-9 +on2 = 1E-9 +off1 = 400e-9 +off2 = 600e-9 + +[pars] +class = secop_psi.ultrasound.Pars +description = SEA parameters diff --git a/cfg/RUS.cfg b/cfg/RUS.cfg new file mode 100644 index 0000000..52c2e87 --- /dev/null +++ b/cfg/RUS.cfg @@ -0,0 +1,63 @@ +[NODE] +id = ultrasound.psi.ch +description = resonant ultra sound setup + +[INTERFACE] +uri = tcp://5000 + +[f] +class = secop_psi.ultrasound.Frequency +description = ultrasound frequency and acquisition loop +uri = serial:///dev/ttyS1 +pars = pars +pollinterval = 0.1 +# this is the start time: +time = 900 +size = 5000 +freq = 1.e+03 +basefreq = 1.E+3 +control = False +rusmode = False +amp = 2.5 +nr = 1 #500 #300 #100 #50 #30 #10 #5 #3 #1 #1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #500 +sr = 1E8 #16384 +plot = True +maxstep = 100000 +bw = 10E6 #butter worth filter bandwidth +# y scale for plot: +maxy = 0.7 +# module to transmit curves: +curves = curves + +[curves] +class = secop_psi.ultrasound.Curves +description = t, I, Q and pulse arrays for plot + +[roi0] +class = secop_psi.ultrasound.Roi +description = I/Q of region in the control loop. +# this is the center of roi: +time = 300 +size = 5000 +main = f + +[roi1] +class = secop_psi.ultrasound.Roi +description = I/Q of region 1 +time = 100 +size = 300 +main = f + +[delay] +class = secop_psi.dg645.Delay +description = delay line with 2 channels +#uri = dil4-ts.psi.ch:3008 +uri = serial:///dev/ttyS2 +on1 = 1e-9 +on2 = 1E-9 +off1 = 400e-9 +off2 = 600e-9 + +[pars] +class = secop_psi.ultrasound.Pars +description = SEA parameters diff --git a/cfg/dg.cfg b/cfg/dg.cfg new file mode 100644 index 0000000..b56a4de --- /dev/null +++ b/cfg/dg.cfg @@ -0,0 +1,17 @@ +[node ultrasound.psi.ch] +description = ultrasound settings + +[interface tcp] +type = tcp +bindto = 0.0.0.0 +bindport = 5000 + +[module delay] +class = secop_psi.dg645.Delay +description = delay line with 2 channels +#uri = dil4-ts.psi.ch:3008 +uri = serial:///dev/ttyS2 +on1 = 10e-9 +on2 = 1E-9 +off1 = 250e-9 +off2 = 550e-9 diff --git a/cfg/ultrasound.cfg b/cfg/ultrasound.cfg new file mode 100644 index 0000000..a1aba9d --- /dev/null +++ b/cfg/ultrasound.cfg @@ -0,0 +1,126 @@ +[NODE] +id = ultrasound.psi.ch +description = ultrasound settings + +[INTERFACE] +uri = tcp://5000 + +[f] +class = secop_psi.ultrasound.Frequency +description = ultrasound frequency and acquisition loop +uri = serial:///dev/ttyS1 +pars = pars +pollinterval = 0.1 +# this is the start time: +time = 900 +size = 5000 +freq = 1.17568e+06 +basefreq = 4.14902e+07 +control = True +amp = 5.0 +nr = 1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #500 +sr = 32768 #16384 +plot = True +maxstep = 100000 +bw = 10E6 #butter worth filter bandwidth +# y scale for plot: +maxy = 0.7 +# module to transmit curves: +curves = curves + +[curves] +class = secop_psi.ultrasound.Curves +description = t, I, Q and pulse arrays for plot + +[roi0] +class = secop_psi.ultrasound.Roi +description = I/Q of region in the control loop. +# this is the center of roi: +time = 2450 +size = 300 +main = f + +[roi1] +class = secop_psi.ultrasound.Roi +description = I/Q of region 1 +time = 5950 +size = 300 +main = f + +[roi2] +class = secop_psi.ultrasound.Roi +description = I/Q of region 2 +# enable = False +time = 9475 +size = 300 +main = f + +[roi3] +class = secop_psi.ultrasound.Roi +description = I/Q of region 3 +#enable = False +time = 12900 +size = 300 +main = f + +[roi4] +class = secop_psi.ultrasound.Roi +description = I/Q of region 4 +enable = True +time = 16100 +size = 300 +main = f + +[roi5] +class = secop_psi.ultrasound.Roi +description = I/Q of region 5 +enable = False +time = 4000 +size = 30 +main = f + +[roi6] +class = secop_psi.ultrasound.Roi +description = I/Q of region 6 +enable = False +time = 4000 +size = 200 +main = f + +[roi7] +class = secop_psi.ultrasound.Roi +description = I/Q of region 7 +enable = False +time = 4000 +size = 200 +main = f + +[roi8] +class = secop_psi.ultrasound.Roi +description = I/Q of region 8 +enable = False +time = 4000 +size = 200 +main = f + +[roi9] +class = secop_psi.ultrasound.Roi +description = I/Q of region 9 +enable = False +time = 4000 +size = 200 +main = f + +[delay] +class = secop_psi.dg645.Delay +description = delay line with 2 channels +#uri = dil4-ts.psi.ch:3008 +uri = serial:///dev/ttyS2 +on1 = 1e-9 +on2 = 1E-9 +off1 = 400e-9 +off2 = 600e-9 + +[pars] +class = secop_psi.ultrasound.Pars +description = SEA parameters diff --git a/ftune.py b/ftune.py new file mode 100644 index 0000000..2e403c1 --- /dev/null +++ b/ftune.py @@ -0,0 +1,127 @@ +import PySimpleGUI as sg +import time +import sys +sys.path.append(r'/Users/bartkowiak/Programs/frappy/frappy') +from secop.client import SecopClient +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import matplotlib.pyplot as plt +from numpy.random import rand +import numpy as np + +def draw_figure(canvas, figure): + figure_canvas_agg = FigureCanvasTkAgg(figure, canvas) + figure_canvas_agg.draw() + figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1) + return figure_canvas_agg + +def upd_flim(inp): + return int(inp) +def connection_handler(sec,status,IP,port, cstate): + if cstate == -1 and status == False: + try : + cstate = 1 + #sock.connect((IP,int(port))) + sec = SecopClient(IP+':'+port) + sec.connect() + sec.setParameter('f','control',False) + status = False + except : + cstate = -1 + status = True + elif status == True and cstate == 1 : + status = False + sec.disconnect() + #sock.close() + cstate = -1 + return cstate, status,sec + +def new_xy(sec, cstate): + xn = 0 + yn = 0 + if cstate == 1: + try: + xn = sec.getParameter('roi0','i')[0] + yn = sec.getParameter('roi0','q')[0] + except: + pass + return xn, yn + +def send_new_f(sec, cstate, freq): + if cstate == 1: + freq = sec.setParameter('f','basefreq',freq) + return freq + + +# THIS IS THE MAIN LOOP +def main(): # define the main loop + freq = '10000000' + f_start = 39000000 + f_end = 42000000 + new_f = f_start + down = True + down2 = True + cstate = -1 + tstart = time.time() + x = np.array([]) + y = np.array([]) + IP = 'pc13252' + port = '5000' + sec = SecopClient(IP+':'+port) + layout = [ [sg.Text('Frequency Tuner)')], + [sg.Text('IP\t\t:',font= ("Helvetica",22)),sg.InputText(key='_IP_',default_text=IP,font= ("Helvetica",22),size=(15,1))], + [sg.Text('PORT\t\t:',font= ("Helvetica",22)),sg.InputText(key='_PORT_',default_text=port,font= ("Helvetica",22),size=(15,1)),sg.T(' ' * 41), sg.Button('connect',key='connect',font= ("Helvetica",22))], + [sg.Text('Start Frequency\t:',font= ("Helvetica",22)),sg.InputText(key='_f_start_', default_text='39000000',font= ("Helvetica",22),size=(15,1))], + [sg.Text('Stop Frequency\t:',font= ("Helvetica",22)),sg.InputText(key='_f_end_',default_text='45000000',font= ("Helvetica",22),size=(15,1))], + [sg.Slider(range=(f_start, f_end),key='_SLtune_', orientation='h',font= ("Helvetica",22), size=(35, 20), default_value=f_start),sg.T(' ' * 2),sg.Button('sweep',key='sweep',font= ("Helvetica",22))], + [sg.Canvas(size=(640, 480), key='-CANVAS-')], + [sg.Button('Exit',font= ("Helvetica",22)),sg.T(' ' * 150),sg.Button('Clear Plot',key='clp',font= ("Helvetica",22))] ] # a couple of buttons + + window = sg.Window('Frequency tuner', layout, finalize=True) + canvas_elem = window['-CANVAS-'] + canvas = canvas_elem.TKCanvas + # draw the intitial scatter plot + fig, ax = plt.subplots() + ax.grid(True) + fig_agg = draw_figure(canvas, fig) + + while True: + # Event Loop + event, values = window.Read(timeout=0.5) + if event in (None, 'Exit'): # checks if user wants to exit + if cstate == 1: + connection_handler(sec,True,values['_IP_'],values['_PORT_'],cstate) + break + + if event == 'connect': + down = not down + window['connect'].Update(('disconnect','connect')[down]) + cstate, down, sec = connection_handler(sec,down,values['_IP_'],values['_PORT_'],cstate) + if event == 'sweep': + down2 = not down2 + window['sweep'].Update(('sweeping','sweep')[down2]) + #if event in ('_SL8_','_SL6_','_SL4_','_SL2_'): # the two lines of code needed to get button and run command + #freq = update_freq(inp=[values['_SL8_'],values['_SL6_'],values['_SL4_'],values['_SL2_']]) + if down2 == False: + if time.time() - tstart > 1.: + tstart = time.time() + new_f = int(int(values['_SLtune_']) + (int(values['_f_end_'])-int(values['_f_start_']))/200) + send_new_f(sec,cstate,new_f) + window['_SLtune_'].Update(value=new_f) + if event == 'clp': + x = np.array([]) + y = np.array([]) + window['_SLtune_'].Update(range=(int(values['_f_start_']),int(values['_f_end_']))) + xn,yn = new_xy(sec,cstate) + ax.cla() + ax.grid(True) + x = np.append(x,xn) + y = np.append(y,yn) + ax.plot(x, y) + fig_agg.draw() + + window.Close() + + + +if __name__ == '__main__': + main() diff --git a/secop/core.py b/secop/core.py index 1da4370..b3a259c 100644 --- a/secop/core.py +++ b/secop/core.py @@ -44,3 +44,4 @@ ERROR = Drivable.Status.ERROR WARN = Drivable.Status.WARN BUSY = Drivable.Status.BUSY IDLE = Drivable.Status.IDLE +DISABLED = Drivable.Status.DISABLED diff --git a/secop/datatypes.py b/secop/datatypes.py index 3f5f3df..8655a88 100644 --- a/secop/datatypes.py +++ b/secop/datatypes.py @@ -760,7 +760,7 @@ class ArrayOf(DataType): def __call__(self, value): """validate an external representation to an internal one""" - if isinstance(value, (tuple, list)): + try: # check number of elements if self.minlen is not None and len(value) < self.minlen: raise BadValueError( @@ -768,11 +768,12 @@ class ArrayOf(DataType): self.minlen) if self.maxlen is not None and len(value) > self.maxlen: raise BadValueError( - 'Array too big, holds at most %d elements!' % self.minlen) - # apply subtype valiation to all elements and return as list + 'Array too big, holds at most %d elements!' % self.maxlen) + # apply subtype valdiation to all elements and return as list return tuple(self.members(elem) for elem in value) - raise BadValueError( - 'Can not convert %s to ArrayOf DataType!' % repr(value)) + except TypeError: + raise BadValueError('%s can not be converted to ArrayOf DataType!' + % type(value).__name__) from None def export_value(self, value): """returns a python object fit for serialisation""" @@ -842,6 +843,7 @@ class TupleOf(DataType): return tuple(sub(elem) for sub, elem in zip(self.members, value)) except Exception as exc: + print(value) raise BadValueError('Can not validate:', str(exc)) from None def export_value(self, value): diff --git a/secop/io.py b/secop/io.py index 07932ef..0ff9359 100644 --- a/secop/io.py +++ b/secop/io.py @@ -47,7 +47,7 @@ class SilentError(CommunicationFailedError): class HasIO(Module): """Mixin for modules using a communicator""" - io = Attached() + io = Attached(mandatory=False) uri = Property('uri for automatic creation of the attached communication module', StringType(), default='') diff --git a/secop/modules.py b/secop/modules.py index 48ebfa5..c58ff61 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -500,10 +500,11 @@ class Module(HasAccessibles): # TODO: remove readerror 'property' and replace value with exception pobj = self.parameters[pname] timestamp = timestamp or time.time() - changed = pobj.value != value try: + value = pobj.datatype(value) + changed = pobj.value != value # store the value even in case of error - pobj.value = pobj.datatype(value) + pobj.value = value except Exception as e: if isinstance(e, DiscouragedConversion): if DiscouragedConversion.log_message: diff --git a/secop/params.py b/secop/params.py index 2328197..d6d3173 100644 --- a/secop/params.py +++ b/secop/params.py @@ -149,6 +149,9 @@ class Parameter(Accessible): default None: write if given in config''', NoneOr(BoolType()), export=False, default=None, settable=False) + # used in NICOS only ... + nicos_category = Property( + '''NICOS parameter category''', StringType(), export=True, default='') # used on the instance copy only value = None diff --git a/secop_psi/adq_mr.py b/secop_psi/adq_mr.py index d0e9fc1..ea65623 100644 --- a/secop_psi/adq_mr.py +++ b/secop_psi/adq_mr.py @@ -1,294 +1,257 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Nov 26 15:42:43 2019 - -@author: tartarotti_d-adm -""" - - -import numpy as np -import ctypes as ct -import time -from numpy import sqrt, arctan2, sin, cos - -#from pylab import * - -from scipy import signal - -#ADQAPI = ct.cdll.LoadLibrary("ADQAPI.dll") -ADQAPI = ct.cdll.LoadLibrary("libadq.so.0") - -#For different trigger modes -SW_TRIG = 1 -EXT_TRIG_1 = 2 #This external trigger does not work if the level of the trigger is very close to 0.5V. Now we have it close to 3V, and it works -EXT_TRIG_2 = 7 -EXT_TRIG_3 = 8 -LVL_TRIG = 3 -INT_TRIG = 4 -LVL_FALLING = 0 -LVL_RISING = 1 - -#samples_per_record=16384 -ADQ_TRANSFER_MODE_NORMAL = 0x00 -ADQ_CHANNELS_MASK = 0x3 - -#f_LO = 40 - -def butter_lowpass(cutoff, sr, order=5): - nyq = 0.5 * sr - normal_cutoff = cutoff / nyq - b, a = signal.butter(order, normal_cutoff, btype = 'low', analog = False) - return b, a - - -class Adq(object): - max_number_of_channels = 2 - samp_freq = 2 - #ndecimate = 50 # decimation ratio (2GHz / 40 MHz) - ndecimate = 50 - - def __init__(self, number_of_records, samples_per_record, bw_cutoff): - self.number_of_records = number_of_records - self.samples_per_record = samples_per_record - self.bw_cutoff = bw_cutoff - ADQAPI.ADQAPI_GetRevision() - - # Manually set return type from some ADQAPI functions - ADQAPI.CreateADQControlUnit.restype = ct.c_void_p - ADQAPI.ADQ_GetRevision.restype = ct.c_void_p - ADQAPI.ADQ_GetPtrStream.restype = ct.POINTER(ct.c_int16) - ADQAPI.ADQControlUnit_FindDevices.argtypes = [ct.c_void_p] - # Create ADQControlUnit - self.adq_cu = ct.c_void_p(ADQAPI.CreateADQControlUnit()) - ADQAPI.ADQControlUnit_EnableErrorTrace(self.adq_cu, 3, '.') - self.adq_num = 1 - - # Find ADQ devices - ADQAPI.ADQControlUnit_FindDevices(self.adq_cu) - n_of_ADQ = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu) - if n_of_ADQ != 1: - raise ValueError('number of ADQs must be 1, not %d' % n_of_ADQ) - - rev = ADQAPI.ADQ_GetRevision(self.adq_cu, self.adq_num) - revision = ct.cast(rev,ct.POINTER(ct.c_int)) - print('\nConnected to ADQ #1') - # Print revision information - print('FPGA Revision: {}'.format(revision[0])) - if (revision[1]): - print('Local copy') - else : - print('SVN Managed') - if (revision[2]): - print('Mixed Revision') - else : - print('SVN Updated') - print('') - - ADQ_CLOCK_INT_INTREF = 0 #internal clock source - ADQ_CLOCK_EXT_REF = 1 #internal clock source, external reference - ADQ_CLOCK_EXT_CLOCK = 2 #External clock source - ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF); - - ########################## - # Test pattern - #ADQAPI.ADQ_SetTestPatternMode(self.adq_cu, self.adq_num, 4) - ########################## - # Sample skip - #ADQAPI.ADQ_SetSampleSkip(self.adq_cu, self.adq_num, 1) - ########################## - - # Set trig mode - self.trigger = EXT_TRIG_1 - #trigger = LVL_TRIG - success = ADQAPI.ADQ_SetTriggerMode(self.adq_cu, self.adq_num, self.trigger) - if (success == 0): - print('ADQ_SetTriggerMode failed.') - if (self.trigger == LVL_TRIG): - success = ADQAPI.ADQ_SetLvlTrigLevel(self.adq_cu, self.adq_num, -100) - if (success == 0): - print('ADQ_SetLvlTrigLevel failed.') - success = ADQAPI.ADQ_SetTrigLevelResetValue(self.adq_cu, self.adq_num, 1000) - if (success == 0): - print('ADQ_SetTrigLevelResetValue failed.') - success = ADQAPI.ADQ_SetLvlTrigChannel(self.adq_cu, self.adq_num, 1) - if (success == 0): - print('ADQ_SetLvlTrigChannel failed.') - success = ADQAPI.ADQ_SetLvlTrigEdge(self.adq_cu, self.adq_num, LVL_RISING) - if (success == 0): - print('ADQ_SetLvlTrigEdge failed.') - elif (self.trigger == EXT_TRIG_1) : - success = ADQAPI.ADQ_SetExternTrigEdge(self.adq_cu, self.adq_num,2) - if (success == 0): - print('ADQ_SetLvlTrigEdge failed.') - # success = ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)) - # if (success == 0): - # print('SetTriggerThresholdVoltage failed.') - print("CHANNEL:"+str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num)))) - self.setup_target_buffers() - - def setup_target_buffers(self): - # Setup target buffers for data - self.target_buffers=(ct.POINTER(ct.c_int16 * self.samples_per_record * self.number_of_records) - * self.max_number_of_channels)() - for bufp in self.target_buffers: - bufp.contents = (ct.c_int16 * self.samples_per_record * self.number_of_records)() - - def deletecu(self): - # Only disarm trigger after data is collected - ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num) - ADQAPI.ADQ_MultiRecordClose(self.adq_cu, self.adq_num); - # Delete ADQControlunit - ADQAPI.DeleteADQControlUnit(self.adq_cu) - - def start(self): - """start datat acquisition""" - # samples_per_records = samples_per_record/number_of_records - # Change number of pulses to be acquired acording to how many records are taken - # Start acquisition - ADQAPI.ADQ_MultiRecordSetup(self.adq_cu, self.adq_num, - self.number_of_records, - self.samples_per_record) - - ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num) - ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num) - - def getdata(self): - """wait for aquisition to be finished and get data""" - #start = time.time() - while(ADQAPI.ADQ_GetAcquiredAll(self.adq_cu,self.adq_num) == 0): - time.sleep(0.001) - #if (self.trigger == SW_TRIG): - # ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num) - #mid = time.time() - status = ADQAPI.ADQ_GetData(self.adq_cu, self.adq_num, self.target_buffers, - self.samples_per_record * self.number_of_records, 2, - 0, self.number_of_records, ADQ_CHANNELS_MASK, - 0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL); - #print(time.time()-mid,mid-start) - if not status: - raise ValueError('no succesS from ADQ_GetDATA') - # Now this is an array with all records, but the time is artificial - data = [] - for ch in range(2): - onedim = np.frombuffer(self.target_buffers[ch].contents, dtype=np.int16) - data.append(onedim.reshape(self.number_of_records, self.samples_per_record) / float(2**14)) # 14 bits ADC - return data - - def acquire(self): - self.start() - return self.getdata() - ''' - def average(self, data): - #Average over records - return [data[ch].sum(axis=0) / self.number_of_records for ch in range(2)] - - def iq(self, channel, f_LO): - newx = np.linspace(0, self.samples_per_record /2, self.samples_per_record) - s0 = channel /((2**16)/2)*0.5*np.exp(1j*2*np.pi*f_LO/(1e3)*newx) - I0 = s0.real - Q0 = s0.imag - return I0, Q0 - - - def fitting(self, data, f_LO, ti, tf): - # As long as data[0] is the pulse - si = 2*ti #Those are for fitting the pulse - sf = 2*tf - phase = np.zeros(self.number_of_records) - amplitude = np.zeros(self.number_of_records) - offset = np.zeros(self.number_of_records) - - for i in range(self.number_of_records): - phase[i], amplitude[i] = sineW(data[0][i][si:sf],f_LO*1e-9,ti,tf) - offset[i] = np.average(data[0][i][si:sf]) - return phase, amplitude, offset - - - def waveIQ(self, channel,ti,f_LO): - #channel is not the sample data - t = np.linspace(0, self.samples_per_record /2, self.samples_per_record + 1)[:-1] - si = 2*ti # Again that is where the wave pulse starts - cwi = np.zeros((self.number_of_records,self.samples_per_record)) - cwq = np.zeros((self.number_of_records,self.samples_per_record)) - iq = np.zeros((self.number_of_records,self.samples_per_record)) - q = np.zeros((self.number_of_records,self.samples_per_record)) - for i in range(self.number_of_records): - cwi[i] = np.zeros(self.samples_per_record) - cwq[i] = np.zeros(self.samples_per_record) - cwi[i] = amplitude[i]*sin(t[si:]*f_LO*1e-9*2*np.pi+phase[i]*np.pi/180)+bias[i] - cwq[i] = amplitude[i]*sin(t[si:]*f_LO*1e-9*(2*np.pi+(phase[i]+90)*np.pi/180))+bias[i] - - iq[i] = channel[i]*cwi[i] - q[i] = channel[i]*cwq[i] - - return iq,q - ''' - def sinW(self,sig,freq,ti,tf): - # sig: signal array - # freq - # ti, tf: initial and end time - si = int(ti * self.samp_freq) - nperiods = freq * (tf - ti) - n = int(round(max(2, int(nperiods)) / nperiods * (tf-ti) * self.samp_freq)) - self.nperiods = n - t = np.arange(si, len(sig)) / self.samp_freq - t = t[:n] - self.pulselen = n / self.samp_freq - sig = sig[si:si+n] - a = 2*np.sum(sig*np.cos(2*np.pi*freq*t))/len(sig) - b = 2*np.sum(sig*np.sin(2*np.pi*freq*t))/len(sig) - return a, b - - def mix(self, sigin, sigout, freq, ti, tf): - # sigin, sigout: signal array, incomping, output - # freq - # ti, tf: initial and end time if sigin - a, b = self.sinW(sigin, freq, ti, tf) - phase = arctan2(a,b) * 180 / np.pi - amp = sqrt(a**2 + b**2) - a, b = a/amp, b/amp - #si = int(ti * self.samp_freq) - t = np.arange(len(sigout)) / self.samp_freq - wave1 = sigout * (a * cos(2*np.pi*freq*t) + b * sin(2*np.pi*freq*t)) - wave2 = sigout * (a * sin(2*np.pi*freq*t) - b * cos(2*np.pi*freq*t)) - return wave1, wave2 - - def averageiq(self, data, freq, ti, tf): - '''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][:], data[1][:], freq, ti, tf)]) - return iorq.sum(axis=0) / self.number_of_records - - def filtro(self, iorq, cutoff): - b, a = butter_lowpass(cutoff, self.samp_freq*1e9) - - #ifi = np.array(signal.filtfilt(b,a,iorq[0])) - #qf = np.array(signal.filtfilt(b,a,iorq[1])) - iqf = [signal.filtfilt(b,a,iorq[i]) for i in np.arange(len(iorq))] - - return iqf - - def box(self, iorq, ti, tf): - si = int(self.samp_freq * ti) - sf = int(self.samp_freq * tf) - bxa = [sum(iorq[i][si:sf])/(sf-si) for i in np.arange(len(iorq))] - return bxa - - def gates_and_curves(self, data, freq, pulse, roi): - """return iq values of rois and prepare plottable curves for iq""" - times = [] - times.append(('aviq', time.time())) - iq = self.averageiq(data,freq*1e-9,*pulse) - times.append(('filtro', time.time())) - iqf = self.filtro(iq,self.bw_cutoff) - m = len(iqf[0]) // self.ndecimate - times.append(('iqdec', time.time())) - iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2) - t_axis = np.arange(m) * self.ndecimate / self.samp_freq - pulsig = np.abs(data[0][0]) - times.append(('pulsig', time.time())) - pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1) - self.curves = (t_axis, iqd[0], iqd[1], pulsig) - #print(times) - return [self.box(iqf,*r) for r in roi] - +# -*- coding: utf-8 -*- +""" +Created on Tue Nov 26 15:42:43 2019 + +@author: tartarotti_d-adm +""" + + +import sys +import atexit +import signal +import time +import numpy as np +import ctypes as ct +from numpy import sqrt, arctan2, sin, cos +import scipy.signal + +#from pylab import * + +#ADQAPI = ct.cdll.LoadLibrary("ADQAPI.dll") +ADQAPI = ct.cdll.LoadLibrary("libadq.so.0") + +#For different trigger modes +SW_TRIG = 1 +EXT_TRIG_1 = 2 #This external trigger does not work if the level of the trigger is very close to 0.5V. Now we have it close to 3V, and it works +EXT_TRIG_2 = 7 +EXT_TRIG_3 = 8 +LVL_TRIG = 3 +INT_TRIG = 4 +LVL_FALLING = 0 +LVL_RISING = 1 + +#samples_per_record=16384 +ADQ_TRANSFER_MODE_NORMAL = 0x00 +ADQ_CHANNELS_MASK = 0x3 + +#f_LO = 40 + +def butter_lowpass(cutoff, sr, order=5): + nyq = 0.5 * sr + normal_cutoff = cutoff / nyq + b, a = scipy.signal.butter(order, normal_cutoff, btype = 'low', analog = False) + return b, a + + +class Adq(object): + max_number_of_channels = 2 + samp_freq = 2 + #ndecimate = 50 # decimation ratio (2GHz / 40 MHz) + ndecimate = 50 + + def __init__(self, number_of_records, samples_per_record, bw_cutoff): + self.number_of_records = number_of_records + self.samples_per_record = samples_per_record + self.bw_cutoff = bw_cutoff + ADQAPI.ADQAPI_GetRevision() + + # Manually set return type from some ADQAPI functions + ADQAPI.CreateADQControlUnit.restype = ct.c_void_p + ADQAPI.ADQ_GetRevision.restype = ct.c_void_p + ADQAPI.ADQ_GetPtrStream.restype = ct.POINTER(ct.c_int16) + ADQAPI.ADQControlUnit_FindDevices.argtypes = [ct.c_void_p] + # Create ADQControlUnit + self.adq_cu = ct.c_void_p(ADQAPI.CreateADQControlUnit()) + ADQAPI.ADQControlUnit_EnableErrorTrace(self.adq_cu, 3, '.') + self.adq_num = 1 + + # Find ADQ devices + ADQAPI.ADQControlUnit_FindDevices(self.adq_cu) + n_of_ADQ = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu) + if n_of_ADQ != 1: + raise ValueError('number of ADQs must be 1, not %d' % n_of_ADQ) + + rev = ADQAPI.ADQ_GetRevision(self.adq_cu, self.adq_num) + revision = ct.cast(rev,ct.POINTER(ct.c_int)) + print('\nConnected to ADQ #1') + # Print revision information + print('FPGA Revision: {}'.format(revision[0])) + if (revision[1]): + print('Local copy') + else : + print('SVN Managed') + if (revision[2]): + print('Mixed Revision') + else : + print('SVN Updated') + print('') + + ADQ_CLOCK_INT_INTREF = 0 #internal clock source + ADQ_CLOCK_EXT_REF = 1 #internal clock source, external reference + ADQ_CLOCK_EXT_CLOCK = 2 #External clock source + ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF); + + ########################## + # Test pattern + #ADQAPI.ADQ_SetTestPatternMode(self.adq_cu, self.adq_num, 4) + ########################## + # Sample skip + #ADQAPI.ADQ_SetSampleSkip(self.adq_cu, self.adq_num, 1) + ########################## + + # Set trig mode + self.trigger = EXT_TRIG_1 + #trigger = LVL_TRIG + success = ADQAPI.ADQ_SetTriggerMode(self.adq_cu, self.adq_num, self.trigger) + if (success == 0): + print('ADQ_SetTriggerMode failed.') + if (self.trigger == LVL_TRIG): + success = ADQAPI.ADQ_SetLvlTrigLevel(self.adq_cu, self.adq_num, -100) + if (success == 0): + print('ADQ_SetLvlTrigLevel failed.') + success = ADQAPI.ADQ_SetTrigLevelResetValue(self.adq_cu, self.adq_num, 1000) + if (success == 0): + print('ADQ_SetTrigLevelResetValue failed.') + success = ADQAPI.ADQ_SetLvlTrigChannel(self.adq_cu, self.adq_num, 1) + if (success == 0): + print('ADQ_SetLvlTrigChannel failed.') + success = ADQAPI.ADQ_SetLvlTrigEdge(self.adq_cu, self.adq_num, LVL_RISING) + if (success == 0): + print('ADQ_SetLvlTrigEdge failed.') + elif (self.trigger == EXT_TRIG_1) : + success = ADQAPI.ADQ_SetExternTrigEdge(self.adq_cu, self.adq_num,2) + if (success == 0): + print('ADQ_SetLvlTrigEdge failed.') + # success = ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)) + # if (success == 0): + # print('SetTriggerThresholdVoltage failed.') + print("CHANNEL:"+str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num)))) + self.setup_target_buffers() + atexit.register(self.deletecu) + signal.signal(signal.SIGTERM, lambda *_: sys.exit(0)) + + def setup_target_buffers(self): + # Setup target buffers for data + self.target_buffers=(ct.POINTER(ct.c_int16 * self.samples_per_record * self.number_of_records) + * self.max_number_of_channels)() + for bufp in self.target_buffers: + bufp.contents = (ct.c_int16 * self.samples_per_record * self.number_of_records)() + + def deletecu(self): + # Only disarm trigger after data is collected + ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num) + ADQAPI.ADQ_MultiRecordClose(self.adq_cu, self.adq_num); + # Delete ADQControlunit + ADQAPI.DeleteADQControlUnit(self.adq_cu) + print('ADQ closed') + + def start(self): + """start data acquisition""" + # samples_per_records = samples_per_record/number_of_records + # Change number of pulses to be acquired acording to how many records are taken + # Start acquisition + ADQAPI.ADQ_MultiRecordSetup(self.adq_cu, self.adq_num, + self.number_of_records, + self.samples_per_record) + + ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num) + ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num) + + def getdata(self): + """wait for aquisition to be finished and get data""" + #start = time.time() + while(ADQAPI.ADQ_GetAcquiredAll(self.adq_cu,self.adq_num) == 0): + time.sleep(0.001) + #if (self.trigger == SW_TRIG): + # ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num) + #mid = time.time() + status = ADQAPI.ADQ_GetData(self.adq_cu, self.adq_num, self.target_buffers, + self.samples_per_record * self.number_of_records, 2, + 0, self.number_of_records, ADQ_CHANNELS_MASK, + 0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL); + #print(time.time()-mid,mid-start) + if not status: + raise ValueError('no succesS from ADQ_GetDATA') + # Now this is an array with all records, but the time is artificial + data = [] + for ch in range(2): + onedim = np.frombuffer(self.target_buffers[ch].contents, dtype=np.int16) + data.append(onedim.reshape(self.number_of_records, self.samples_per_record) / float(2**14)) # 14 bits ADC + return data + + def acquire(self): + self.start() + return self.getdata() + + def sinW(self,sig,freq,ti,tf): + # sig: signal array + # freq + # ti, tf: initial and end time + si = int(ti * self.samp_freq) + nperiods = freq * (tf - ti) + n = int(round(max(2, int(nperiods)) / nperiods * (tf-ti) * self.samp_freq)) + self.nperiods = n + t = np.arange(si, len(sig)) / self.samp_freq + t = t[:n] + self.pulselen = n / self.samp_freq + sig = sig[si:si+n] + a = 2*np.sum(sig*np.cos(2*np.pi*freq*t))/len(sig) + b = 2*np.sum(sig*np.sin(2*np.pi*freq*t))/len(sig) + return a, b + + def mix(self, sigin, sigout, freq, ti, tf): + # sigin, sigout: signal array, incomping, output + # freq + # ti, tf: initial and end time if sigin + a, b = self.sinW(sigin, freq, ti, tf) + phase = arctan2(a,b) * 180 / np.pi + amp = sqrt(a**2 + b**2) + a, b = a/amp, b/amp + #si = int(ti * self.samp_freq) + t = np.arange(len(sigout)) / self.samp_freq + wave1 = sigout * (a * cos(2*np.pi*freq*t) + b * sin(2*np.pi*freq*t)) + wave2 = sigout * (a * sin(2*np.pi*freq*t) - b * cos(2*np.pi*freq*t)) + return wave1, wave2 + + def averageiq(self, data, freq, ti, tf): + '''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][:], data[1][:], freq, ti, tf)]) + return iorq.sum(axis=0) / self.number_of_records + + def filtro(self, iorq, cutoff): + b, a = butter_lowpass(cutoff, self.samp_freq*1e9) + + #ifi = np.array(scipy.signal.filtfilt(b,a,iorq[0])) + #qf = np.array(scipy.signal.filtfilt(b,a,iorq[1])) + iqf = [scipy.signal.filtfilt(b,a,iorq[i]) for i in np.arange(len(iorq))] + + return iqf + + def box(self, iorq, ti, tf): + si = int(self.samp_freq * ti) + sf = int(self.samp_freq * tf) + bxa = [sum(iorq[i][si:sf])/(sf-si) for i in np.arange(len(iorq))] + return bxa + + def gates_and_curves(self, data, freq, pulse, roi): + """return iq values of rois and prepare plottable curves for iq""" + self.ndecimate = int(round(2E9/freq)) + times = [] + times.append(('aviq', time.time())) + iq = self.averageiq(data,freq*1e-9,*pulse) + times.append(('filtro', time.time())) + iqf = self.filtro(iq,self.bw_cutoff) + m = len(iqf[0]) // self.ndecimate + ll = m * self.ndecimate + iqf = [iqfx[0:ll] for iqfx in iqf] + times.append(('iqdec', time.time())) + iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2) + t_axis = np.arange(m) * self.ndecimate / self.samp_freq + pulsig = np.abs(data[0][0]) + times.append(('pulsig', time.time())) + pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1) + self.curves = (t_axis, iqd[0], iqd[1], pulsig) + #print(times) + return [self.box(iqf,*r) for r in roi] + diff --git a/secop_psi/ultrasound.py b/secop_psi/ultrasound.py index f645f10..86be4c9 100644 --- a/secop_psi/ultrasound.py +++ b/secop_psi/ultrasound.py @@ -30,7 +30,8 @@ import numpy as np import secop_psi.iqplot as iqplot from secop_psi.adq_mr import Adq from secop.core import Attached, BoolType, Done, FloatRange, HasIO, \ - IntRange, Module, Parameter, Readable, StringIO, StringType + IntRange, Module, Parameter, Readable, StringIO, StringType, \ + IDLE, DISABLED, TupleOf, ArrayOf from secop.properties import Property @@ -54,7 +55,6 @@ class Roi(Readable): time = Parameter('start 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) - #status = Parameter(export=False) pollinterval = Parameter(export=False) interval = (0,0) @@ -67,6 +67,9 @@ class Roi(Readable): def calc_interval(self): self.interval = (self.time - 0.5 * self.size, self.time + 0.5 * self.size) + def read_status(self): + return (IDLE, '') if self.enable else (DISABLED, 'disabled') + def write_time(self, value): self.time = value self.calc_interval() @@ -84,7 +87,7 @@ class Pars(Module): timestamp = Parameter('unix timestamp', StringType(), default='0', readonly=False) temperature = Parameter('T', FloatRange(unit='K'), default=0, readonly=False) mf = Parameter('field', FloatRange(unit='T'), default=0, readonly=False) - sr = Parameter('rotaion angle', FloatRange(unit='deg'), default=0, readonly=False) + sr = Parameter('rotation angle', FloatRange(unit='deg'), default=0, readonly=False) class FreqStringIO(StringIO): @@ -93,16 +96,19 @@ class FreqStringIO(StringIO): class Frequency(HasIO, Readable): pars = Attached() - sr = Property('samples per record', datatype=IntRange(), default=16384) + curves = Attached(mandatory=False) +# sr = Property('samples per record', datatype=IntRange(), default=16384) 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'), @@ -130,6 +136,7 @@ class Frequency(HasIO, Readable): self.adq = Adq(self.nr, self.sr, self.bw) self.roilist = [] self.write_nr(self.nr) + self.write_sr(self.sr) self.skipctrl = 0 self.plotter = iqplot.Plot(self.maxy) self.calc_interval() @@ -151,6 +158,9 @@ class Frequency(HasIO, Readable): # self.pollinterval = value * 0.0001 return value + def write_sr(self, value): + return value + def register_roi(self, roi): self.roilist.append(roi) @@ -176,7 +186,11 @@ class Frequency(HasIO, Readable): """main poll loop body""" if self.lastfreq is None: self.lastfreq = self.set_freq() + if self.rusmode: + self.sr = int(12e9/self.lastfreq) #picking up 12 period at the ith frequency in the time scale +# self.adq.samples_per_record = self.sr self.adq.start() + if self.starttime is None: self.starttime = time.time() times = [] @@ -200,6 +214,8 @@ class Frequency(HasIO, Readable): gates = self.adq.gates_and_curves(data, freq, self.interval, [r.interval for r in roilist]) + if self.curves: # if attached Curves module is defined, update it + self.curves.value = self.adq.curves if self.save: times.append(('save',time.time())) tdata, idata, qdata, pdata = self.adq.curves @@ -259,3 +275,9 @@ class Frequency(HasIO, Readable): self.freq = sorted((self.freq - self.maxstep, newfreq, self.freq + self.maxstep))[1] #print(times) return Done + + +class Curves(Readable): + value = Parameter("t, i, q, pulse curves", + TupleOf(*[ArrayOf(FloatRange(), 0, 16283) for _ in range(4)]), default=[[]] * 4) +