Source code for SciFiReaders.readers.microscopy.spm.afm.igor_ibw

# -*- coding: utf-8 -*-
"""
Created on Wed Dec 07 16:04:34 2016

Updated on Fri Sep 11 15:22:00 2020 for ScopeReader

@author: Suhas Somnath, Chris R. Smith, Raj Giridhargopal, Rama Vasudevan
"""

import sys
import numpy as np  # For array operations
import sidpy as sid
from sidpy.sid import Reader

try:
    from igor import binarywave as bw
except ModuleNotFoundError:
    bw = None


[docs]class IgorIBWReader(Reader): """ Extracts data and metadata from Igor Binary Wave (.ibw) files containing images or force curves """
[docs] def read(self, verbose=False, parm_encoding='utf-8'): """ Reads the file given in file_path into a sidpy dataset Parameters ---------- verbose : Boolean (Optional) Whether or not to show print statements for debugging parm_encoding : str, optional Codec to be used to decode the bytestrings into Python strings if needed. Default 'utf-8' Returns ------- sidpy.Dataset : List of sidpy.Dataset objects. Multi-channel inputs are separated into individual dataset objects """ file_path = self._input_file_path # Load the ibw file first ibw_obj = bw.load(file_path) ibw_wave = ibw_obj.get('wave') parm_dict = self._read_parms(ibw_wave, parm_encoding) chan_labels, chan_units = self._get_chan_labels(ibw_wave, parm_encoding) if verbose: print('Channels and units found:') print(chan_labels) print(chan_units) # Get the data to figure out if this is an image or a force curve images = ibw_wave.get('wData') datasets = [] #list of sidpy datasets if images.shape[-1] != len(chan_labels): chan_labels = chan_labels[1:] # for layer 0 null set errors in older AR software chan_units = chan_units[1:] if images.ndim == 3: # Image stack if verbose: print('Found image stack of size {}'.format(images.shape)) num_rows = parm_dict['ScanLines'] num_cols = parm_dict['ScanPoints'] for channel in range(images.shape[-1]): #Convert it to sidpy dataset object data_set = sid.Dataset.from_array(images[:,:,channel], title=chan_labels[channel]) data_set.data_type = 'Image' #Add quantity and units data_set.units = chan_units[channel] data_set.quantity = chan_labels[channel] #Add dimension info data_set.set_dimension(0, sid.Dimension(np.linspace(0, parm_dict['FastScanSize'], num_cols), name = 'x', units=chan_units[channel], quantity = 'x', dimension_type='spatial')) data_set.set_dimension(1, sid.Dimension(np.linspace(0, parm_dict['SlowScanSize'], num_rows), name = 'y', units=chan_units[channel], quantity='y', dimension_type='spatial')) # append metadata data_set.original_metadata = parm_dict data_set.data_type = 'image' #Finally, append it datasets.append(data_set) else: # single force curve if verbose: print('Found force curve of size {}'.format(images.shape)) images = np.atleast_3d(images) # now [Z, chan, 1] # Find the channel that corresponds to either Z sensor or Raw: try: chan_ind = chan_labels.index('ZSnsr') spec_data = images[:,chan_ind].squeeze() except ValueError: try: chan_ind = chan_labels.index('Raw') spec_data = images[:,chan_ind,0].squeeze() except ValueError: # We don't expect to come here. If we do, spectroscopic values remains as is spec_data = np.arange(images.shape[2]) #Go through the channels for channel in range(images.shape[-2]): # The data generated above varies linearly. Override. # For now, we'll shove the Z sensor data into the spectroscopic values. #convert to sidpy dataset data_set = sid.Dataset.from_array((images[:,channel,0]), title=chan_labels[channel]) if verbose: print('Channel {} and spec_data is {}'.format(channel, spec_data)) #Set units, quantity data_set.units = chan_units[channel] data_set.quantity = chan_labels[channel] data_set.set_dimension(0, sid.Dimension(spec_data, name = chan_labels[channel], units=chan_units[channel], quantity=chan_labels[channel], dimension_type='spectral')) #append metadata data_set.data_type = 'line_plot' data_set.original_metadata = parm_dict #Add dataset to list datasets.append(data_set) # Return the dataset return datasets
@staticmethod def _read_parms(ibw_wave, codec='utf-8'): """ Parses the parameters in the provided dictionary Parameters ---------- ibw_wave : dictionary Wave entry in the dictionary obtained from loading the ibw file codec : str, optional Codec to be used to decode the bytestrings into Python strings if needed. Default 'utf-8' Returns ------- parm_dict : dictionary Dictionary containing parameters """ parm_string = ibw_wave.get('note') if type(parm_string) == bytes: try: parm_string = parm_string.decode(codec) except: parm_string = parm_string.decode('ISO-8859-1') # for older AR software parm_string = parm_string.rstrip('\r') parm_list = parm_string.split('\r') parm_dict = dict() for pair_string in parm_list: temp = pair_string.split(':') if len(temp) == 2: temp = [item.strip() for item in temp] try: num = float(temp[1]) parm_dict[temp[0]] = num try: if num == int(num): parm_dict[temp[0]] = int(num) except OverflowError: pass except ValueError: parm_dict[temp[0]] = temp[1] # Grab the creation and modification times: other_parms = ibw_wave.get('wave_header') for key in ['creationDate', 'modDate', 'bname']: try: parm_dict[key] = other_parms[key] except KeyError: pass return parm_dict @staticmethod def _get_chan_labels(ibw_wave, codec='utf-8'): """ Retrieves the names of the data channels and default units Parameters ---------- ibw_wave : dictionary Wave entry in the dictionary obtained from loading the ibw file codec : str, optional Codec to be used to decode the bytestrings into Python strings if needed. Default 'utf-8' Returns ------- labels : list of strings List of the names of the data channels default_units : list of strings List of units for the measurement in each channel """ temp = ibw_wave.get('labels') labels = [] for item in temp: if len(item) > 0: labels += item for item in labels: if item == '': labels.remove(item) default_units = list() for chan_ind, chan in enumerate(labels): # clean up channel names if type(chan) == bytes: chan = chan.decode(codec) if chan.lower().rfind('trace') > 0: labels[chan_ind] = chan[:chan.lower().rfind('trace') + 5] else: labels[chan_ind] = chan # Figure out (default) units if chan.startswith('Phase'): default_units.append('deg') elif chan.startswith('Current'): default_units.append('A') else: default_units.append('m') return labels, default_units
[docs] def can_read(self): """ Tests whether or not the provided file has a .ibw extension Returns ------- """ return super(IgorIBWReader, self).can_read(extension='ibw')