Fixed hardware issue?
This commit is contained in:
@@ -1,13 +1,14 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
TNMR_DG_Extension
|
OTFModule
|
||||||
______________________
|
______________________
|
||||||
|
|
||||||
Version: 1.0
|
Version: 1.0
|
||||||
Authors: Davis Garrad (Paul Scherrer Institute, CH)
|
Authors: Davis Garrad (Paul Scherrer Institute, CH)
|
||||||
______________________
|
______________________
|
||||||
|
|
||||||
frappy-based module for generating and running pulse sequences on TNMR (Tecmag).
|
frappy-based module for generating and running pulse sequences on TNMR (Tecmag). The On-The-Fly module is meant to represent a SECoP node frappy-side, so it really just interacts with the TNMR software.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#On-the-fly!
|
#On-the-fly!
|
||||||
|
|
||||||
@@ -24,33 +25,36 @@ import numpy as np
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
class ProgrammedSequence(fc.Readable):
|
class ProgrammedSequence(fc.Readable):
|
||||||
"""An NMR device being driven by an instance of TNMR. Requires that an instance of TNMR is opened before creation.
|
"""An NMR device being driven by an instance of TNMR. Requires that an instance of TNMR is opened before creation.
|
||||||
|
|
||||||
Instance Attributes
|
Use
|
||||||
-------------------
|
---
|
||||||
(parameter) title: a title which will be embedded to the sequence files. Use this for identification.
|
Generating a pulse sequence is as simple as setting the sequence_data parameter to be a list of sequence dictionaries, in the order you want them to be executed. The next step is to set the acquisition parameters (see below), including title, acquisition_time, pre_acquisition_time, post_acquisition_time, ringdown_time, and acq_phase_cycle.
|
||||||
(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
|
|
||||||
(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
|
Once all the parameters are set, you can call the frappy command and member function compile_and_run() (NOT the private member function __compile_and_run()).
|
||||||
-------
|
|
||||||
(cmd) add_pulse: adds a pulse to the end of the sequence.
|
Attributes
|
||||||
Arguments:
|
----------
|
||||||
- pulse_width: pulse width to add to sequence (usec)
|
value: an array of complexes representing the TNMR data return (technically inherited from Readable)
|
||||||
- pulse_height: pulse height to add to sequence (a.u.)
|
sequence_data: an array of structs: keys are { 'pulse_width': (width of pulse in us), 'pulse_height': (amplitude of pulse in a.u.), 'delay_time': (delay time in us), 'phase_cycle': (a str denoting a phase cycle, e.g., '0 1 2 3') }
|
||||||
- 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', ...)
|
Acquisition Parameters
|
||||||
(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
|
title: a title which will be embedded to the sequence files. Use this for identification.
|
||||||
(cmd) run: if compiled, starts data acquisition on TNMR (async)
|
acquisition_time: float (usecs) which describes the length of acquisition
|
||||||
(cmd) compile and run: you can guess
|
ringdown_time: float (usecs) which describes the length of ringdown
|
||||||
|
pre_acquisition_time: float (usecs) which describes the length of time to wait after ringdown finishes (1u is okay)
|
||||||
|
post_acquisition_time: float (ms) which describes the length of time to wait after finishing acquisition
|
||||||
|
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', ...)
|
||||||
|
num_scans: int (ct), the number of 1D scans to take per sequence
|
||||||
|
obs_freq: float (MHz), the NMR frequency
|
||||||
|
|
||||||
|
Commands
|
||||||
|
--------
|
||||||
|
compile and run: finishes building the sequence, saves it and its configuration to files (timestamped), and starts data acquisition on TNMR (async)
|
||||||
|
|
||||||
Inherited Attributes
|
Inherited Attributes
|
||||||
--------------------
|
--------------------
|
||||||
@@ -75,17 +79,11 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
title = fc.Parameter('title', fc.StringType(), default='Sequence', readonly=False)
|
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'),
|
sequence_data = fc.Parameter('sequence_config', fc.ArrayOf(fc.StructOf(pulse_width=fc.FloatRange(unit='u'),
|
||||||
pulse_height=fc.FloatRange(),
|
pulse_height=fc.FloatRange(),
|
||||||
relaxation_time=fc.FloatRange(unit='u'),
|
delay_time=fc.FloatRange(unit='u'),
|
||||||
phase_cycle=fc.StringType())), default=[], readonly=False)
|
phase_cycle=fc.StringType())), default=[], readonly=False)
|
||||||
|
|
||||||
# sequence edit
|
|
||||||
#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
|
# final details
|
||||||
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)
|
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
|
||||||
ringdown_time = fc.Parameter('ringdown_time', fc.FloatRange(unit='u'), readonly=False, group='sequence_editor', default=1)
|
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)
|
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)
|
post_acquisition_time = fc.Parameter('post_acquisition_time', fc.FloatRange(unit='m'), readonly=False, group='sequence_editor', default=500)
|
||||||
@@ -93,10 +91,17 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
num_scans = fc.Parameter('num_scans', fc.IntRange(), readonly=False, group='sequence_editor', default=16)
|
num_scans = fc.Parameter('num_scans', fc.IntRange(), readonly=False, group='sequence_editor', default=16)
|
||||||
obs_freq = fc.Parameter('obs_freq', fc.FloatRange(unit='MHz'), readonly=False, group='sequence_editor', default=213.16)
|
obs_freq = fc.Parameter('obs_freq', fc.FloatRange(unit='MHz'), readonly=False, group='sequence_editor', default=213.16)
|
||||||
|
|
||||||
|
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
|
inited = False
|
||||||
|
|
||||||
### SETUP
|
### SETUP
|
||||||
def tnmr(self):
|
def tnmr(self):
|
||||||
|
'''Creates a new instance or retrieves a previously-made instance of the TNMR API wrapper.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
an instance of the TNMR API wrapper.
|
||||||
|
'''
|
||||||
if not(self.inited):
|
if not(self.inited):
|
||||||
try:
|
try:
|
||||||
self.ntnmr = te.TNMR()
|
self.ntnmr = te.TNMR()
|
||||||
@@ -110,33 +115,14 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
def initialReads(self):
|
def initialReads(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
### COMMANDS
|
|
||||||
#@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="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", argument={'type': 'bool'})
|
@fc.Command(description="Compile & Run", argument={'type': 'bool'})
|
||||||
def compile_and_run(self, thread=True):
|
def compile_and_run(self, thread=True):
|
||||||
|
'''Compiles and runs the currently loaded sequence (in sequence_data), populating this instance's value member with the results.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
thread: bool, determines if a new thread is created and detached for this process. (default true)
|
||||||
|
'''
|
||||||
if(thread):
|
if(thread):
|
||||||
threading.Thread(target=lambda s=self: s.__compile_and_run()).start()
|
threading.Thread(target=lambda s=self: s.__compile_and_run()).start()
|
||||||
else:
|
else:
|
||||||
@@ -144,6 +130,10 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
|
|
||||||
@fc.Command(description="Kill")
|
@fc.Command(description="Kill")
|
||||||
def kill(self):
|
def kill(self):
|
||||||
|
'''Aborts the current scan, if one is running. Else, does nothing'''
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
try:
|
try:
|
||||||
self.tnmr().get_instance().Abort
|
self.tnmr().get_instance().Abort
|
||||||
self.status = ('IDLE', 'ok - killed')
|
self.status = ('IDLE', 'ok - killed')
|
||||||
@@ -157,22 +147,6 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
self.status = ('IDLE', 'ok - uncompiled')
|
self.status = ('IDLE', 'ok - uncompiled')
|
||||||
return self.read_title()
|
return self.read_title()
|
||||||
|
|
||||||
#def write_pulse_width(self, t):
|
|
||||||
# self.pulse_width = t
|
|
||||||
# return self.read_pulse_width()
|
|
||||||
|
|
||||||
#def write_pulse_height(self, t):
|
|
||||||
# self.pulse_height = t
|
|
||||||
# return self.read_pulse_height()
|
|
||||||
|
|
||||||
#def write_relaxation_time(self, t):
|
|
||||||
# self.relaxation_time = t
|
|
||||||
# return self.read_relaxation_time()
|
|
||||||
|
|
||||||
#def write_phase_cycle(self, t):
|
|
||||||
# self.phase_cycle = t
|
|
||||||
# return self.read_phase_cycle()
|
|
||||||
|
|
||||||
def write_acquisition_time(self, t):
|
def write_acquisition_time(self, t):
|
||||||
self.acquisition_time = t
|
self.acquisition_time = t
|
||||||
self.status = ('IDLE', 'ok - uncompiled')
|
self.status = ('IDLE', 'ok - uncompiled')
|
||||||
@@ -218,6 +192,16 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
|
|
||||||
### PRIVATE (Utility)
|
### PRIVATE (Utility)
|
||||||
def __compile_sequence(self):
|
def __compile_sequence(self):
|
||||||
|
'''Compiles the sequence loaded in sequence_data.
|
||||||
|
|
||||||
|
This involves:
|
||||||
|
1. creating the sequence table via seq_gen.get_single_pulse_block calls;
|
||||||
|
2. combining them all;
|
||||||
|
3. saving this sequence where TNMR can see it, and in a format it can read;
|
||||||
|
4. taking a copy of all the acquisition parameters (so that if they are changed mid-acquisition, no incorrect information is written to files)
|
||||||
|
5. telling TNMR to read it (i.e., tnmr().load_sequence()), reloading the dashboard and parameters in the process
|
||||||
|
5. giving TNMR the correct parameters to populate the new dashboard with
|
||||||
|
'''
|
||||||
if(self.status[0] != 'BUSY'):
|
if(self.status[0] != 'BUSY'):
|
||||||
self.status = ('BUSY', 'compiling')
|
self.status = ('BUSY', 'compiling')
|
||||||
# first, create the sequence
|
# first, create the sequence
|
||||||
@@ -226,7 +210,7 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
for s in self.sequence_data:
|
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',
|
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['pulse_height']),
|
||||||
str(s['relaxation_time']) + 'u',
|
str(s['delay_time']) + 'u',
|
||||||
str(s['phase_cycle'])))
|
str(s['phase_cycle'])))
|
||||||
i += 1
|
i += 1
|
||||||
seq = seq_gen.combine_blocks(seq, seq_gen.get_final_block(str(self.ringdown_time) + 'u',
|
seq = seq_gen.combine_blocks(seq, seq_gen.get_final_block(str(self.ringdown_time) + 'u',
|
||||||
@@ -234,7 +218,7 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
str(self.acquisition_time) + 'u',
|
str(self.acquisition_time) + 'u',
|
||||||
str(self.post_acquisition_time) + 'm',
|
str(self.post_acquisition_time) + 'm',
|
||||||
str(self.acq_phase_cycle)))
|
str(self.acq_phase_cycle)))
|
||||||
|
|
||||||
# then, save the thing
|
# then, save the thing
|
||||||
filepath = os.getcwd()
|
filepath = os.getcwd()
|
||||||
filename = self.title + f'_{time.time()}'
|
filename = self.title + f'_{time.time()}'
|
||||||
@@ -246,30 +230,49 @@ class ProgrammedSequence(fc.Readable):
|
|||||||
'Scans 1D': self.read_num_scans(),
|
'Scans 1D': self.read_num_scans(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.compiled_parameters['ringdown_time'] = self.ringdown_time
|
||||||
|
self.compiled_parameters['pre_acquisition_time'] = self.pre_acquisition_time
|
||||||
|
self.compiled_parameters['acquisition_time'] = self.acquisition_time
|
||||||
|
self.compiled_parameters['post_acquisition_time'] = self.post_acquisition_time
|
||||||
|
self.compiled_parameters['acq_phase_cycle'] = self.acq_phase_cycle
|
||||||
|
self.compiled_parameters['num_scans'] = self.read_num_scans()
|
||||||
|
self.compiled_parameters['obs_freq'] = self.read_obs_freq()
|
||||||
|
|
||||||
# then, load the thing into TNMR
|
# then, load the thing into TNMR
|
||||||
self.tnmr().load_sequence(filename)
|
self.tnmr().load_sequence(filename)
|
||||||
|
time.sleep(1.0) # hardware module issue???
|
||||||
|
|
||||||
# load some parameters back to TNMR
|
# load some parameters back to TNMR
|
||||||
for key, val in dashboard_params.items():
|
for key, val in dashboard_params.items():
|
||||||
self.tnmr().set_nmrparameter(key, val)
|
self.tnmr().set_nmrparameter(key, val)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
# finally, let ourselves know we're ready
|
# finally, let ourselves know we're ready
|
||||||
self.status = ('PREPARED', 'compiled')
|
self.status = ('PREPARED', 'compiled')
|
||||||
|
else:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
def __zero_go(self):
|
def __zero_go(self):
|
||||||
|
'''Tells TNMR to acquire data. Only call after __compile_sequence().'''
|
||||||
if(self.status[0] != 'BUSY'):
|
if(self.status[0] != 'BUSY'):
|
||||||
self.status = ('BUSY', 'acquiring')
|
self.status = ('BUSY', 'acquiring')
|
||||||
self.tnmr().ZeroGo(lock=True, interval=0.5)
|
self.tnmr().ZeroGo(lock=True, interval=0.5)
|
||||||
newvals = {}
|
newvals = {}
|
||||||
newvals['reals'] = self.tnmr().get_data()[0]
|
newvals['reals'] = self.tnmr().get_data()[0]
|
||||||
newvals['imags'] = self.tnmr().get_data()[1]
|
newvals['imags'] = self.tnmr().get_data()[1]
|
||||||
newvals['t'] = self.tnmr().get_data_times()
|
newvals['t'] = [ self.compiled_parameters['acquisition_time'] * i/self.compiled_parameters['num_scans'] for i in range(0, self.compiled_parameters['num_scans']) ]
|
||||||
self.value = newvals
|
self.value = newvals
|
||||||
self.status = ('PREPARED', 'compiled')
|
self.status = ('PREPARED', 'compiled')
|
||||||
|
|
||||||
def __compile_and_run(self, thread=True, recurse=True):
|
def __compile_and_run(self, thread=True):
|
||||||
self.tnmr().reset_NTNMR_instance()
|
'''Compiles and runs the currently-loaded sequence
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
thread: bool, determines if this should open a child thread and detach the process
|
||||||
|
'''
|
||||||
self.__compile_sequence()
|
self.__compile_sequence()
|
||||||
|
time.sleep(0.5)
|
||||||
self.__zero_go()
|
self.__zero_go()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
sequence_fileformat
|
||||||
|
______________________
|
||||||
|
|
||||||
|
Version: 1.0
|
||||||
|
Authors: Davis Garrad (Paul Scherrer Institute, CH)
|
||||||
|
______________________
|
||||||
|
|
||||||
|
A basic system to generate Tecmag Sequence (.tps) files for reading by TNMR and sequence generation.
|
||||||
|
"""
|
||||||
|
|
||||||
# HEADER SYNTAX
|
# HEADER SYNTAX
|
||||||
# TODO: See if I can't get my name into this...
|
# TODO: See if I can't get my name into this...
|
||||||
# PSEQ1.001.18 BIN
|
# PSEQ1.001.18 BIN
|
||||||
@@ -87,7 +99,7 @@
|
|||||||
|
|
||||||
Z = '\x00' # for my sanity
|
Z = '\x00' # for my sanity
|
||||||
|
|
||||||
event_codes = {
|
event_codes = { # These hold codes that TNMR uses to identify different events. I'm not sure as to what the black magic is governing them, but I took them from files TNMR generated.
|
||||||
'F1_Ampl': ('\x1f', 'P', 'E', '3', 'R', '\x08'), # F1
|
'F1_Ampl': ('\x1f', 'P', 'E', '3', 'R', '\x08'), # F1
|
||||||
'F1_Ph': ('\x05', 'M', 'E', 'H', 'P', '\x02'),
|
'F1_Ph': ('\x05', 'M', 'E', 'H', 'P', '\x02'),
|
||||||
'F1_PhMod': ('\x17', 'P', 'E', '3', 'P', '\x08'),
|
'F1_PhMod': ('\x17', 'P', 'E', '3', 'P', '\x08'),
|
||||||
@@ -107,7 +119,8 @@ event_codes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event_types = list(event_codes.keys())
|
event_types = list(event_codes.keys())
|
||||||
event_defaults = { 'F1_Ampl': 0, # F1
|
event_defaults = { # default values of each of the events, if nothing else is set.
|
||||||
|
'F1_Ampl': 0, # F1
|
||||||
'F1_PhMod': -1,
|
'F1_PhMod': -1,
|
||||||
'F1_Ph': -1,
|
'F1_Ph': -1,
|
||||||
'F1_UnBlank': 0,
|
'F1_UnBlank': 0,
|
||||||
@@ -132,6 +145,18 @@ def fm(s, spacing=3):
|
|||||||
return a
|
return a
|
||||||
|
|
||||||
def get_info_header(filename, author, col_names, tuning_number, binary_name='PSEQ1.001.18 BIN'):
|
def get_info_header(filename, author, col_names, tuning_number, binary_name='PSEQ1.001.18 BIN'):
|
||||||
|
'''Creates a string which should be written (in binary format) to the top of a TNMR sequence file (.tps)
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
filename: str, the filename of the sequence file which will be written to (doesn't need to exist, just for TNMR to read)
|
||||||
|
author: str, the author of the sequence
|
||||||
|
col_names: list(str), a list of all the column names that will be in use in the sequence
|
||||||
|
tuning_number: str, the number of columns+1, in UTF-8 (i.e., it should be a single character. For 2 columns, tuning_number = '\x02')
|
||||||
|
binary_name: str, a code specifying which version of TNMR Sequence Editor generated this sequence. Best kept its default, PSEQ1.001.18 BIN
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
headerstr = ''
|
headerstr = ''
|
||||||
headerstr += 'PSEQ1.001.18 BIN'
|
headerstr += 'PSEQ1.001.18 BIN'
|
||||||
headerstr += fm(filename)
|
headerstr += fm(filename)
|
||||||
@@ -150,6 +175,13 @@ def get_info_header(filename, author, col_names, tuning_number, binary_name='PSE
|
|||||||
return headerstr
|
return headerstr
|
||||||
|
|
||||||
def get_delay_header(col_delays, tuning_number):
|
def get_delay_header(col_delays, tuning_number):
|
||||||
|
'''Creates a string which should be written (in binary format) after the info header (see get_info_header()) of a TNMR sequence file (.tps). Specifies the delays of each column.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
col_delays: list(float), a list of all the column delays that will be in use in the sequence. Ordered. In microseconds.
|
||||||
|
tuning_number: str, the number of columns+1, in UTF-8 (i.e., it should be a single character. For 2 columns, tuning_number = '\x02')
|
||||||
|
'''
|
||||||
headerstr = ''
|
headerstr = ''
|
||||||
headerstr += f'{tuning_number}{Z*3}\x01{Z*7}\x01{Z*3}\x01'
|
headerstr += f'{tuning_number}{Z*3}\x01{Z*7}\x01{Z*3}\x01'
|
||||||
headerstr += Z*11
|
headerstr += Z*11
|
||||||
@@ -163,15 +195,15 @@ def get_delay_header(col_delays, tuning_number):
|
|||||||
return headerstr
|
return headerstr
|
||||||
|
|
||||||
def get_event_header(event_type, vals, tables, table_reg, tuning_number, col_delays):
|
def get_event_header(event_type, vals, tables, table_reg, tuning_number, col_delays):
|
||||||
'''Generates the file information for the events section.
|
'''Generates the file information for the events section. This should come after the delay header (see get_delay_header())
|
||||||
|
|
||||||
Params
|
Params
|
||||||
------
|
------
|
||||||
event_type: str describing the event
|
event_type: str describing the event (an element of event_types)
|
||||||
vals: an array of strs to be written.
|
vals: an array of strs to be written.
|
||||||
tables: an array of dictionaries of table names to be written in format [ { '1D': None/str } (for col0), { '1D': None/str } (for col1) ... ]
|
tables: an array of dictionaries of table names to be written in format [ { '1D': None/str } (for col0), { '1D': None/str } (for col1) ... ]
|
||||||
table_reg: a dictionary of the table registry
|
table_reg: a dictionary of the table registry
|
||||||
tuning_number: the number of columns, minus 1
|
tuning_number: str, the number of columns+1, in UTF-8 (i.e., it should be a single character. For 2 columns, tuning_number = '\x02')
|
||||||
col_delays: the array of column delays
|
col_delays: the array of column delays
|
||||||
'''
|
'''
|
||||||
codes = event_codes[event_type]
|
codes = event_codes[event_type]
|
||||||
@@ -208,9 +240,6 @@ def get_event_header(event_type, vals, tables, table_reg, tuning_number, col_del
|
|||||||
sweep = f'{freq}Hz'
|
sweep = f'{freq}Hz'
|
||||||
filtr = f'{freq}Hz'
|
filtr = f'{freq}Hz'
|
||||||
|
|
||||||
#sweep = '2500000.0Hz'
|
|
||||||
#filtr = '2500000.0Hz'
|
|
||||||
#dwell = '400.0n' # hard limit apparently...
|
|
||||||
|
|
||||||
headerstr += Z*52
|
headerstr += Z*52
|
||||||
headerstr += f'\x01{Z*3}' + fm(str(acq_points))
|
headerstr += f'\x01{Z*3}' + fm(str(acq_points))
|
||||||
@@ -218,7 +247,7 @@ def get_event_header(event_type, vals, tables, table_reg, tuning_number, col_del
|
|||||||
headerstr += fm(str(filtr))
|
headerstr += fm(str(filtr))
|
||||||
headerstr += fm(str(dwell))
|
headerstr += fm(str(dwell))
|
||||||
headerstr += fm(str(col_delays[i]))
|
headerstr += fm(str(col_delays[i]))
|
||||||
headerstr += f'\x00{Z*5}' # I believe this is to link to dashboard... (set to zero or else it will just go to default)
|
headerstr += f'\x00{Z*5}' # This is to link to dashboard... (set to zero or else it will just go to dashboard default)
|
||||||
else:
|
else:
|
||||||
if not(tables[i] in list(table_reg.keys())):
|
if not(tables[i] in list(table_reg.keys())):
|
||||||
headerstr += Z*56
|
headerstr += Z*56
|
||||||
@@ -229,11 +258,11 @@ def get_event_header(event_type, vals, tables, table_reg, tuning_number, col_del
|
|||||||
return headerstr
|
return headerstr
|
||||||
|
|
||||||
def get_table_spec(tables):
|
def get_table_spec(tables):
|
||||||
'''Generates the file information for a set of tables.
|
'''Generates the file information for a set of tables. This should go after the event section (see get_event_header())
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
tables: a dictionary of form { [table_name]: { 'values': '1 2 3', 'typestr': 'HP', 'start': 1 } }
|
tables: a dictionary of form { [table_name]: { 'values': '1 2 3', 'typestr': 'HP', 'start': 1 } }. typestr should be HP for phase, and you'll have to figure out what the other codes are. Start should almost always be 1.
|
||||||
'''
|
'''
|
||||||
specstr = ''
|
specstr = ''
|
||||||
specstr += Z*56
|
specstr += Z*56
|
||||||
@@ -257,8 +286,8 @@ def generate_default_sequence(col_names, col_delays):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
col_names: an iterable of each of the column titles
|
col_names: an iterable of each of the column titles.
|
||||||
col_delays: an iterable of each of the column delay values.
|
col_delays: an iterable of each of the column delay values. Should match the ordering of col_names
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -274,12 +303,12 @@ def generate_default_sequence(col_names, col_delays):
|
|||||||
return full_dict
|
return full_dict
|
||||||
|
|
||||||
def create_sequence_file(filename, data, author='NA'):
|
def create_sequence_file(filename, data, author='NA'):
|
||||||
'''Generates a Tecmag sequence file for use in Tecmag NMR (TNMR).
|
'''Generates a Tecmag sequence file (.tps) for use in Tecmag NMR (TNMR). Combines header, delay, event, and table information to create a fully-readable file for TNMR.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
filename: str
|
filename: str, where to write this.
|
||||||
data: a dictionary in the form { 'columns': { [column_name_0]: { 'F1_Ampl': [value], ..., 'Rx_Blank': [value], 'Delay': [value] }, ... }, 'tables': { 'table_1': {...}, ... } }. If any sub-entries are empty, they will be given default values (requires that all event_types are present). See event_types and event_defaults.
|
data: a dictionary in the form { 'columns': { [column_name_0]: { 'F1_Ampl': [value], ..., 'Rx_Blank': [value], 'Delay': [value] }, ... }, 'tables': { 'table_1': {...}, ... } }. If any sub-entries are empty, they will be given default values (requires that all event_types are present). See event_types and event_defaults. This is best generated using generate_default_sequence and then modifying the given sequence.
|
||||||
author [optional]: str to describe the file creator.
|
author [optional]: str to describe the file creator.
|
||||||
'''
|
'''
|
||||||
content = ''
|
content = ''
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
SequenceGeneration
|
sequence_generation
|
||||||
______________________
|
______________________
|
||||||
|
|
||||||
Version: 1.0
|
Version: 1.0
|
||||||
Authors: Davis Garrad (Paul Scherrer Institute, CH)
|
Authors: Davis Garrad (Paul Scherrer Institute, CH)
|
||||||
______________________
|
______________________
|
||||||
|
|
||||||
Wrapper for the API I wrote to generate pulse sequences programmatically in TNMR (Tecmag).
|
Wrapper for the API I wrote to generate pulse sequences programmatically in TNMR (Tecmag), with a little more polish than the original driver code.
|
||||||
"""
|
"""
|
||||||
import frappy_psi.tnmr.sequence_fileformat as se
|
import frappy_psi.tnmr.sequence_fileformat as se
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ from pydantic.utils import deep_update
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
def get_single_pulse_block(name, pulse_width, pulse_height, relaxation_time, phase_cycle='0'):
|
def get_single_pulse_block(name, pulse_width, pulse_height, delay_time, phase_cycle='0'):
|
||||||
'''Generates a single block of data to create a sequence with.
|
'''Generates a single block of data to create a sequence with.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -23,26 +23,28 @@ def get_single_pulse_block(name, pulse_width, pulse_height, relaxation_time, pha
|
|||||||
name: str, just the prefix for the column names. Ensure this is unique over the whole sequence!
|
name: str, just the prefix for the column names. Ensure this is unique over the whole sequence!
|
||||||
pulse_width: str, in the format '10u' (10 microseconds)
|
pulse_width: str, in the format '10u' (10 microseconds)
|
||||||
pulse_height: str, in the format '40' (I'm not honestly sure what units this is in...)
|
pulse_height: str, in the format '40' (I'm not honestly sure what units this is in...)
|
||||||
relaxation_time: str, in the format '10u'
|
delay_time: str, in the format '10u'. Minimum value of '0.1u'. If '0', then only the pulse is added (not the delay afterwards). If no unit is given, assumes microseconds.
|
||||||
phase_cycle: a given phase cycle (steps of 4, 0-3 inc.) (eg., '0 0 1 1 2 3 0 1', etc.)
|
phase_cycle: a given phase cycle (steps of 4, 0-3 inc.) (eg., '0 0 1 1 2 3 0 1', etc.) (default '0')
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
a dictionary which can be updated with others to generate a larger, more complex sequence.
|
a dictionary which can be updated with others (with combine_blocks) to generate a larger, more complex sequence.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if(relaxation_time.strip()[-1] == 'u'):
|
if(delay_time.strip()[-1] == 'u'):
|
||||||
relax_time = float(relaxation_time.strip()[:-1])
|
delay_time = float(delay_time.strip()[:-1])
|
||||||
elif(relaxation_time.strip()[-1] == 'n'):
|
elif(delay_time.strip()[-1] == 'n'):
|
||||||
relax_time = float(relaxation_time.strip()[:-1]) * 1000
|
delay_time = float(delay_time.strip()[:-1]) * 1000
|
||||||
if(relaxation_time.strip()[-1] == 'm'):
|
elif(delay_time.strip()[-1] == 'm'):
|
||||||
relax_time = float(relaxation_time.strip()[:-1]) / 1e3
|
delay_time = float(delay_time.strip()[:-1]) / 1e3
|
||||||
if(relaxation_time.strip()[-1] == 's'):
|
elif(delay_time.strip()[-1] == 's'):
|
||||||
relax_time = float(relaxation_time.strip()[:-1]) / 1e6
|
delay_time = float(delay_time.strip()[:-1]) / 1e6
|
||||||
|
else:
|
||||||
|
delay_time = float(delay_time.strip()) # assume in us
|
||||||
|
|
||||||
ph = name + '_phase'
|
ph = name + '_phase'
|
||||||
rl = name + '_relaxation'
|
rl = name + '_delay'
|
||||||
block = se.generate_default_sequence([ ph, rl ] if relax_time > 0 else [ ph ], [ pulse_width, relaxation_time ] if relax_time > 0 else [pulse_width])
|
block = se.generate_default_sequence([ ph, rl ] if delay_time > 0 else [ ph ], [ pulse_width, str(delay_time) + 'u' ] if delay_time > 0 else [pulse_width])
|
||||||
|
|
||||||
# COLUMNNS
|
# COLUMNNS
|
||||||
# PH column
|
# PH column
|
||||||
@@ -51,8 +53,8 @@ def get_single_pulse_block(name, pulse_width, pulse_height, relaxation_time, pha
|
|||||||
block['columns'][ph]['F1_UnBlank']['value'] = '1'
|
block['columns'][ph]['F1_UnBlank']['value'] = '1'
|
||||||
block['columns'][ph]['Rx_Blank']['value'] = '1'
|
block['columns'][ph]['Rx_Blank']['value'] = '1'
|
||||||
|
|
||||||
if(relax_time > 0):
|
if(delay_time > 0):
|
||||||
# relaxation column
|
# delay column
|
||||||
block['columns'][rl]['F1_UnBlank']['value'] = '1'
|
block['columns'][rl]['F1_UnBlank']['value'] = '1'
|
||||||
block['columns'][rl]['Rx_Blank']['value'] = '1'
|
block['columns'][rl]['Rx_Blank']['value'] = '1'
|
||||||
|
|
||||||
@@ -64,6 +66,12 @@ def get_single_pulse_block(name, pulse_width, pulse_height, relaxation_time, pha
|
|||||||
return block
|
return block
|
||||||
|
|
||||||
def get_initial_block():
|
def get_initial_block():
|
||||||
|
'''Generates the mandatory initial block, which consists of phase reset and unblanking
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
a dictionary which can be updated with others (with combine_blocks) to generate a larger, more complex sequence.
|
||||||
|
'''
|
||||||
block = se.generate_default_sequence(['Phase reset', 'Unblank'], ['1u', '10u'])
|
block = se.generate_default_sequence(['Phase reset', 'Unblank'], ['1u', '10u'])
|
||||||
# Phase reset
|
# Phase reset
|
||||||
block['columns']['Phase reset']['F1_PhRst']['value'] = '1'
|
block['columns']['Phase reset']['F1_PhRst']['value'] = '1'
|
||||||
@@ -83,6 +91,7 @@ def get_final_block(ringdown_time, preacquire_time, acquire_time, cooldown_time,
|
|||||||
acquire_time: str, in the format '10u', how long to acquire data for
|
acquire_time: str, in the format '10u', how long to acquire data for
|
||||||
cooldown_time: str, in the format '40u', how long to wait after acquisition.
|
cooldown_time: str, in the format '40u', how long to wait after acquisition.
|
||||||
acq_phase_cycle: str, the phase cycle that the acquisition should follow
|
acq_phase_cycle: str, the phase cycle that the acquisition should follow
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
a dictionary which can be updated with others to generate a larger, more complex sequence.
|
a dictionary which can be updated with others to generate a larger, more complex sequence.
|
||||||
@@ -104,12 +113,15 @@ def get_final_block(ringdown_time, preacquire_time, acquire_time, cooldown_time,
|
|||||||
return block
|
return block
|
||||||
|
|
||||||
def combine_blocks(l, r):
|
def combine_blocks(l, r):
|
||||||
|
'''Combines two dictionaries (just deep copies them, as they each have recursive dictionaries'''
|
||||||
return deep_update(l, r)
|
return deep_update(l, r)
|
||||||
|
|
||||||
def save_sequence(filename, sequence):
|
def save_sequence(filename, sequence):
|
||||||
|
'''Saves the given sequence to a file'''
|
||||||
se.create_sequence_file(filename, sequence)
|
se.create_sequence_file(filename, sequence)
|
||||||
|
|
||||||
def save_sequence_cfg(filename, sequence):
|
def save_sequence_cfg(filename, sequence):
|
||||||
|
'''Saves the sequence to a file in JSON form to be read easier. Appends the extension ".cfg" '''
|
||||||
with open(filename + '.cfg', 'w') as file:
|
with open(filename + '.cfg', 'w') as file:
|
||||||
json.dump(sequence, file, indent=4)
|
json.dump(sequence, file, indent=4)
|
||||||
file.close()
|
file.close()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
TNMR_DG_Extension
|
tnmr_interface
|
||||||
______________________
|
______________________
|
||||||
|
|
||||||
Version: 1.0
|
Version: 1.0
|
||||||
@@ -30,12 +30,17 @@ class TNMR:
|
|||||||
|
|
||||||
Instance Attributes
|
Instance Attributes
|
||||||
-------------------
|
-------------------
|
||||||
NMTNR: win32com object
|
|
||||||
ACTIVEFILE: str
|
ACTIVEFILE: str
|
||||||
path to active TNMR file
|
path to active TNMR file
|
||||||
|
ACTIVEPATH: str
|
||||||
|
path to active TNMR file's parent directory.
|
||||||
|
|
||||||
Methods
|
Methods
|
||||||
-------
|
-------
|
||||||
|
get_instance():
|
||||||
|
Gets an instance of the NTNMR "API". Best to use this rather than keep an object, as threads can mess with the usefulness of an object.
|
||||||
|
execute_cmd(cmd):
|
||||||
|
Sends an arbitrary command to the TNMR software. Best for debugging.
|
||||||
openfile(filepath: str, active: bool):
|
openfile(filepath: str, active: bool):
|
||||||
opens the tnt file specified by filepath
|
opens the tnt file specified by filepath
|
||||||
if active is true, the newly opened file will be set to ACTIVEFILE
|
if active is true, the newly opened file will be set to ACTIVEFILE
|
||||||
@@ -47,8 +52,28 @@ class TNMR:
|
|||||||
the acqusition endswith
|
the acqusition endswith
|
||||||
acquisition_running():
|
acquisition_running():
|
||||||
returns the acquisition status of TNMR
|
returns the acquisition status of TNMR
|
||||||
|
get_data():
|
||||||
|
Pulls the currently loaded data from TNMR and returns it.
|
||||||
|
save_file(filepath=''):
|
||||||
|
Saves the experiment to a file
|
||||||
|
set_nmrparameter(param_name: str, value: str):
|
||||||
|
If it exists, sets an NMR parameter
|
||||||
|
get_nmrparameter(param_name: str):
|
||||||
|
If it exists, returns the value of an NMR parameter
|
||||||
|
is_nmrparameter(param_name: str):
|
||||||
|
Checks if an NMR parameter exists.
|
||||||
|
get_all_nmrparameters():
|
||||||
|
Returns all possible NMR parameters in the dashboard.
|
||||||
|
get_page_parameters(page):
|
||||||
|
Returns all possible NMR parameters on a page of the dashboard.
|
||||||
|
load_sequence(filename):
|
||||||
|
WARNING: POSSIBLY DESTRUCTIVE. ENSURE DATA IS SAVED BEFORE CALLING.
|
||||||
|
Loads a sequence (and reloads the template dashboard) into the TNMR software.
|
||||||
|
load_dashboard(dashboard_fn):
|
||||||
|
Loads a dashboard (resetting parameters) into TNMR.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, filepath = "", NTNMR_inst=None):
|
def __init__(self, filepath = ""):
|
||||||
""" Creates an instance of the NTNMR class, which is used to communicate with TNMR,
|
""" Creates an instance of the NTNMR class, which is used to communicate with TNMR,
|
||||||
Tecmags control software. Basically a wrapper for TNMRs api
|
Tecmags control software. Basically a wrapper for TNMRs api
|
||||||
|
|
||||||
@@ -57,27 +82,18 @@ class TNMR:
|
|||||||
filepath: specifies a path to the file tnt you want to use
|
filepath: specifies a path to the file tnt you want to use
|
||||||
"""
|
"""
|
||||||
#first we check if an instance of TNMR is running an get it or create it
|
#first we check if an instance of TNMR is running an get it or create it
|
||||||
if(NTNMR_inst is None):
|
print('Opening new TNMR connection')
|
||||||
print('Opening new TNMR connection')
|
ntnmr = self.get_instance()
|
||||||
self.reset_NTNMR_instance()
|
|
||||||
else:
|
|
||||||
self.NTNMR = NTNMR_inst
|
|
||||||
# next we open a specified file. If none is specified, then we use the active file
|
# next we open a specified file. If none is specified, then we use the active file
|
||||||
if filepath != "":
|
if filepath != "":
|
||||||
print(f'Loading file {filepath}')
|
print(f'Loading file {filepath}')
|
||||||
self.NTNMR.OpenFile(filepath)
|
ntnmr.OpenFile(filepath)
|
||||||
self.ACTIVEFILE = self.NTNMR.GetActiveDocPath
|
self.ACTIVEFILE = ntnmr.GetActiveDocPath
|
||||||
self.ACTIVEPATH = os.path.dirname(self.ACTIVEFILE)
|
self.ACTIVEPATH = os.path.dirname(self.ACTIVEFILE)
|
||||||
print(f'Active file: {self.ACTIVEFILE} in path {self.ACTIVEPATH}')
|
print(f'Active file: {self.ACTIVEFILE} in path {self.ACTIVEPATH}')
|
||||||
|
|
||||||
def reset_NTNMR_instance(self):
|
|
||||||
try:
|
|
||||||
pythoncom.CoInitialize()
|
|
||||||
self.NTNMR = win32com.client.GetActiveObject("NTNMR.Application")
|
|
||||||
except pythoncom.com_error:
|
|
||||||
raise TNMRNotRunnningError
|
|
||||||
|
|
||||||
def get_instance(self):
|
def get_instance(self):
|
||||||
|
'''Tries to open up a Windows COM connection to the TNMR program, and returns an instance if able'''
|
||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
return win32com.client.GetActiveObject("NTNMR.Application")
|
return win32com.client.GetActiveObject("NTNMR.Application")
|
||||||
@@ -85,9 +101,10 @@ class TNMR:
|
|||||||
raise TNMRNotRunnningError
|
raise TNMRNotRunnningError
|
||||||
|
|
||||||
def execute_cmd(self, cmd):
|
def execute_cmd(self, cmd):
|
||||||
print('W: Executing arbitrary command: ' + f'out = self.NTNMR.{cmd}')
|
'''Sends an arbitrary command through to TNMR'''
|
||||||
|
print('W: Executing arbitrary command: ' + f'out = self.get_instance().{cmd}')
|
||||||
out = 0
|
out = 0
|
||||||
exec(f'out = self.NTNMR.{cmd}\nprint("W: OUTPUT: " + str(out))')
|
exec(f'out = self.get_instance().{cmd}\nprint("W: OUTPUT: " + str(out))')
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def openfile(self, filepath, active = True):
|
def openfile(self, filepath, active = True):
|
||||||
@@ -102,15 +119,16 @@ class TNMR:
|
|||||||
active: bool
|
active: bool
|
||||||
"""
|
"""
|
||||||
print(f'Opening file {filepath}')
|
print(f'Opening file {filepath}')
|
||||||
self.NTNMR.OpenFile(filepath)
|
ntnmr = self.get_instance()
|
||||||
|
ntnmr.OpenFile(filepath)
|
||||||
if active:
|
if active:
|
||||||
self.ACTIVEFILE = self.NTNMR.GetActiveDocPath
|
self.ACTIVEFILE = ntnmr.GetActiveDocPath
|
||||||
print(f'Active file: {self.ACTIVEFILE} in path {self.ACTIVEPATH}')
|
print(f'Active file: {self.ACTIVEFILE} in path {self.ACTIVEPATH}')
|
||||||
|
|
||||||
def set_activefile(self):
|
def set_activefile(self):
|
||||||
""" Sets TNMR active doc path to ACTIVEFILE
|
""" Sets TNMR active doc path to ACTIVEFILE
|
||||||
"""
|
"""
|
||||||
self.ACTIVEFILE = self.NTNMR.GetActiveDocPath
|
self.ACTIVEFILE = self.get_instance().GetActiveDocPath
|
||||||
self.ACTIVEPATH = os.path.dirname(self.ACTIVEFILE)
|
self.ACTIVEPATH = os.path.dirname(self.ACTIVEFILE)
|
||||||
print(f'Active file: {self.ACTIVEFILE} in path {self.ACTIVEPATH}')
|
print(f'Active file: {self.ACTIVEFILE} in path {self.ACTIVEPATH}')
|
||||||
|
|
||||||
@@ -129,6 +147,7 @@ class TNMR:
|
|||||||
print('Zero-going...')
|
print('Zero-going...')
|
||||||
ntnmr = self.get_instance()
|
ntnmr = self.get_instance()
|
||||||
if not(self.acquisition_running()):
|
if not(self.acquisition_running()):
|
||||||
|
ntnmr.Reset # to avoid hardware issues?
|
||||||
ntnmr.ZG
|
ntnmr.ZG
|
||||||
else:
|
else:
|
||||||
print('An Acquisition is already running')
|
print('An Acquisition is already running')
|
||||||
@@ -137,6 +156,7 @@ class TNMR:
|
|||||||
print("Application locked during acquisition\n...waiting...")
|
print("Application locked during acquisition\n...waiting...")
|
||||||
while self.acquisition_running():
|
while self.acquisition_running():
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
|
# TODO: https://stackoverflow.com/questions/27586411/how-do-i-close-window-with-handle-using-win32gui-in-python to close any tecmag dialogues that show up. Need to determine proper search string, so next time it pops up, run some tests.
|
||||||
print("Acquisition done")
|
print("Acquisition done")
|
||||||
|
|
||||||
def acquisition_running(self):
|
def acquisition_running(self):
|
||||||
@@ -147,34 +167,22 @@ class TNMR:
|
|||||||
True: if running
|
True: if running
|
||||||
False: if not running
|
False: if not running
|
||||||
"""
|
"""
|
||||||
#try:
|
|
||||||
ntnmr = self.get_instance()
|
ntnmr = self.get_instance()
|
||||||
res = not(ntnmr.CheckAcquisition)
|
res = not(ntnmr.CheckAcquisition)
|
||||||
#except AttributeError as e:
|
|
||||||
# if(e
|
|
||||||
# res = False
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
|
'''Pulls data from TNMR
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
a tuple of ([real_array], [imaginary_array])
|
||||||
|
'''
|
||||||
raw_data = self.get_instance().GetData
|
raw_data = self.get_instance().GetData
|
||||||
reals = raw_data[::2]
|
reals = raw_data[::2]
|
||||||
imags = raw_data[1::2]
|
imags = raw_data[1::2]
|
||||||
|
|
||||||
return (reals, imags)
|
return (reals, imags)
|
||||||
|
|
||||||
def get_data_times(self):
|
|
||||||
#acq_n = int(self.NTNMR.GetNMRParameter('Acq. Points')) # TODO: These do NOT return the actual used values!
|
|
||||||
#acq_t = self.NTNMR.GetNMRParameter('Acq. Time')
|
|
||||||
#acq_t = acq_t.strip()
|
|
||||||
#if(acq_t[-1] == 'm'):
|
|
||||||
# acq_t = float(acq_t[:-1]) * 1000
|
|
||||||
#elif(acq_t[-1] == 'u'):
|
|
||||||
# acq_t = float(acq_t[:-1])
|
|
||||||
#elif(acq_t[-1] == 'n'):
|
|
||||||
# acq_t = float(acq_t[:-1]) / 1000
|
|
||||||
acq_t = 204.8 # us
|
|
||||||
acq_n = 1024
|
|
||||||
return [ i * (acq_t / (acq_n - 1)) for i in range(0, acq_n+1) ]
|
|
||||||
|
|
||||||
def save_file(self, filepath=''):
|
def save_file(self, filepath=''):
|
||||||
""" Save file to filepath. if no filepath specified, save current active file
|
""" Save file to filepath. if no filepath specified, save current active file
|
||||||
@@ -244,9 +252,6 @@ class TNMR:
|
|||||||
try:
|
try:
|
||||||
self.get_instance().GetNMRParameter(param_name)
|
self.get_instance().GetNMRParameter(param_name)
|
||||||
return True
|
return True
|
||||||
except AttributeError:
|
|
||||||
self.reset_NTNMR_instance()
|
|
||||||
return self.is_nmrparameter(param_name)
|
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -341,6 +346,16 @@ class TNMR:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def load_dashboard(self, dashboard_fn):
|
def load_dashboard(self, dashboard_fn):
|
||||||
|
'''Loads a dashboard into TNMR. Resets the parameters to those in the dashboard file, despite what the TNMR documentation says.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
filename: str, designates the dashboard to load
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
A success flag - beware; sometimes, this can fail and still provide a false positive. False negatives, as far as I'm aware, never happen, though, so feel free to rely on that.
|
||||||
|
'''
|
||||||
print(f'I: Loading dashboard setup from {dashboard_fn}')
|
print(f'I: Loading dashboard setup from {dashboard_fn}')
|
||||||
|
|
||||||
success = self.get_instance().LoadParameterSetupFromFile(dashboard_fn)
|
success = self.get_instance().LoadParameterSetupFromFile(dashboard_fn)
|
||||||
|
|||||||
Reference in New Issue
Block a user