Source code for BGlib.be.translators.be_odf_relaxation

# -*- coding: utf-8 -*-
"""
Created on Thursday May 26 11:23:00 2016

@author:  Rama Vasudevan, Suhas Somnath, Chris Smith
"""

from __future__ import division, print_function, absolute_import, unicode_literals

from os import path, remove  # File Path formatting
from warnings import warn
import numpy as np  # For array operations
from scipy.io.matlab import loadmat  # To load parameters stored in Matlab .mat file
import h5py
from sidpy.sid import Translator
from sidpy.hdf.hdf_utils import write_simple_attrs
from pyUSID.io.anc_build_utils import INDICES_DTYPE, Dimension
from pyUSID.io.hdf_utils import create_indexed_group, write_main_dataset

from .df_utils.be_utils import trimUDVS, getSpectroscopicParmLabel, \
    generatePlotGroups, createSpecVals, maxReadPixels, nf32


[docs] class BEodfRelaxationTranslator(Translator): """ Translates old Relaxation data into the new H5 format. This is for the files generated from the old BEPSDAQ program utilizing two cards simultaneously. At present, this version of the translator only works for Out of field measurements It will not work for in-field. This should be fixed at a later date. """ def __init__(self, max_mem_mb=1024): super(BEodfRelaxationTranslator, self).__init__(max_mem_mb) self.FFT_BE_wave = None self.h5_file = None self.ds_main = None self.mean_resp = None self.max_resp = None self.min_resp = None
[docs] def translate(self, file_path, show_plots=True, save_plots=True, do_histogram=False): """ Basic method that translates .dat data file(s) to a single .h5 file Inputs: file_path -- Absolute file path for one of the data files. It is assumed that this file is of the OLD data format. Outputs: Nothing """ file_path = path.abspath(file_path) (folder_path, basename) = path.split(file_path) (basename, path_dict) = self._parse_file_path(file_path) h5_path = path.join(folder_path, basename + '.h5') if path.exists(h5_path): remove(h5_path) self.h5_file = h5py.File(h5_path, 'w') isBEPS = True parm_dict = self.__getParmsFromOldMat(path_dict['old_mat_parms']) ignored_plt_grps = ['in-field'] # Here we assume that there is no in-field. # If in-field data is captured then the translator would have to be modified. # Technically, we could do away with this if statement, as isBEPS is always true for this translation if isBEPS: parm_dict['data_type'] = 'BEPSData' std_expt = parm_dict['VS_mode'] != 'load user defined VS Wave from file' if not std_expt: warn('This translator does not handle user defined voltage spectroscopy') return spec_label = getSpectroscopicParmLabel(parm_dict['VS_mode']) # Check file sizes: if 'read_real' in path_dict.keys(): real_size = path.getsize(path_dict['read_real']) imag_size = path.getsize(path_dict['read_imag']) else: real_size = path.getsize(path_dict['write_real']) imag_size = path.getsize(path_dict['write_imag']) if real_size != imag_size: raise ValueError("Real and imaginary file sizes DON'T match!. Ending") num_rows = int(parm_dict['grid_num_rows']) num_cols = int(parm_dict['grid_num_cols']) num_pix = num_rows * num_cols tot_bins = real_size / (num_pix * 4) # Finding bins by simple division of entire datasize # Check for case where only a single pixel is missing. check_bins = real_size / ((num_pix - 1) * 4) if tot_bins % 1 and check_bins % 1: warn('Aborting! Some parameter appears to have changed in-between') return elif not tot_bins % 1: # Everything's ok pass elif not check_bins % 1: tot_bins = check_bins warn('Warning: A pixel seems to be missing from the data. File will be padded with zeros.') tot_bins = int(tot_bins) (bin_inds, bin_freqs, bin_FFT, ex_wfm, dc_amp_vec) = self.__readOldMatBEvecs(path_dict['old_mat_parms']) """ Because this is the old data format and there is a discrepancy in the number of bins (they seem to be 2 less than the actual number), we need to re-calculate it based on the available data. This is done below. """ band_width = parm_dict['BE_band_width_[Hz]'] * (0.5 - parm_dict['BE_band_edge_trim']) st_f = parm_dict['BE_center_frequency_[Hz]'] - band_width en_f = parm_dict['BE_center_frequency_[Hz]'] + band_width bin_freqs = np.linspace(st_f, en_f, len(bin_inds), dtype=np.float32) # Forcing standardized datatypes: bin_inds = np.int32(bin_inds) bin_freqs = np.float32(bin_freqs) bin_FFT = np.complex64(bin_FFT) ex_wfm = np.float32(ex_wfm) self.FFT_BE_wave = bin_FFT (UDVS_labs, UDVS_units, UDVS_mat) = self.__buildUDVSTable(parm_dict) # Remove the unused plot group columns before proceeding: (UDVS_mat, UDVS_labs, UDVS_units) = trimUDVS(UDVS_mat, UDVS_labs, UDVS_units, ignored_plt_grps) spec_inds = np.zeros(shape=(2, tot_bins), dtype=INDICES_DTYPE) # Will assume that all excitation waveforms have same number of bins # Here, the denominator is 2 because only out of field measruements. For IF + OF, should be 1 num_actual_udvs_steps = UDVS_mat.shape[0] / 2 bins_per_step = tot_bins / num_actual_udvs_steps # Some more checks if bins_per_step % 1: warn('Non integer number of bins per step!') return else: bins_per_step = int(bins_per_step) num_actual_udvs_steps = int(num_actual_udvs_steps) stind = 0 for step_index in range(UDVS_mat.shape[0]): if UDVS_mat[step_index, 2] < 1E-3: # invalid AC amplitude continue # skip spec_inds[0, stind:stind + bins_per_step] = np.arange(bins_per_step, dtype=INDICES_DTYPE) # Bin step spec_inds[1, stind:stind + bins_per_step] = step_index * np.ones(bins_per_step, dtype=INDICES_DTYPE) # UDVS step stind += bins_per_step del stind, step_index # Some very basic information that can help the processing / analysis crew parm_dict['num_bins'] = tot_bins parm_dict['num_pix'] = num_pix parm_dict['num_udvs_steps'] = num_actual_udvs_steps global_parms = dict() global_parms['grid_size_x'] = parm_dict['grid_num_cols'] global_parms['grid_size_y'] = parm_dict['grid_num_rows'] global_parms['experiment_date'] = parm_dict['File_date_and_time'] # assuming that the experiment was completed: global_parms['current_position_x'] = parm_dict['grid_num_cols'] - 1 global_parms['current_position_y'] = parm_dict['grid_num_rows'] - 1 global_parms['data_type'] = parm_dict['data_type'] # self.__class__.__name__ global_parms['translator'] = 'ODF' write_simple_attrs(self.h5_file, global_parms) # Create Measurement and Channel groups meas_grp = create_indexed_group(self.h5_file, 'Measurement') write_simple_attrs(meas_grp, parm_dict) chan_grp = create_indexed_group(meas_grp, 'Channel') chan_grp.attrs['Channel_Input'] = parm_dict['IO_Analog_Input_1'] # Create Auxilliary Datasets h5_ex_wfm = chan_grp.create_dataset('Excitation_Waveform', data=ex_wfm) udvs_slices = dict() for col_ind, col_name in enumerate(UDVS_labs): udvs_slices[col_name] = (slice(None), slice(col_ind, col_ind + 1)) h5_UDVS = chan_grp.create_dataset('UDVS', data=UDVS_mat, dtype=np.float32) write_simple_attrs(h5_UDVS, {'labels': UDVS_labs, 'units': UDVS_units}) h5_bin_steps = chan_grp.create_dataset('Bin_Steps', data=np.arange(bins_per_step, dtype=np.uint32), dtype=np.uint32) # Need to add the Bin Waveform type - infer from UDVS exec_bin_vec = self.signal_type * np.ones(len(bin_inds), dtype=np.int32) h5_wfm_typ = chan_grp.create_dataset('Bin_Wfm_Type', data=exec_bin_vec, dtype=np.int32) h5_bin_inds = chan_grp.create_dataset('Bin_Indices', data=bin_inds, dtype=np.uint32) h5_bin_freq = chan_grp.create_dataset('Bin_Frequencies', data=bin_freqs, dtype=np.float32) h5_bin_FFT = chan_grp.create_dataset('Bin_FFT', data=bin_FFT, dtype=np.complex64) # Noise floor should be of shape: (udvs_steps x 3 x positions) h5_noise_floor = chan_grp.create_dataset('Noise_Floor', shape=(num_pix, num_actual_udvs_steps), dtype=nf32, chunks=(1, num_actual_udvs_steps)) """ ONLY ALLOCATING SPACE FOR MAIN DATA HERE! Chunk by each UDVS step - this makes it easy / quick to: 1. read data for a single UDVS step from all pixels 2. read an entire / multiple pixels at a time The only problem is that a typical UDVS step containing 50 steps occupies only 400 bytes. This is smaller than the recommended chunk sizes of 10,000 - 999,999 bytes meaning that the metadata would be very substantial. This assumption is fine since we almost do not handle any user defined cases """ """ New Method for chunking the Main_Data dataset. Chunking is now done in N-by-N squares of UDVS steps by pixels. N is determined dinamically based on the dimensions of the dataset. Currently it is set such that individual chunks are less than 10kB in size. Chris Smith -- csmith55@utk.edu """ pos_dims = [Dimension('X', 'nm', num_cols), Dimension('Y', 'nm', num_rows)] # Create Spectroscopic Values and Spectroscopic Values Labels datasets spec_vals, spec_inds, spec_vals_labs, spec_vals_units, spec_vals_names = createSpecVals(UDVS_mat, spec_inds, bin_freqs, exec_bin_vec, parm_dict, UDVS_labs, UDVS_units) spec_dims = list() for row_ind, row_name in enumerate(spec_vals_labs): spec_dims.append(Dimension(row_name, spec_vals_units[row_ind], spec_vals[row_ind])) pixel_chunking = maxReadPixels(10240, num_pix * num_actual_udvs_steps, bins_per_step, np.dtype('complex64').itemsize) chunking = np.floor(np.sqrt(pixel_chunking)) chunking = max(1, chunking) chunking = min(num_actual_udvs_steps, num_pix, chunking) self.h5_main = write_main_dataset(chan_grp, (num_pix, tot_bins), 'Raw_Data', 'Piezoresponse', 'V', pos_dims, spec_dims, dtype=np.complex64, chunks=(chunking, chunking * bins_per_step), compression='gzip') self.mean_resp = np.zeros(shape=(self.ds_main.shape[1]), dtype=np.complex64) self.max_resp = np.zeros(shape=(self.ds_main.shape[0]), dtype=np.float32) self.min_resp = np.zeros(shape=(self.ds_main.shape[0]), dtype=np.float32) # Now read the raw data files: self._read_data(path_dict['read_real'], path_dict['read_imag'], parm_dict) self.h5_file.flush() generatePlotGroups(self.ds_main, self.mean_resp, folder_path, basename, self.max_resp, self.min_resp, max_mem_mb=self.max_ram, spec_label=spec_label, show_plots=show_plots, save_plots=save_plots, do_histogram=do_histogram) self.h5_file.close() return h5_path
def _read_data(self, real_path, imag_path, parm_dict): """ Reads the imaginary and real data files one pixel at a time andwrites to the H5 dataset. Inputs: real_path -- file path of the .dat file containing the real component of the data imag_path -- file path of the .dat file containing the imaginary component of the data parm_dict--dictionary of parameters for the experiment Outputs: None """ print('---- reading data one pixel at a time----------') num_pix = int(parm_dict['grid_num_rows']) * int(parm_dict['grid_num_cols']) # print 'Number of rows is: ', parm_dict['grid_num_rows'] # print 'Number of cols is: ', parm_dict['grid_num_cols'] # print 'Rows * cols is:', int(parm_dict['grid_num_rows'])*int(parm_dict['grid_num_cols']) if path.getsize(real_path) != path.getsize(imag_path): print('Sizes of real and imaginary files NOT matching!!!!') if 1.0 * path.getsize(real_path) % num_pix != 0: print('Incomplete dataset!!!') bytes_per_pix = path.getsize(real_path) / num_pix f_real = open(real_path, "rb") f_imag = open(imag_path, "rb") for pix_ind in range(num_pix): print('Reading pixel #{}, file position {}'.format(pix_ind, hex(pix_ind * bytes_per_pix))) pix_vec = np.fromstring(f_real.read(int(bytes_per_pix)), dtype='f') + \ 1j * np.fromstring(f_imag.read(int(bytes_per_pix)), dtype='f') # Make chronologically correct pix_mat = np.reshape(pix_vec, (parm_dict['BE_bins_per_read'], parm_dict['VS_steps_per_full_cycle'], parm_dict['BE_repeats'])) pix_mat_temp = np.transpose(pix_mat, (1, 2, 0)) pix_vec2 = np.reshape(pix_mat_temp, -1) # Calculate the mean, min, max self.max_resp[pix_ind] = np.max(np.abs(pix_vec2)) self.min_resp[pix_ind] = np.min(np.abs(pix_vec2)) self.mean_resp = (1 / (pix_ind + 1)) * (pix_vec2 + pix_ind * self.mean_resp) # Write to file now self.ds_main[pix_ind, :] = np.complex64(pix_vec2) self.h5_file.flush() f_real.close() f_imag.close() print('Finished writing data to .h5') @staticmethod def __readOldMatBEvecs(file_path): """ Returns information about the excitation BE waveform present in the .mat file Inputs: filepath -- Absolute filepath of the .mat parameter file Outputs: Tuple -- (bin_inds, bin_w, bin_FFT, BE_wave, dc_amp_vec_full)\n bin_inds -- Bin indices\n bin_w -- Excitation bin Frequencies\n bin_FFT -- FFT of the BE waveform for the excited bins\n BE_wave -- Band Excitation waveform\n dc_amp_vec_full -- spectroscopic waveform. This information will be necessary for fixing the UDVS for AC modulation for example """ matread = loadmat(file_path, squeeze_me=True) BE_wave = matread['BE_wave_1'] bin_inds = matread['bin_ind_s'] - 1 # Python base 0. note also _s, for this case bin_w = matread['bin_w'] dc_amp_vec_full = matread['dc_amp_vec_full'] FFT_full = np.fft.fftshift(np.fft.fft(BE_wave)) bin_FFT = np.conjugate(FFT_full[bin_inds]) return bin_inds, bin_w, bin_FFT, BE_wave, dc_amp_vec_full def _parse_file_path(self, data_filepath): """ Returns the basename and a dictionary containing the absolute file paths for the real and imaginary data files, text and mat parameter files in a dictionary Parameters ---------- data_filepath : str Absolute path of the real / imaginary data file (.dat) Returns ------- basename : str path_dict : dict """ (folder_path, basename) = path.split(data_filepath) (super_folder, basename) = path.split(folder_path) if basename.endswith('_c'): # Old old data format where the folder ended with a _d for some reason base_name = basename[:-2] """ A single pair of real and imaginary files are / were generated for: BE-Line and BEPS (compiled version only generated out-of-field or 'read') Two pairs of real and imaginary files were generated for later BEPS datasets These have 'read' and 'write' prefixes to denote out or in field respectively """ path_dict = dict() real_path = path.join(folder_path, base_name + '_sub_real.dat') imag_path = path.join(folder_path, base_name + '_sub_imag.dat') path_dict['read_real'] = real_path path_dict['read_imag'] = imag_path path_dict['old_mat_parms'] = data_filepath return basename, path_dict @staticmethod def __getParmsFromOldMat(file_path): """ Formats parameters found in the old parameters .mat file into a dictionary as though the dataset had a parms.txt describing it Inputs: file_path -- absolute filepath of the .mat file containing the parameters Outputs -- dictionary containing parameters """ parm_dict = dict() matread = loadmat(file_path, squeeze_me=True) parm_dict['IO_rate'] = str(int(matread['AO_rate'] / 1E+6)) + ' MHz' position_vec = matread['position_vec'] parm_dict['grid_current_row'] = position_vec[0] parm_dict['grid_current_col'] = position_vec[1] parm_dict['grid_num_rows'] = int(position_vec[2]) parm_dict['grid_num_cols'] = int(position_vec[3]) if position_vec[0] != position_vec[1] or position_vec[2] != position_vec[3]: warn('WARNING: Incomplete dataset. Translation not guaranteed!') parm_dict['grid_num_rows'] = int(position_vec[0]) # set to number of present cols and rows parm_dict['grid_num_cols'] = int(position_vec[1]) BE_parm_vec_1 = matread['BE_parm_vec_1'] # Not required for translation but necessary to have if BE_parm_vec_1[0] == 3: parm_dict['BE_phase_content'] = 'chirp-sinc hybrid' else: parm_dict['BE_phase_content'] = 'Unknown' parm_dict['BE_center_frequency_[Hz]'] = BE_parm_vec_1[1] parm_dict['BE_band_width_[Hz]'] = BE_parm_vec_1[2] parm_dict['BE_amplitude_[V]'] = BE_parm_vec_1[3] parm_dict['BE_band_edge_trim'] = -1 * BE_parm_vec_1[6] # 150 most likely parm_dict['BE_phase_variation'] = BE_parm_vec_1[5] # 0.01 most likely parm_dict['BE_repeats'] = 2 ** int(BE_parm_vec_1[8]) parm_dict['File_date_and_time'] = 0 # For now ignoring. parm_dict['BE_bins_per_read'] = matread['bins_per_band_s'] assembly_parm_vec = matread['assembly_parm_vec'] if assembly_parm_vec[2] == 0: parm_dict['VS_measure_in_field_loops'] = 'out-of-field' elif assembly_parm_vec[2] == 1: parm_dict['VS_measure_in_field_loops'] = 'in and out-of-field' else: parm_dict['VS_measure_in_field_loops'] = 'in-field' parm_dict['IO_Analog_Input_1'] = '+/- 10V, FFT' if assembly_parm_vec[3] == 0: parm_dict['IO_Analog_Input_2'] = 'off' else: parm_dict['IO_Analog_Input_2'] = '+/- 10V, FFT' # num_driving_bands = assembly_parm_vec[0] # 0 = 1, 1 = 2 bands # band_combination_order = assembly_parm_vec[1] # 0 parallel 1 series VS_parms = matread['SS_parm_vec'] dc_amp_vec_full = matread['dc_amp_vec_full'] VS_start_V = VS_parms[4] VS_start_loop_amp = VS_parms[5] VS_final_loop_amp = VS_parms[6] # VS_read_write_ratio = VS_parms[8] #1 <- SS_read_write_ratio parm_dict['VS_set_pulse_amplitude_[V]'] = VS_parms[9] # 0 <- SS_set_pulse_amp parm_dict['VS_read_voltage_[V]'] = VS_parms[3] parm_dict['VS_steps_per_full_cycle'] = len(dc_amp_vec_full) # VS_parms[7] parm_dict['VS_cycle_fraction'] = 'full' parm_dict['VS_cycle_phase_shift'] = 0 parm_dict['VS_number_of_cycles'] = VS_parms[2] parm_dict['FORC_num_of_FORC_cycles'] = 1 parm_dict['FORC_V_high1_[V]'] = 0 parm_dict['FORC_V_high2_[V]'] = 0 parm_dict['FORC_V_low1_[V]'] = 0 parm_dict['FORC_V_low2_[V]'] = 0 if VS_parms[0] == 0: parm_dict['VS_mode'] = 'DC modulation mode' parm_dict['VS_amplitude_[V]'] = 0.5 * (max(dc_amp_vec_full) - min(dc_amp_vec_full)) # SS_max_offset_amplitude parm_dict['VS_offset_[V]'] = max(dc_amp_vec_full) + min(dc_amp_vec_full) elif VS_parms[0] == 1: # FORC parm_dict['VS_mode'] = 'DC modulation mode' parm_dict['VS_amplitude_[V]'] = 1 # VS_parms[1] # SS_max_offset_amplitude parm_dict['VS_offset_[V]'] = 0 parm_dict['VS_number_of_cycles'] = 1 parm_dict['FORC_num_of_FORC_cycles'] = VS_parms[2] parm_dict['FORC_V_high1_[V]'] = VS_start_V parm_dict['FORC_V_high2_[V]'] = VS_start_V parm_dict['FORC_V_low1_[V]'] = VS_start_V - VS_start_loop_amp parm_dict['FORC_V_low2_[V]'] = VS_start_V - VS_final_loop_amp elif VS_parms[0] == 2: # AC mode parm_dict['VS_mode'] = 'AC modulation mode with time reversal' parm_dict['VS_amplitude_[V]'] = 0.5 * VS_final_loop_amp parm_dict[ 'VS_offset_[V]'] = 0 # this is not correct. Fix manually when it comes to UDVS generation? else: parm_dict['VS_mode'] = 'Custom' return parm_dict def __buildUDVSTable(self, parm_dict): """ Generates the UDVS table using the parameters Inputs: parm_dict -- Dictionary of parameters present in the text files Outputs: tuple (labels, table) labels -- List of strings - Labels for columns in the UDVS table table -- UDVS data table """ def translateVal(target, strvals, numvals): """ Internal function - Interprets the provided value using the provided lookup table """ if len(strvals) != len(numvals): return None for strval, fltval in zip(strvals, numvals): if target == strval: return fltval return None # not found in list # Extract values from parm text file BE_signal_type = 1 # This is necessary when normalzing the AI by the AO self.harmonic = BE_signal_type self.signal_type = BE_signal_type BE_amp = parm_dict['BE_amplitude_[V]'] VS_amp = parm_dict['VS_amplitude_[V]'] VS_offset = parm_dict['VS_offset_[V]'] VS_steps = parm_dict['VS_steps_per_full_cycle'] VS_cycles = parm_dict['VS_number_of_cycles'] VS_fraction = translateVal(parm_dict['VS_cycle_fraction'], ['full', '1/2', '1/4', '3/4'], [1., 0.5, 0.25, 0.75]) VS_shift = parm_dict['VS_cycle_phase_shift'] if VS_shift != 0: VS_shift = translateVal(VS_shift, ['1/4', '1/2', '3/4'], [0.25, 0.5, 0.75]) VS_in_out_cond = translateVal(parm_dict['VS_measure_in_field_loops'], ['out-of-field', 'in-field', 'in and out-of-field'], [0, 1, 2]) VS_ACDC_cond = translateVal(parm_dict['VS_mode'], ['DC modulation mode', 'AC modulation mode with time reversal', 'load user defined VS Wave from file', 'current mode'], [0, 2, 3, 4]) self.expt_type = VS_ACDC_cond FORC_cycles = parm_dict['FORC_num_of_FORC_cycles'] FORC_A1 = parm_dict['FORC_V_high1_[V]'] FORC_A2 = parm_dict['FORC_V_high2_[V]'] FORC_B1 = parm_dict['FORC_V_low1_[V]'] FORC_B2 = parm_dict['FORC_V_low2_[V]'] # % build vector of voltage spectroscopy values if VS_ACDC_cond == 0 or VS_ACDC_cond == 4: # DC voltage spectroscopy or current mode VS_amp_vec_1 = np.arange(0, 1 + 1 / (VS_steps / 4), 1 / (VS_steps / 4)) VS_amp_vec_2 = np.flipud(VS_amp_vec_1[:-1]) VS_amp_vec_3 = -VS_amp_vec_1[1:] VS_amp_vec_4 = VS_amp_vec_1[1:-1] - 1 VS_amp_vec = VS_amp * (np.hstack((VS_amp_vec_1, VS_amp_vec_2, VS_amp_vec_3, VS_amp_vec_4))) VS_amp_vec = np.roll(VS_amp_vec, int(np.floor(VS_steps / VS_fraction * VS_shift))) # apply phase shift to VS wave VS_amp_vec = VS_amp_vec[:int(np.floor(VS_steps * VS_fraction))] # cut VS waveform VS_amp_vec = np.tile(VS_amp_vec, VS_cycles) # repeat VS waveform VS_amp_vec = VS_amp_vec + VS_offset if FORC_cycles > 1: VS_amp_vec = VS_amp_vec / np.max(np.abs(VS_amp_vec)) FORC_cycle_vec = np.arange(0, FORC_cycles + 1, FORC_cycles / (FORC_cycles - 1)) FORC_A_vec = FORC_cycle_vec * (FORC_A2 - FORC_A1) / FORC_cycles + FORC_A1 FORC_B_vec = FORC_cycle_vec * (FORC_B2 - FORC_B1) / FORC_cycles + FORC_B1 FORC_amp_vec = (FORC_A_vec - FORC_B_vec) / 2 FORC_off_vec = (FORC_A_vec + FORC_B_vec) / 2 VS_amp_mat = np.tile(VS_amp_vec, [FORC_cycles, 1]) FORC_amp_mat = np.tile(FORC_amp_vec, [len(VS_amp_vec), 1]).transpose() FORC_off_mat = np.tile(FORC_off_vec, [len(VS_amp_vec), 1]).transpose() VS_amp_mat = VS_amp_mat * FORC_amp_mat + FORC_off_mat VS_amp_vec = VS_amp_mat.reshape(int(FORC_cycles * VS_cycles * VS_fraction * VS_steps)) BE_repeats = parm_dict['BE_repeats'] total_steps = len(VS_amp_vec) * BE_repeats # Needed for relaxation datasets # % Build UDVS table: if VS_ACDC_cond == 0 or VS_ACDC_cond == 4: # relaxation measurements num_VS_steps = total_steps * 2 # To account for IF and OOF UD_VS_table_label = ['step_num', 'dc_offset', 'ac_amp', 'wave_type', 'wave_mod', 'in-field', 'out-of-field'] UD_VS_table = np.zeros(shape=(num_VS_steps, 7), dtype=np.float32) UD_VS_table_unit = ['', 'V', 'A', '', '', 'V', 'V'] UD_VS_table[:, 0] = np.arange(0, num_VS_steps) # Python base 0 for step_num in np.arange(0, VS_steps): step_values = (np.arange(int(step_num) * int(BE_repeats) * 2, (int(step_num) + 1) * int(BE_repeats) * 2)) UD_VS_table[step_values, 1] = VS_amp_vec[step_num] BE_IF_switch = np.abs(np.imag(np.exp(1j * np.pi / 2 * np.arange(1, num_VS_steps + 1)))) BE_OF_switch = np.abs(np.real(np.exp(1j * np.pi / 2 * np.arange(1, num_VS_steps + 1)))) if VS_in_out_cond == 0: # out of field only UD_VS_table[:, 2] = BE_amp * BE_OF_switch elif VS_in_out_cond == 1: # in field only UD_VS_table[:, 2] = BE_amp * BE_IF_switch elif VS_in_out_cond == 2: # both in and out of field UD_VS_table[:, 2] = BE_amp * np.ones(num_VS_steps) UD_VS_table[:, 3] = np.ones(num_VS_steps) # wave type UD_VS_table[:, 4] = np.ones(num_VS_steps) * BE_signal_type # wave mod UD_VS_table[:, 5] = float('NaN') * np.ones(num_VS_steps) UD_VS_table[:, 6] = float('NaN') * np.ones(num_VS_steps) UD_VS_table[BE_IF_switch == 1, 5] = UD_VS_table[BE_IF_switch == 1, 1] UD_VS_table[BE_OF_switch == 1, 6] = UD_VS_table[BE_IF_switch == 1, 1] return UD_VS_table_label, UD_VS_table_unit, UD_VS_table