frappy_psi.tnmr: added a module (frappy_psi.tnmr.OTFModule) to interface with the Tecmag NMR (TNMR) program from a frappy server.
This commit is contained in:
268
frappy_psi/tnmr/OTFModule.py
Normal file
268
frappy_psi/tnmr/OTFModule.py
Normal file
@@ -0,0 +1,268 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
TNMR_DG_Extension
|
||||
______________________
|
||||
|
||||
Version: 1.0
|
||||
Authors: Davis Garrad (Paul Scherrer Institute, CH)
|
||||
______________________
|
||||
|
||||
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.core as fc
|
||||
import frappy
|
||||
|
||||
import win32com
|
||||
import pythoncom
|
||||
import numpy as np
|
||||
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
|
||||
class ProgrammedSequence(fc.Readable):
|
||||
"""An NMR device being driven by an instance of TNMR. Requires that an instance of TNMR is opened before creation.
|
||||
|
||||
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) 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
|
||||
(parameter) pre_acquisition_time: float (usecs) which describes the length of time to wait after ringdown finishes (1u is okay)
|
||||
(parameter) post_acquisition_time: float (ms) which describes the length of time to wait after finishing acquisition
|
||||
(parameter) acq_phase_cycle: str, the phase cycle to run on acquisition (eg., '0 1 1 2', '0 1 2 3', '1 1 2 2 0 0 3 3 1 2 3 4', ...)
|
||||
|
||||
Methods
|
||||
-------
|
||||
(cmd) add_pulse: adds a pulse to the end of the sequence.
|
||||
Arguments:
|
||||
- pulse_width: pulse width to add to sequence (usec)
|
||||
- pulse_height: pulse height to add to sequence (a.u.)
|
||||
- relaxation_time: relaxation time to add to sequence (usec)
|
||||
- phase_cycle: the phase cycle to run for this pulse (eg., '0 1 1 2', '0 1 2 3', '1 1 2 2 0 0 3 3 1 2 3 4', ...)
|
||||
(cmd) pop_pulse: removes the last pulse from the sequence
|
||||
(cmd) compile_sequence: finishes building the sequence, saves it and its configuration to files (timestamped), and sets the system ready to run
|
||||
(cmd) run: if compiled, starts data acquisition on TNMR (async)
|
||||
(cmd) compile and run: you can guess
|
||||
|
||||
Inherited Attributes
|
||||
--------------------
|
||||
name: from Module
|
||||
logger: from Module
|
||||
cfgdict: from Module
|
||||
src: from Module
|
||||
status: from Readable
|
||||
value: from Readable
|
||||
pollinterval: from Readable
|
||||
"""
|
||||
|
||||
# inherited
|
||||
value = fc.Parameter('data_return', fc.ArrayOf(fc.FloatRange(), maxlen=4096), default=[])
|
||||
status = fc.Parameter(datatype=frappy.datatypes.StatusType(fc.Readable, "DISABLED", 'PREPARED', 'BUSY'))
|
||||
pollinterval = fc.Parameter(default=1)
|
||||
|
||||
# basic
|
||||
title = fc.Parameter('title', fc.StringType(), default='Sequence', readonly=False)
|
||||
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())))
|
||||
|
||||
# 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='')
|
||||
|
||||
# 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')
|
||||
acq_phase_cycle = fc.Parameter('acq_phase_cycle', fc.StringType(), readonly=False, group='sequence_editor', default='')
|
||||
|
||||
inited = False
|
||||
|
||||
### SETUP
|
||||
def tnmr(self):
|
||||
if not(self.inited):
|
||||
self.ntnmr = te.TNMR()
|
||||
self.inited = True
|
||||
return self.ntnmr
|
||||
|
||||
def initialReads(self):
|
||||
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="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="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()
|
||||
|
||||
### 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 read_sequence_data(self):
|
||||
return self.sequence_data
|
||||
|
||||
def read_pulse_width(self):
|
||||
return self.pulse_width
|
||||
|
||||
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_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
|
||||
self.status = ('IDLE', 'ok - uncompiled')
|
||||
return self.read_acq_phase_cycle()
|
||||
|
||||
### PRIVATE (Utility)
|
||||
def __compile_sequence(self):
|
||||
if(self.status != ('PREPARED', 'compiled')) and (self.status[0] != 'BUSY'):
|
||||
self.status = ('BUSY', 'compiling')
|
||||
# first, create the sequence
|
||||
seq = seq_gen.get_initial_block()
|
||||
i = 0
|
||||
for s in self.sequence_data:
|
||||
seq = seq_gen.combine_blocks(seq, seq_gen.get_single_pulse_block(f'pulse_{i}', str(s['pulse_width']) + 'u',
|
||||
str(s['pulse_height']),
|
||||
str(s['relaxation_time']) + 'u',
|
||||
str(s['phase_cycle'])))
|
||||
i += 1
|
||||
seq = seq_gen.combine_blocks(seq, seq_gen.get_final_block(str(self.ringdown_time) + 'u',
|
||||
str(self.pre_acquisition_time) + 'u',
|
||||
str(self.acquisition_time) + 'u',
|
||||
str(self.post_acquisition_time) + 'm',
|
||||
str(self.acq_phase_cycle)))
|
||||
|
||||
# then, save the thing
|
||||
filepath = os.getcwd()
|
||||
filename = self.title + f'_{time.time()}'
|
||||
filename = filepath + '/' + filename.replace('.','')
|
||||
seq_gen.save_sequence(filename, seq)
|
||||
seq_gen.save_sequence_cfg(filename, seq)
|
||||
|
||||
# then, load the thing into TNMR
|
||||
self.tnmr().load_sequence(filename)
|
||||
|
||||
# finally, let ourselves know we're ready
|
||||
self.status = ('PREPARED', 'compiled')
|
||||
|
||||
def __zero_go(self):
|
||||
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)
|
||||
self.status = ('PREPARED', 'compiled')
|
||||
|
||||
def __compile_and_run(self):
|
||||
self.__compile_sequence()
|
||||
self.__zero_go()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user