From 7bfc6b3cb866712b140a3b72de781258ebe4d1ed Mon Sep 17 00:00:00 2001 From: Last Davis Vern Date: Wed, 30 Jul 2025 13:56:25 +0200 Subject: [PATCH] Live data acquisition over SECoP --- frappy_psi/tnmr/OTFModule.py | 61 ++++++++++++++++---------- frappy_psi/tnmr/sequence_generation.py | 3 ++ frappy_psi/tnmr/tnmr_interface.py | 11 +++-- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/frappy_psi/tnmr/OTFModule.py b/frappy_psi/tnmr/OTFModule.py index c05f8662..3212af69 100644 --- a/frappy_psi/tnmr/OTFModule.py +++ b/frappy_psi/tnmr/OTFModule.py @@ -27,7 +27,7 @@ import time import os import traceback -class ProgrammedSequence(fc.Readable): +class ProgrammedSequence(fc.Drivable): # Drivable only for kill() funcitonality """An NMR device being driven by an instance of TNMR. Requires that an instance of TNMR is opened before creation. Use @@ -72,7 +72,12 @@ class ProgrammedSequence(fc.Readable): imags=fc.ArrayOf(fc.FloatRange(), maxlen=4096), # imag values t =fc.ArrayOf(fc.FloatRange(), maxlen=4096)), # times (starting from zero) default={ 'reals': [], 'imags': [], 't': [] }) - status = fc.Parameter(datatype=frappy.datatypes.StatusType(fc.Readable, "DISABLED", 'PREPARED', 'BUSY'), default=('IDLE', 'ok - uncompiled')) + target = fc.Parameter('dummy', fc.StructOf(reals=fc.ArrayOf(fc.FloatRange(), maxlen=4096), # real values + imags=fc.ArrayOf(fc.FloatRange(), maxlen=4096), # imag values + t =fc.ArrayOf(fc.FloatRange(), maxlen=4096)), # times (starting from zero) + default={ 'reals': [], 'imags': [], 't': [] }, readonly=True, visibility='w--') + + status = fc.Parameter(datatype=frappy.datatypes.StatusType(fc.Drivable, "DISABLED", 'PREPARED', 'BUSY'), default=('IDLE', 'ok - uncompiled')) pollinterval = fc.Parameter(default=1) # basic @@ -99,6 +104,7 @@ class ProgrammedSequence(fc.Readable): compiled_parameters = {} # so that we can store the values of parameters only when compiling, effectively giving us an instance of each parameter loaded into TNMR, as well as "targets" (those above) inited = False + starting = False approx_sequence_length = 0 ### SETUP @@ -150,6 +156,20 @@ class ProgrammedSequence(fc.Readable): ### READ/WRITE + def read_status(self): + if not(self.inited): + self.status = ('ERROR', 'TNMR disconnected!') + else: + if(self.starting): + self.status = ('BUSY', 'starting') + else: + if(self.tnmr().acquisition_running()): + self.status = ('BUSY', 'acquiring') + elif(self.status[1] == 'acquiring'): + # we've just finished acquiring, in frappy's perspective + self.status = ('PREPARED', 'compiled') + return self.status + def write_title(self, t): self.title = t self.status = ('IDLE', 'ok - uncompiled') @@ -196,7 +216,7 @@ class ProgrammedSequence(fc.Readable): return self.read_acq_phase_cycle() def read_num_acqs(self): - return self.tnmr().get_nmrparameter('Scans 1D') + return int(self.tnmr().get_nmrparameter('Scans 1D')) def write_num_acqs(self, t): if(self.status[0] != 'BUSY'): @@ -223,31 +243,23 @@ class ProgrammedSequence(fc.Readable): return self.read_sequence_data() - def read_status(self): - if(self.tnmr().acquisition_running()): - self.status = ('BUSY', 'acquiring') - elif(self.status[1] == 'acquiring'): - # we've just finished acquiring, in frappy's perspective - self.status = ('PREPARED', 'compiled') - return self.status - def read_value(self): newvals = {} - try: - d = self.tnmr().get_data() - newvals['reals'] = d[0] - newvals['imags'] = d[1] - newvals['t'] = [ self.compiled_parameters['acquisition_time'] * i/len(d[0]) for i in range(0, len(d[0])) ] - except: - newvals['reals'] = [] - newvals['imags'] = [] - newvals['t'] = [] + #try: + d = self.tnmr().get_data() + newvals['reals'] = d[0] + newvals['imags'] = d[1] + newvals['t'] = [ self.compiled_parameters['acquisition_time'] * i/len(d[0]) for i in range(0, len(d[0])) ] + #except: + # newvals['reals'] = [] + # newvals['imags'] = [] + # newvals['t'] = [] return newvals def read_num_acqs_actual(self): try: n = self.tnmr().get_nmrparameter('Actual Scans 1D') - return n + return int(n) except: return 0 @@ -330,9 +342,8 @@ class ProgrammedSequence(fc.Readable): def __zero_go(self): '''Tells TNMR to acquire data. Only call after __compile_sequence().''' - if(self.status[0] != 'BUSY'): - self.status = ('BUSY', 'acquiring') - self.tnmr().ZeroGo(lock=False, check_time=max(self.approx_sequence_length*5, 5)) + if(self.status[0] != 'BUSY' or self.starting): + self.tnmr().ZeroGo(lock=False, check_time=max(int(self.approx_sequence_length*1.5), 5)) def __compile_and_run(self, thread=True): '''Compiles and runs the currently-loaded sequence @@ -341,9 +352,11 @@ class ProgrammedSequence(fc.Readable): ---------- thread: bool, determines if this should open a child thread and detach the process ''' + self.starting = True self.__compile_sequence() time.sleep(1.0) self.__zero_go() + self.starting = False diff --git a/frappy_psi/tnmr/sequence_generation.py b/frappy_psi/tnmr/sequence_generation.py index 928eb673..b0b366b3 100644 --- a/frappy_psi/tnmr/sequence_generation.py +++ b/frappy_psi/tnmr/sequence_generation.py @@ -41,6 +41,9 @@ def get_single_pulse_block(name, pulse_width, pulse_height, delay_time, phase_cy delay_time = float(delay_time.strip()[:-1]) / 1e6 else: delay_time = float(delay_time.strip()) # assume in us + + delay_time_rounded = int(delay_time*10) / 10 # nearest 10ns + delay_time = delay_time_rounded ph = name + '_phase' rl = name + '_delay' diff --git a/frappy_psi/tnmr/tnmr_interface.py b/frappy_psi/tnmr/tnmr_interface.py index 19ecebbc..551847c4 100644 --- a/frappy_psi/tnmr/tnmr_interface.py +++ b/frappy_psi/tnmr/tnmr_interface.py @@ -155,8 +155,8 @@ class TNMR: print('Zero-going...') ntnmr = self.get_instance() if not(self.acquisition_running()): - print('Reset') - ntnmr.Reset() # to avoid hardware issues? EDIT: Doesn't seem to do much... + #print('Reset') + #ntnmr.Reset() # to avoid hardware issues? EDIT: Doesn't seem to do much... if(CHECK_MODE == 'data'): print('Artificially setting the zeroth point to NULL for error detection.') ntnmr.SetDataPoint(1, [0,0]) @@ -258,7 +258,7 @@ class TNMR: """ print('I: Saving') if filepath == '': - self.get_instance().Save + self.get_instance().Save() else: self.get_instance().SaveAs(filepath) print(f'I: Saved to file {filepath}') @@ -378,6 +378,11 @@ class TNMR: """ ntnmr = self.get_instance() + + if (self.acquisition_running()): + ntnmr.Abort() + print('W: Aborting currently running acquisition!') + print(f'Loading sequence at {filename}') ntnmr.CloseActiveFile() success = ntnmr.OpenFile(TEMPLATE_FILE_PATH + 'tmp.tnt')