Source code for SciFiReaders.readers.microscopy.spm.stm.omicron_asc

# -*- coding: utf-8 -*-
"""
Created on Fri Jan 22 15:48:34 2020

@author: Suhas Somnath, Sudhajit Misra, Rama Vasudevan
"""

from __future__ import division, print_function, absolute_import, unicode_literals
import sys
import numpy as np  # For array operations
import sidpy as sid
from sidpy.sid import Reader
import re
from os import path

if sys.version_info.major == 3:
    unicode = str

[docs]class AscReader(Reader): """ Reads Scanning Tunnelling Spectroscopy (STS) data in .asc files obtained from Omicron STMs. These are actually compatible with Nanonis controllers for Omicron microscopes, It seems they are probably NOT compatible with matrix controller outputs. See the group discussion at https://groups.google.com/g/pycroscopy/c/d7lcGnlPFpo/m/UEysliC8CAAJ TODO: At this point this AscReader does not appear to work for non-Z spec files. Needs to be fixed! """
[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 folder_path, file_name = path.split(file_path) file_name = file_name[:-4] # Extracting the raw data into memory file_handle = open(file_path, 'r') string_lines = file_handle.readlines() file_handle.close() # Extract parameters from the first few header lines parm_dict, num_headers = self._read_parms(string_lines) if verbose: print(parm_dict) num_rows = int(parm_dict['Main-y_pixels']) num_cols = int(parm_dict['Main-x_pixels']) num_pos = num_rows * num_cols spectra_length = int(parm_dict['Main-z_points']) # Extract the STS data from subsequent lines raw_data_2d = self._read_data(string_lines, num_pos, spectra_length, num_headers) raw_data = np.reshape(raw_data_2d, (num_rows, num_cols, spectra_length)) # Generate the x / voltage / spectroscopic axis: volt_vec = np.linspace(parm_dict['Spectroscopy-Device_1_Start [Volt]'], parm_dict['Spectroscopy-Device_1_End [Volt]'], spectra_length) # Build row, column vectors xvec = np.linspace(0, parm_dict['Main-x_length'], num_cols) yvec = np.linspace(0, parm_dict['Main-y_length'], num_rows) # now write it to the sidpy dataset object data_set = sid.Dataset.from_array(raw_data, name='Omicron_STS') data_set.data_type = 'spectral_image' # Add quantity and units data_set.units = parm_dict['Main-value_unit'] data_set.quantity = 'Current' # Add dimension info data_set.set_dimension(0, sid.Dimension(xvec, name='x', units='nm', quantity='position', dimension_type=sid.DimensionType.SPATIAL)) data_set.set_dimension(1, sid.Dimension(yvec, name='y', units='nm', quantity='position', dimension_type=sid.DimensionType.SPATIAL)) data_set.set_dimension(2, sid.Dimension(volt_vec, name='I', units=parm_dict['Main-value_unit'], quantity='Current', dimension_type=sid.DimensionType.SPECTRAL)) # append metadata data_set.metadata = parm_dict # Return the sidy dataset return data_set
def _read_data(self, string_lines, num_pos, spectra_length, num_headers): """ Reads the data from lines of the data file Parameters ---------- string_lines : :class:`list` of str Lines containing the data in string format, separated by tabs num_pos : unsigned int Number of pixels spectra_length : unsigned int Number of points in the spectral / voltage axis num_headers : unsigned int Number of header lines to ignore Returns ------- raw_data_2d : 2D numpy array Data arranged as [position x voltage points] """ raw_data_2d = np.zeros(shape=(num_pos, spectra_length), dtype=np.float32) for line_ind in range(num_pos): this_line = string_lines[num_headers + line_ind] string_spectrum = this_line.split('\t')[:-1] # omitting the new line raw_data_2d[line_ind] = np.array(string_spectrum, dtype=np.float32) return raw_data_2d def _parse_file_path(self, input_path): pass @staticmethod def _read_parms(string_lines): """ Returns the parameters regarding the experiment as dictionary Parameters ---------- string_lines : list of strings Lines from the data file in string representation Returns ------- parm_dict : dictionary Dictionary of parameters regarding the experiment """ # Reading parameters stored in the first few rows of the file def parse_header(line): # ". Aux2_V:" match_obj = re.match(r'\. (.*):', line, re.M | re.I) type_list = [str] if match_obj: raw_vals = [type_caster(match_obj.group(ind)) for ind, type_caster in zip(range(1, 1 + len(type_list)), type_list)] return raw_vals[0] else: return None def parse_parm(line): # ". . Auto_Flush_Period = 0.1 Second" match_obj = re.match(r'\. \. (.*) = (.*)', line, re.M | re.I) type_list = [str, str] if match_obj: raw_vals = [type_caster(match_obj.group(ind)) for ind, type_caster in zip(range(1, 1 + len(type_list)), type_list)] # Some cleaning: raw_vals[0] = raw_vals[0].replace('-', '_').strip() # We use '-' as a level separator raw_vals[1] = raw_vals[1].replace('--', '').strip() # often, units are on the values side, see if these can be transitioned over to the key: vals_split = raw_vals[1].split(' ') if len(vals_split) == 2: raw_vals = [raw_vals[0] + ' [' + vals_split[1] + ']', vals_split[0]] try: raw_vals[1] = float(raw_vals[1]) # convert those values that should be integers: if raw_vals[1] % 1 == 0: raw_vals[1] = int(raw_vals[1]) except ValueError: pass return {raw_vals[0]: raw_vals[1]} else: return None def flatten_dict(nested_dict, separator='-'): # From: https://codereview.stackexchange.com/questions/21033/flatten-dictionary-in-python-functional-style def expand(outer_key, outer_value): if isinstance(outer_value, dict): return [(outer_key + separator + inner_key, inner_value) for inner_key, inner_value in flatten_dict(outer_value).items()] else: return [(outer_key, outer_value)] items = [item for outer_key, outer_value in nested_dict.items() for item in expand(outer_key, outer_value)] return dict(items) # ############################################################################################################# temp_dict = dict() line = string_lines[1] if line.startswith('# Created by SPIP'): line = line.replace('# Created by SPIP ', '').replace('\n', ' ') ind = line.index(' ') temp_dict['SPIP_version'] = line[:ind] temp_dict['creation_time'] = line[ind + 1:] # ################################################################################################# line_offset = 3 for line_ind, line in enumerate(string_lines[line_offset:]): if parse_header(line) is not None: line_offset += line_ind break line = line.replace('# ', '') line = line.replace('\n', '') temp = line.split('=') try: test = temp[1].strip() try: test = float(test) # convert those values that should be integers: if test % 1 == 0: test = int(test) except ValueError: pass except IndexError: pass temp_dict[temp[0].strip().replace('-', '_')] = test main_dict = {'Main': temp_dict.copy()} # ################################################################################################# curr_cat_name = None temp_dict = dict() for ind, line in enumerate(string_lines[line_offset:]): if line.strip().startswith('# Start of Data:'): line_offset += ind + 1 break header_name = parse_header(line) if header_name: if curr_cat_name is not None: main_dict[curr_cat_name] = temp_dict.copy() temp_dict = dict() curr_cat_name = header_name else: this_parm = parse_parm(line) if this_parm is None: continue temp_dict.update(this_parm) if len(temp_dict) > 0: main_dict[curr_cat_name] = temp_dict.copy() # ################################################################################################# return flatten_dict(main_dict), line_offset
[docs] def can_read(self): """ Tests whether or not the provided file has a .asc extension Returns ------- """ return super(AscReader, self).can_read(extension='asc')