A few things: 1. Got it working again; 2. Renamed files to make more sense; 3. Replaced template tmp.tnt with an emptied out file that previously took data, now data is collected correctly (bug, I'm not sure where this need comes from but this is, as far as I know, a permanent workaround); 4. Added automatic COM interface restart on errors compiling; 5. Implemented variable acquisition times.

This commit is contained in:
2025-06-12 11:04:57 +02:00
parent 05324a8966
commit 365f0a2374
8 changed files with 125 additions and 115 deletions

View File

@@ -11,8 +11,8 @@ frappy-based module for generating and running pulse sequences on TNMR (Tecmag).
"""
#On-the-fly!
import TNMRExt.TNMR_DG_Extension as te
import TNMRExt.SequenceGeneration as seq_gen
import frappy_psi.tnmr.tnmr_interface as te
import frappy_psi.tnmr.sequence_generation as seq_gen
import frappy.core as fc
import frappy
@@ -31,7 +31,7 @@ class ProgrammedSequence(fc.Readable):
Instance Attributes
-------------------
(parameter) title: a title which will be embedded to the sequence files. Use this for identification.
(parameter) sequence_data: a dictionary describing the currently-built sequence
(parameter) sequence_data: an array of structs: keys are { 'pulse_width': (width of pulse in us), 'pulse_height': (amplitude of pulse in a.u.), 'relaxation_time': (relaxation time in us), 'phase_cycle': (a str denoting a phase cycle, e.g., '0 1 2 3') }
(parameter) value: an array of complexes representing the TNMR data return (technically inherited from Readable)
(parameter) acquisition_time: float (usecs) which describes the length of acquisition
(parameter) ringdown_time: float (usecs) which describes the length of ringdown
@@ -64,7 +64,10 @@ class ProgrammedSequence(fc.Readable):
"""
# inherited
value = fc.Parameter('data_return', fc.ArrayOf(fc.FloatRange(), maxlen=4096), default=[])
value = fc.Parameter('data_return', 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': [] })
status = fc.Parameter(datatype=frappy.datatypes.StatusType(fc.Readable, "DISABLED", 'PREPARED', 'BUSY'))
pollinterval = fc.Parameter(default=1)
@@ -73,19 +76,19 @@ class ProgrammedSequence(fc.Readable):
sequence_data = fc.Parameter('sequence_config', fc.ArrayOf(fc.StructOf(pulse_width=fc.FloatRange(unit='u'),
pulse_height=fc.FloatRange(),
relaxation_time=fc.FloatRange(unit='u'),
phase_cycle=fc.StringType())))
phase_cycle=fc.StringType())), default=[], readonly=False)
# sequence edit
pulse_width = fc.Parameter('pulse_width', fc.FloatRange(unit='u'), readonly=False, group='pulse_editor')
pulse_height = fc.Parameter('pulse_height', fc.FloatRange(), readonly=False, group='pulse_editor')
relaxation_time = fc.Parameter('relaxation_time', fc.FloatRange(unit='u', min=0.1), readonly=False, group='pulse_editor')
phase_cycle = fc.Parameter('phase_cycle', fc.StringType(), readonly=False, group='pulse_editor', default='')
#pulse_width = fc.Parameter('pulse_width', fc.FloatRange(unit='u'), readonly=False, group='pulse_editor', default=5)
#pulse_height = fc.Parameter('pulse_height', fc.FloatRange(), readonly=False, group='pulse_editor', default=40)
#relaxation_time = fc.Parameter('relaxation_time', fc.FloatRange(unit='u', min=0.1), readonly=False, group='pulse_editor', default=50)
#phase_cycle = fc.Parameter('phase_cycle', fc.StringType(), readonly=False, group='pulse_editor', default='')
# final details
acquisition_time = fc.Parameter('acquisition_time', fc.FloatRange(unit='u'), readonly=True, group='sequence_editor', default=204.8) # this is a limit set by the dwell limit and number of acquisition points (1024, TODO: Make this adjustable)
ringdown_time = fc.Parameter('ringdown_time', fc.FloatRange(unit='u'), readonly=False, group='sequence_editor')
pre_acquisition_time = fc.Parameter('pre_acquisition_time', fc.FloatRange(unit='u'), readonly=False, group='sequence_editor')
post_acquisition_time = fc.Parameter('post_acquisition_time', fc.FloatRange(unit='m'), readonly=False, group='sequence_editor')
acquisition_time = fc.Parameter('acquisition_time', fc.FloatRange(unit='u'), readonly=False, group='sequence_editor', default=204.8) # this is a limit set by the dwell limit and number of acquisition points (1024, TODO: Make this adjustable)
ringdown_time = fc.Parameter('ringdown_time', fc.FloatRange(unit='u'), readonly=False, group='sequence_editor', default=1)
pre_acquisition_time = fc.Parameter('pre_acquisition_time', fc.FloatRange(unit='u'), readonly=False, group='sequence_editor', default=1)
post_acquisition_time = fc.Parameter('post_acquisition_time', fc.FloatRange(unit='m'), readonly=False, group='sequence_editor', default=500)
acq_phase_cycle = fc.Parameter('acq_phase_cycle', fc.StringType(), readonly=False, group='sequence_editor', default='')
inited = False
@@ -101,111 +104,79 @@ class ProgrammedSequence(fc.Readable):
pass
### COMMANDS
@fc.Command(description="Add Pulse", group='pulse_editor', argument={ 'type': 'struct' }, members={ 'a': { 'type': 'string' }})
def add_pulse(self):
if(self.status == ('PREPARED', 'compiled')):
self.status = ('IDLE', 'ok - uncompiled')
data = list(self.sequence_data) # should be a tuple when it comes out of ArrayOf __call__, so make it mutable
data += [ { 'pulse_width': self.pulse_width, 'pulse_height': self.pulse_height, 'relaxation_time': self.relaxation_time, 'phase_cycle': self.phase_cycle } ]
self.sequence_data = data
#@fc.Command(description="Add Pulse", group='pulse_editor')
#def add_pulse(self):
# if(self.status == ('PREPARED', 'compiled')):
# self.status = ('IDLE', 'ok - uncompiled')
# data = list(self.sequence_data) # should be a tuple when it comes out of ArrayOf __call__, so make it mutable
# data += [ { 'pulse_width': self.pulse_width, 'pulse_height': self.pulse_height, 'relaxation_time': self.relaxation_time, 'phase_cycle': self.phase_cycle } ]
# self.sequence_data = data
@fc.Command(description="Pop Pulse", group='pulse_editor')
def pop_pulse(self):
if(self.status == ('PREPARED', 'compiled')):
self.status = ('IDLE', 'ok - uncompiled')
data = list(self.sequence_data) # should be a tuple when it comes out of ArrayOf __call__, so make it mutable
data = data[:-1] # chop off the tail
self.sequence_data = data
#@fc.Command(description="Pop Pulse", group='pulse_editor')
#def pop_pulse(self):
# if(self.status == ('PREPARED', 'compiled')):
# self.status = ('IDLE', 'ok - uncompiled')
# data = list(self.sequence_data) # should be a tuple when it comes out of ArrayOf __call__, so make it mutable
# data = data[:-1] # chop off the tail
# self.sequence_data = data
@fc.Command(description="Compile", group='sequence_editor')
def compile_sequence(self):
threading.Thread(target=lambda s=self: s.__compile_sequence()).start()
#@fc.Command(description="Compile", group='sequence_editor')
#def compile_sequence(self):
# threading.Thread(target=lambda s=self: s.__compile_sequence()).start()
@fc.Command(description="Run")
def run(self):
threading.Thread(target=lambda s=self: s.__zero_go()).start()
#@fc.Command(description="Run")
#def run(self):
# threading.Thread(target=lambda s=self: s.__zero_go()).start()
@fc.Command(description="Compile & Run")
def compile_and_run(self):
threading.Thread(target=lambda s=self: s.__compile_and_run()).start()
@fc.Command(description="Compile & Run", argument={'type': 'bool'})
def compile_and_run(self, thread=True):
if(thread):
threading.Thread(target=lambda s=self: s.__compile_and_run()).start()
else:
self.__compile_and_run()
### READ/WRITE
def read_value(self):
return self.value # TODO: this is only reals
def read_title(self):
return self.title
def write_title(self, t):
self.title = t
self.status = ('IDLE', 'ok - uncompiled')
return self.read_title()
#def write_pulse_width(self, t):
# self.pulse_width = t
# return self.read_pulse_width()
def read_sequence_data(self):
return self.sequence_data
#def write_pulse_height(self, t):
# self.pulse_height = t
# return self.read_pulse_height()
def read_pulse_width(self):
return self.pulse_width
#def write_relaxation_time(self, t):
# self.relaxation_time = t
# return self.read_relaxation_time()
def write_pulse_width(self, t):
self.pulse_width = t
return self.read_pulse_width()
def read_pulse_height(self):
return self.pulse_height
def write_pulse_height(self, t):
self.pulse_height = t
return self.read_pulse_height()
def read_relaxation_time(self):
return self.relaxation_time
def write_relaxation_time(self, t):
self.relaxation_time = t
return self.read_relaxation_time()
def read_phase_cycle(self):
return self.phase_cycle
def write_phase_cycle(self, t):
self.phase_cycle = t
return self.read_phase_cycle()
def read_acquisition_time(self):
return self.acquisition_time
#def write_phase_cycle(self, t):
# self.phase_cycle = t
# return self.read_phase_cycle()
def write_acquisition_time(self, t):
self.acquisition_time = t
self.status = ('IDLE', 'ok - uncompiled')
return self.read_acquisition_time()
def read_ringdown_time(self):
return self.ringdown_time
def write_ringdown_time(self, t):
self.ringdown_time = t
self.status = ('IDLE', 'ok - uncompiled')
return self.read_ringdown_time()
def read_pre_acquisition_time(self):
return self.pre_acquisition_time
def write_pre_acquisition_time(self, t):
self.pre_acquisition_time = t
self.status = ('IDLE', 'ok - uncompiled')
return self.read_pre_acquisition_time()
def read_post_acquisition_time(self):
return self.post_acquisition_time
def write_post_acquisition_time(self, t):
self.post_acquisition_time = t
self.status = ('IDLE', 'ok - uncompiled')
return self.read_post_acquisition_time()
def read_acq_phase_cycle(self):
return self.acq_phase_cycle
def write_acq_phase_cycle(self, t):
self.acq_phase_cycle = t
@@ -214,7 +185,7 @@ class ProgrammedSequence(fc.Readable):
### PRIVATE (Utility)
def __compile_sequence(self):
if(self.status != ('PREPARED', 'compiled')) and (self.status[0] != 'BUSY'):
if(self.status[0] != 'BUSY'):
self.status = ('BUSY', 'compiling')
# first, create the sequence
seq = seq_gen.get_initial_block()
@@ -248,13 +219,30 @@ class ProgrammedSequence(fc.Readable):
if(self.status[0] != 'BUSY'):
self.status = ('BUSY', 'acquiring')
self.tnmr().ZeroGo(lock=True, interval=0.5)
self.value = self.tnmr().get_data()[0] # TODO: this is only reals...
print(self.value)
newvals = {}
newvals['reals'] = self.tnmr().get_data()[0]
newvals['imags'] = self.tnmr().get_data()[1]
newvals['t'] = self.tnmr().get_data_times()
self.value = newvals
self.status = ('PREPARED', 'compiled')
def __compile_and_run(self):
self.__compile_sequence()
self.__zero_go()
def __compile_and_run(self, thread=True, recurse=True):
pythoncom.CoInitialize()
try:
self.__compile_sequence()
self.__zero_go()
except AttributeError as e:
print(f'Attribute error on compile and run.{" Resetting the COM interface and retrying..." if recurse else " Resetting did not fix this problem!"}')
self.status = ('IDLE', 'ok - uncompiled')
self.tnmr().reset_NTNMR_instance()
self.__compile_and_run(thread, recurse=False)
except Exception as e:
print('Failed to compile and run!')
print(str(e))
print(repr(e))
self.status = ('IDLE', 'ok - uncompiled')
if(thread):
pythoncom.CoUninitialize()