Commit 95d852f1 authored by Patrik Marschalik's avatar Patrik Marschalik
Browse files

Add translation for sxm files

The changes include:
  - Changes at _parse_3ds and translate to address the differences
    between the different Nanonis file types
  - PEP 8 and PEP 257 polishing
parent 102efb4c
# -*- coding: utf-8 -*-
from __future__ import division, print_function, absolute_import, unicode_literals
from __future__ import (division, print_function, absolute_import,
unicode_literals)
import os
import numpy as np
import h5py
from pyUSID.io.hdf_utils import create_indexed_group, write_main_dataset, write_simple_attrs, Dimension, \
write_ind_val_dsets
from pyUSID.io.hdf_utils import (create_indexed_group, write_main_dataset,
write_simple_attrs, Dimension,
write_ind_val_dsets)
from pyUSID.io.translator import Translator
from pyUSID.io.write_utils import get_aux_dset_slicing
from .df_utils.nanonis_utils import read_nanonis_file
......@@ -15,16 +17,17 @@ from .df_utils.nanonis_utils import read_nanonis_file
class NanonisTranslator(Translator):
"""
Translate Nanonis data files (3ds, sxm, and dat) into Pycroscopy compatible HDF5 files
Translator for Nanonis data files.
This translator provides method to translate Nanonis data files
(3ds, sxm, and dat) into Pycroscopy compatible HDF5 files.
Parameters
----------
filepath : str
Path to the input data file.
"""
def __init__(self, filepath, *args, **kwargs):
"""
Parameters
----------
filepath : str
Path to the input data file.
"""
super(Translator, self).__init__(*args, **kwargs)
filepath = os.path.abspath(filepath)
......@@ -57,7 +60,7 @@ class NanonisTranslator(Translator):
def translate(self, data_channels=None, verbose=False):
"""
Translates the data in the Nanonis file into a Pycroscopy compatible HDF5 file.
Translate the data into a Pycroscopy compatible HDF5 file.
Parameters
----------
......@@ -90,34 +93,38 @@ class NanonisTranslator(Translator):
h5_file = h5py.File(self.h5_path, 'w')
# Create measurement group and assign attributes
meas_grp = create_indexed_group(h5_file, 'Measurement')
write_simple_attrs(
meas_grp, self.parm_dict['meas_parms']
)
dc_offset = self.data_dict['sweep_signal']
spec_label, spec_units = self.parm_dict['sweep_signal'].split()
spec_units = spec_units.strip('()')
spec_dim = Dimension(spec_label, spec_units, dc_offset)
# Create datasets for positional and spectroscopic indices and values
spec_dim = self.data_dict['Spectroscopic Dimensions']
pos_dims = self.data_dict['Position Dimensions']
h5_pos_inds, h5_pos_vals = write_ind_val_dsets(meas_grp, pos_dims,
is_spectral=False)
h5_spec_inds, h5_spec_vals = write_ind_val_dsets(meas_grp, spec_dim,
is_spectral=True)
h5_pos_inds, h5_pos_vals = write_ind_val_dsets(meas_grp, pos_dims, is_spectral=False)
h5_spec_inds, h5_spec_vals = write_ind_val_dsets(meas_grp, spec_dim, is_spectral=True)
# Create the datasets for all the channels
num_points = h5_pos_inds.shape[0]
for data_channel in data_channels:
raw_data = self.data_dict[data_channel].reshape([num_points, -1])
chan_grp = create_indexed_group(meas_grp, 'Channel')
data_label, data_unit = data_channel.rsplit(maxsplit=1)
data_unit = data_unit.strip('()')
write_simple_attrs(chan_grp, self.parm_dict['channel_parms'][data_channel])
data_label = data_channel
data_unit = self.parm_dict['channel_parms'][data_channel]['Unit']
write_simple_attrs(
chan_grp, self.parm_dict['channel_parms'][data_channel]
)
write_main_dataset(chan_grp, raw_data, 'Raw_Data',
data_label, data_unit,
None, None,
h5_pos_inds=h5_pos_inds, h5_pos_vals=h5_pos_vals,
h5_spec_inds=h5_spec_inds, h5_spec_vals=h5_spec_vals)
h5_pos_inds=h5_pos_inds,
h5_pos_vals=h5_pos_vals,
h5_spec_inds=h5_spec_inds,
h5_spec_vals=h5_spec_vals)
h5_file.flush()
h5_file.close()
......@@ -125,13 +132,13 @@ class NanonisTranslator(Translator):
return self.h5_path
def _read_data(self, grid_file_path):
def _read_data(self, file_path):
"""
Handles reading the data from the file and extracting the needed parameters for translating.
Extracting data and parameters from Nanonis files.
Parameters
----------
grid_file_path : str
file_path : str
File path to the source data file
Returns
......@@ -139,7 +146,7 @@ class NanonisTranslator(Translator):
None
"""
data, file_ext = read_nanonis_file(grid_file_path)
data, file_ext = read_nanonis_file(file_path)
header_dict = data.header
signal_dict = data.signals
......@@ -162,53 +169,100 @@ class NanonisTranslator(Translator):
@staticmethod
def _parse_sxm_parms(header_dict, signal_dict):
"""
Parse sxm files.
Parameters
----------
header_dict
signal_dict
header_dict : dict
signal_dict : dict
Returns
-------
parm_dict
parm_dict : dict
"""
parm_dict = dict()
parm_dict['channels'] = header_dict.pop('scan>channels').split(';')
parm_dict['sweep_signal'] = 'Single Point'
signal_dict['sweep_signal'] = np.arange(1, dtype=np.float32)
nx, ny = header_dict['scan_pixels']
parm_dict['num_cols'] = nx
parm_dict['num_rows'] = ny
# Reorganize the channel parameters
info_dict = header_dict.pop('data_info', dict())
chan_names = info_dict.pop('Name')
parm_dict['channel_parms'] = {name: dict() for name in chan_names}
for field_name, field_val in info_dict.items():
for name, val in zip(chan_names, field_val):
parm_dict['channel_parms'][name][field_name] = val
data_dict = signal_dict
data_dict = dict()
# Create dictionary with measurement parameters
meas_parms = dict()
meas_parms['Acquisition time'] = header_dict['acq_time']
meas_parms['bias'] = header_dict['bias']
scan_dir = header_dict['scan_dir']
meas_parms['Scan direction'] = scan_dir
parm_dict['meas_parms'] = meas_parms
# Create dictionary with channel parameters
channel_parms = dict()
info_dict = header_dict['data_info']
channel_names = info_dict['Name']
single_channel_parms = {name: dict() for name in channel_names}
for field_name, field_value, in info_dict.items():
for channel_name, value in zip(channel_names, field_value):
single_channel_parms[channel_name][field_name] = value
for value in single_channel_parms.values():
if value['Direction'] == 'both':
value['Direction'] = ['forward', 'backward']
else:
direction = [value['Direction']]
for name, parms in single_channel_parms.items():
for direction in parms['Direction']:
key = ' '.join((name, direction))
channel_parms[key] = dict(parms)
channel_parms[key]['Direction'] = direction
data = signal_dict[name][direction]
if scan_dir == 'up':
data = np.flip(data, axis=0)
if direction == 'backward':
data = np.flip(data, axis=1)
data_dict[key] = data
parm_dict['channel_parms'] = channel_parms
# Position dimensions
num_cols, num_rows = header_dict['scan_pixels']
width, height = header_dict['scan_range']
pos_names = ['X', 'Y']
pos_units = ['nm', 'nm']
pos_vals = np.vstack([
np.linspace(0, width, num_cols),
np.linspace(0, height, num_rows),
])
pos_vals *= 1e9
pos_dims = [Dimension(name, unit, values) for name, unit, values
in zip(pos_names, pos_units, pos_vals)]
data_dict['Position Dimensions'] = pos_dims
# Spectroscopic dimensions
spec_dims = Dimension('arb.', 'a. u.', np.arange(1, dtype=np.float32))
data_dict['Spectroscopic Dimensions'] = spec_dims
return parm_dict, data_dict
@staticmethod
def _parse_3ds_parms(header_dict, signal_dict):
"""
Parse 3ds files.
Parameters
----------
header_dict
signal_dict
header_dict : dict
signal_dict : dict
Returns
-------
parm_dict
parm_dict : dict
"""
parm_dict = dict()
for key, parm_grid in zip(header_dict['fixed_parameters'] + header_dict['experimental_parameters'],
data_dict = dict()
# Create dictionary with measurement parameters
meas_parms = dict()
for key, parm_grid in zip(header_dict['fixed_parameters']
+ header_dict['experimental_parameters'],
signal_dict['params'].T):
# Collapse the parm_grid along one axis if it's constant along said axis
# Collapse the parm_grid along one axis if it's constant
# along said axis
if parm_grid.ndim > 1:
dim_slice = list()
# Find dimensions that are constant
......@@ -221,20 +275,33 @@ class NanonisTranslator(Translator):
# print(key, dim_slice)
# print(parm_grid[tuple(dim_slice)])
parm_grid = parm_grid[tuple(dim_slice)]
parm_dict[key] = parm_grid
meas_parms[key] = parm_grid
parm_dict['meas_parms'] = meas_parms
# Create dictionary with channel parameters and
# save channel data before renaming keys
data_channel_parms = dict()
for chan_name in header_dict['channels']:
parm_dict['channel_parms'] = {chan_name: {'Name': chan_name}}
parm_dict['sweep_signal'] = header_dict['sweep_signal']
splitted_chan_name = chan_name.split(maxsplit=2)
if len(splitted_chan_name) == 2:
direction = 'forward'
elif len(splitted_chan_name) == 3:
direction = 'backward'
splitted_chan_name.pop(1)
name, unit = splitted_chan_name
key = ' '.join((name, direction))
data_channel_parms[key] = {'Name': name,
'Direction': direction,
'Unit': unit.strip('()'),
}
data_dict[key] = signal_dict.pop(chan_name)
parm_dict['channel_parms'] = data_channel_parms
# Add remaining signal_dict elements to data_dict
data_dict.update(signal_dict)
# Position dimensions
nx, ny = header_dict['dim_px']
parm_dict['num_cols'] = nx
parm_dict['num_rows'] = ny
nx = parm_dict['num_cols']
ny = parm_dict['num_rows']
# num_points = nx * ny
if 'X (m)' in parm_dict:
row_vals = parm_dict.pop('X (m)')
else:
......@@ -244,42 +311,44 @@ class NanonisTranslator(Translator):
col_vals = parm_dict.pop('Y (m)')
else:
col_vals = np.arange(ny, dtype=np.float32)
pos_vals = np.hstack([row_vals.reshape(-1, 1),
col_vals.reshape(-1, 1)])
pos_names = ['X', 'Y']
# all channels are (nx, ny, nsweep), so not going to treat Z as position
# z_data = np.mean(parm_dict['Z (m)'], axis=0).reshape([num_points, -1])
# pos_vals = np.hstack([pos_vals, z_data])
# pos_names.append('Z')
# pos_vals *= 1E9
pos_dims = [Dimension(label, 'nm', values)
for label, values in zip(pos_names, pos_vals.T)]
data_dict = signal_dict
data_dict['Position Dimensions'] = pos_dims
# Spectroscopic dimensions
sweep_signal = header_dict['sweep_signal']
spec_label, spec_unit = sweep_signal.split(maxsplit=1)
spec_unit = spec_unit.strip('()')
# parm_dict['sweep_signal'] = (sweep_name, sweep_unit)
dc_offset = data_dict['sweep_signal']
spec_dim = Dimension(spec_label, spec_unit, dc_offset)
data_dict['Spectroscopic Dimensions'] = spec_dim
return parm_dict, data_dict
@staticmethod
def _parse_dat_parms(header_dict, signal_dict):
"""
Parse dat files.
Parameters
----------
header_dict
signal_dict
header_dict : dict
signal_dict : dict
Returns
-------
parm_dict : dict
"""
pass
def _parse_file_path(self, file_path):
"""
Get the folder and base filename for the input data file
Get the folder and base filename for the input data file.
Parameters
----------
......@@ -291,8 +360,8 @@ class NanonisTranslator(Translator):
folder_path : str
Path to the directory containing the input data file
basename : str
The base of the input file after stripping away the extension and folder
from the path
The base of the input file after stripping away the
extension and folder from the path
"""
# Get the folder and basename from the file path
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment