Skip to content

Commit

Permalink
separated from previous projects
Browse files Browse the repository at this point in the history
  • Loading branch information
eimrek committed Feb 14, 2022
0 parents commit 07a656c
Show file tree
Hide file tree
Showing 8 changed files with 526 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.egg-info/
dist/*
*.pyc
.ipynb_checkpoints
.vscode
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# igor-tools

Simple tools to manage IGOR Pro `.itx` files.

See `example.ipynb` for usage.
172 changes: 172 additions & 0 deletions example.ipynb

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions example_files/example_out.itx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
IGOR
WAVES/N=(6, 7) example_output
BEGIN
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00
END
X SetScale/P x 0.0,0.1, "m", example_output;
X SetScale/P y 0.0,0.2, "m", example_output;
1 change: 1 addition & 0 deletions example_files/waves_1d_and_2d.itx

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions igor_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import traceback

from . import igor

from .igor import Wave, Wave1d, Wave2d

__version__ = "0.1.0"


def read_itx(fname):
"""
Returns the list of igor waves that are included in the .itx file
"""
f=open(fname, 'r')
lines=f.readlines()
f.close()

line = lines.pop(0).strip()
if not line == "IGOR":
raise IOError("Files does not begin with 'IGOR'")

waves = []
while len(lines) != 0:
try:
wave = igor.read_wave(lines)
if wave is not None:
waves.append(wave)
except Exception:
traceback.print_exc()

return waves

def write_2d_itx(fname, data, xaxis, yaxis, wavename):
"""
Write a 2-dimensional data as .itx file
Args:
fname: Name of the output file
data: the 2d data
xaxis: iterable containing (x_min, x_delta, unit)
yaxis: iterable containing (y_min, y_delta, unit)
wavename: name of the igor wave
"""

x = igor.Axis('x', xaxis[0], xaxis[1], xaxis[2], wavename)
y = igor.Axis('y', yaxis[0], yaxis[1], yaxis[2], wavename)

wave = Wave2d(
data=data,
axes=[x,y],
name=wavename,
)

wave.write(fname)

253 changes: 253 additions & 0 deletions igor_tools/igor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
"""Classes for use with IGOR Pro
Partially based on asetk module by Leopold Talirz
(https://github.com/ltalirz/asetk/blob/master/asetk/format/igor.py)
"""

import re
import numpy as np

def read_wave(lines):
"""
Reads the next wave section from the inputted list of lines
Parsed lines are removed from the list, allowing for subsequent calls
"""

line = lines.pop(0)
while not re.match("WAVES",line):
if len(lines) == 0:
return None
line = lines.pop(0)
# 1d or 2d?
d2 = False
if "N=" in line:
d2 = True
match = re.search("WAVES/N=\(([\d, ]+)\)",line)
grid = match.group(1).split(',')
grid = np.array(grid, dtype=int)
name = line.split(")")[-1].strip()
else:
name = line.split()[-1]

line = lines.pop(0).strip()
if not line == "BEGIN":
raise IOError("Missing 'BEGIN' statement of data block")

# read data
datastring = ""
line = lines.pop(0)
while not re.match("END",line):
if len(lines) == 0:
return None
if line.startswith("X"):
return None
datastring += line
line = lines.pop(0)
data = np.array(datastring.split(), dtype=float)
if d2:
data = data.reshape(grid)

# read axes
axes = []
line = lines.pop(0)
matches = re.findall("SetScale.+?(?:;|$)", line)
for match in matches:
ax = Axis(None,None,None,None)
ax.read(match)
axes.append(ax)

if d2:
# read also the second axis
# is this necessary? can there be 2 lines with "SetScale" ?
line = lines.pop(0)
matches = re.findall("SetScale.+?(?:;|$)", line)
for match in matches:
ax = Axis(None,None,None,None)
ax.read(match)
axes.append(ax)
return Wave2d(data, axes, name)

return Wave1d(data, axes, name)


class Axis(object):
"""Represents an axis of an IGOR wave"""

def __init__(self, symbol, min_, delta, unit, wavename=None):
self.symbol = symbol
self.min = min_
self.delta = delta
self.unit = unit
self.wavename = wavename

def __str__(self):
"""Prints axis in itx format
Note: SetScale/P expects minimum value and step-size
"""
delta = 0 if self.delta is None else self.delta
s = "X SetScale/P {symb} {min},{delta}, \"{unit}\", {name};\n"\
.format(symb=self.symbol, min=self.min, delta=delta,\
unit=self.unit, name=self.wavename)
return s

def read(self, string):
"""Read axis from string
Format:
X SetScale/P x 0,2.01342281879195e-11,"m", data_00381_Up;
SetScale d 0,0,"V", data_00381_Up
"""
match = re.search("SetScale/?P? (.) ([+-\.\de]+),([+-\.\de]+),[ ]*\"(.*)\",\s*(\S+)", string)
self.symbol = match.group(1)
self.min = float(match.group(2))
self.delta = float(match.group(3))
self.unit = match.group(4)
self.wavename = match.group(5)
if self.wavename.endswith(';'):
self.wavename = self.wavename[:-1]


class Wave(object):
"""A class for IGOR waves"""

def __init__(self, data, axes, name=None):
"""Initialize IGOR wave of generic dimension"""
self.data = data
self.axes = axes
self.name = "PYTHON_IMPORT" if name is None else name
self.dim = len(self.data.shape)

def __str__(self):
"""Print IGOR wave"""
s = ""
s += "IGOR\n"

dimstring = "("
for i in range(len(self.data.shape)):
dimstring += "{}, ".format(self.data.shape[i])
dimstring = dimstring[:-2] + ")"

s += "WAVES/N={} {}\n".format(dimstring, self.name)
s += "BEGIN\n"
s += self.print_data()
s += "END\n"
for ax in self.axes:
s += str(ax)
return s

@property
def extent(self):
"""Returns extent for plotting"""
grid = self.data.shape
extent = []
for i in range(len(grid)):
ax = self.axes[i]
extent.append(ax.min)
extent.append(ax.min+ax.delta*grid[i])

return np.array(extent)

@property
def x_min(self):
return self.axes[0].min

@property
def dx(self):
return self.axes[0].delta

@property
def x_max(self):
return self.x_min + self.dx * self.data.shape[0]

@property
def x_arr(self):
return self.x_min + np.arange(0, self.data.shape[0])*self.dx

@property
def x_symbol(self):
return self.axes[0].symbol

@property
def x_unit(self):
return self.axes[0].unit

def print_data(self):
"""Determines how to print the data block.
To be reimplemented by subclasses."""
pass

def write(self, fname):
f=open(fname, 'w')
f.write(str(self))
f.close()

def csv_header(self):
header = ""
shape = self.data.shape
for i_ax in range(len(shape)):
ax = self.axes[i_ax]
if header != "":
header += "\n"
header += "axis %d: %s [unit: %s] [%.6e, %.6e], delta=%.6e, n=%d" % (
i_ax, ax.symbol, ax.unit, ax.min, ax.min+ax.delta*(shape[i_ax]-1), ax.delta, shape[i_ax]
)
return header

def write_csv(self, fname, fmt="%.6e"):
np.savetxt(fname, self.data, delimiter=",", header=self.csv_header(), fmt=fmt)


class Wave1d(Wave):
"""1d Igor wave"""

def __init__(self, data, axes, name="1d"):
"""Initialize 1d IGOR wave"""
super().__init__(data, axes, name)

def print_data(self):
"""Determines how to print the data block"""
s = ""
for line in self.data:
s += "{:12.6e}\n".format(float(line))
return s


class Wave2d(Wave):
"""2d Igor wave"""

def __init__(self, data, axes, name="2d"):
"""Initialize 2d IGOR wave"""
super().__init__(data, axes, name)

def print_data(self):
"""Determines how to print the data block"""
s = ""
for line in self.data:
for x in line:
s += "{:12.6e} ".format(x)
s += "\n"
return s

@property
def y_min(self):
return self.axes[1].min

@property
def dy(self):
return self.axes[1].delta

@property
def y_max(self):
return self.y_min + self.dy * self.data.shape[1]

@property
def y_arr(self):
return self.y_min + np.arange(0, self.data.shape[1])*self.dy

@property
def y_symbol(self):
return self.axes[1].symbol

@property
def y_unit(self):
return self.axes[1].unit
23 changes: 23 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import setuptools

with open("README.md", "r") as fh:
long_description = fh.read()

setuptools.setup(
name="igor-tools",
version="0.1.0",
description="Simple tools to manage IGOR Pro .itx files",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/nanotech-empa",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
install_requires=[
"numpy",
],
)

0 comments on commit 07a656c

Please sign in to comment.