import numpy as np
import os
import re
import SimpleITK as sitk
from typing import *
from disptools import *
try:
import vtk
except ImportError as e:
print("Warning: cannot import 'vtk' module. " +
"Some functionalities depending upon it may be unavailable.")
try:
import ply
import ply.lex as lex
import ply.yacc as yacc
except ImportError as e:
print("Warning: cannot import 'ply' module. " +
"Some functionalities depending upon it may be unavailable.")
class _ElastixParametersLexer():
r""" Lexer for Elastix parameter files.
"""
def __init__(self, **kwargs):
self.lexer = lex.lex(module=self, **kwargs)
def test(self, data):
self.lexer.input(data)
while True:
tok = self.lexer.token()
if not tok:
break
print(tok)
t_ignore = " \t"
tokens = (
'LPAREN',
'RPAREN',
'IDENTIFIER',
'STRING',
'NUMBER',
'COMMENT'
)
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_IDENTIFIER = r'[a-zA-Z_][a-zA-Z0-9_]*'
def t_NUMBER(self, t):
r'[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?'
t.value = float(t.value)
if t.value.is_integer():
t.value = int(t.value)
return t
def t_STRING(self, t):
r'"[^"]*"'
t.value = t.value[1:-1]
if t.value == 'true':
t.value = True
elif t.value == 'false':
t.value = False
return t
def t_error(self, t):
raise Exception("Lexer error while reading parameter file at line %d, token '%s'"
% (t.lexer.lineno, t.value[0]))
def t_COMMENT(self, t):
r'//.*'
pass
def t_newline(self, t):
r'\n+'
t.lexer.lineno += t.value.count("\n")
class _ElastixParametersParser():
r""" Parser object for Elastix parameter files.
The `parse(text)` method accepts as input the content of an Elastix
parameter file, and returns a dictionary containing couples of
parameters with their respective values.
"""
def __init__(self, lexer_opts={}, **kwargs):
self.names = {}
self.lexer = _ElastixParametersLexer(**lexer_opts)
self.tokens = self.lexer.tokens
self.parser = yacc.yacc(module=self, **kwargs)
def parse(self, text):
self.names = {}
self.parser.parse(text, lexer=self.lexer.lexer)
return self.names
precedence = ()
def p_program(self, p):
'''program : program statement
| statement'''
pass
def p_statement_assign(self, p):
'''statement : LPAREN IDENTIFIER rvaluelist RPAREN'''
self.names[p[2]] = p[3]
def p_statement_comment(self, p):
'''statement : COMMENT'''
pass
def p_expression_rvalue(self, p):
'''rvalue : NUMBER
| STRING'''
p[0] = p[1]
def p_expression_rvaluelist_2(self, p):
'''rvaluelist : rvalue
| rvaluelist rvalue'''
if len(p) == 2:
p[0] = [p[1]]
else:
p[0] = p[1] + [p[2]]
def p_error(self, p):
raise Exception("Syntax error while reading parameter file at '%s'" % p.value)
[docs]def make_unique_directory(path: str) -> str:
r""" Create a unique directory.
If a directory with the given name already exists,
a suffix in the form `_x` with `x` integer will be
added at the end of its name.
Parameters
----------
path : str
Relative or absolute path for the new directory.
Returns
-------
str
A string containing the path of the new directory.
"""
if not os.path.exists(path):
os.makedirs(path)
return path
base_path, name = os.path.split(path)
name, extension = os.path.splitext(name)
i = 1
while True:
path = os.path.join(base_path, '%s_%d%s' % (name, i, extension))
if not os.path.exists(path):
os.makedirs(path)
return path
i += 1
[docs]def read_rvf(filename: str) -> sitk.Image:
r""" Read an RVF file.
Read image data from an RVF file.
Parameters
----------
filename : str
Filename for the input vector field.
Returns
-------
result : sitk.Image
Vector field.
"""
with open(filename, 'rb') as f:
size = [int(i) for i in f.readline().split()]
spacing = [float(i) for i in f.readline().split()]
data = np.fromfile(f, dtype=np_float_type)
image = sitk.GetImageFromArray(data.reshape((*size, 3), order='C'))
image.SetSpacing(tuple(spacing))
return image
[docs]def write_vtk_points(points: np.ndarray, filename: str) -> None:
r""" Write a set of points to a VTK PolyData file.
The points are given as a numpy bidimensional array, with a
row for each point, and three columns, one per component.
Parameters
----------
points : np.ndarray
A `n × m` array containing `n` points with `m` components.
filename : str
Output file.
"""
if 'vtk' not in sys.modules:
raise Exception('write_vtk_points: vtk module is required to use this feature.')
vtk_points = vtk.vtkPoints()
for i in range(0, points.shape[0]):
vtk_points.InsertNextPoint(points[i,0], points[i,1], points[i,2])
polydata = vtk.vtkPolyData()
polydata.SetPoints(vtk_points)
writer = vtk.vtkPolyDataWriter()
writer.SetInputData(polydata)
writer.SetFileName(filename)
writer.Update()
[docs]def read_elastix_points(filename: str) -> np.ndarray:
r""" Read a set of points from a file in Elastix format.
The points are returned as a two-dimensional numpy array, with a
row for each point and three columns, one per coordinate.
Parameters
----------
filename : str
Output file.
Returns
-------
np.ndarray
A `n × m` array containing `n` points with `m` components.
"""
with open(filename, 'r') as f:
line = f.readline()
if not re.match(r'\s*point|index\s*', line):
raise Exception('Invalid point file format')
line = f.readline()
try:
n = int(re.match(r'([0-9]+)', line).group(1))
except:
raise Exception('Invalid point file format')
points = np.empty([n, 3])
i = 0
for line in f:
try:
m = re.match(r'([0-9.e+-]+)\s+([0-9.e+-]+)\s+([0-9.e+-]+)', line)
points[i,:] = [float(x) for x in m.groups()]
except:
raise Exception('Invalid point file format')
i += 1
return points
[docs]def write_elastix_points(
points: np.ndarray,
filename: str,
point_format: str = 'point'
) -> None:
r""" Write a set of points to a file in Elastix format.
The points are passed as a two-dimensional numpy array, with a row
for each point and three columns, one per coordinate.
Parameters
----------
points : np.ndarray
A `n × m` array containing `n` points with `m` components.
filename : str
Output file.
point_format : str
One of 'point' (default) or 'index'.
"""
if point_format not in ['point', 'index']:
raise Exception('Unsupported point format %s' % point_format)
if len(points.shape) != 2 and points.shape[1] != 3:
raise Exception('Invalid point array')
with open(filename, 'w') as f:
f.write('%s\n' % point_format)
f.write('%d\n' % points.shape[0])
for i in range(0, points.shape[0]):
f.write('%.10e\t%.10e\t%.10e\n' % (points[i,0], points[i,1], points[i,2]))
[docs]def read_elastix_parameters(filename: str) -> dict:
r""" Read an Elastix parameter file.
Read an Elastix parameter file, returning a dictionary mapping each
parameter name to a list of values. Booleans are converted to Python
`bool`s, same for `int` and `float` values. Strings do not need to be
quoted. Comments are discarded.
.. warning::
This function will not work if Python is allowed to discard docstrings
(e.g. due to the option -OO).
Parameters
----------
filename : str
Name of the parameter file to be read.
Returns
-------
dict
Dictionary of parameters.
"""
if 'ply' not in sys.modules:
raise Exception('read_elastix_parameters: ply module is required to use this feature.')
if not hasattr(read_elastix_parameters, "parser"):
try:
read_elastix_parameters.parser = _ElastixParametersParser(debug=False, write_tables=False)
except SyntaxError:
raise Exception('read_elastix_parameters: unable to build the lexer. '
'Please make sure you are not running Python with '
'docstring discarding enabled (-OO).')
with open(filename, 'r') as f:
return read_elastix_parameters.parser.parse(f.read())
[docs]def write_elastix_parameters(parameters: dict, filename: str) -> None:
r""" Write Elastix parameters to file.
Write an Elastix parameter file. Parameters are passed as a dictionary
mapping each parameter name to a list of values. Python `bool`s, `int`s,
and `float`s are converted automatically.
Parameters
----------
parameters : dict
Dictionary of parameters.
filename : str
Name of the parameter file to be read.
"""
def _format(p):
if isinstance(p, bool):
return '"true"' if p else '"false"'
elif isinstance(p, str):
return '"%s"' % p
else:
return '%s' % p
with open(filename, 'w') as f:
for k, v in parameters.items():
f.write('(%s %s)\n' % (k, ' '.join([_format(p) for p in v])))