Skip to content

Commit b294837

Browse files
committed
fix Elastix binary search on Windows + formatting
1 parent ab4de9e commit b294837

File tree

1 file changed

+78
-51
lines changed

1 file changed

+78
-51
lines changed

navis/transforms/elastix.py

Lines changed: 78 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@
2020
import subprocess
2121
import shutil
2222
import tempfile
23+
import platform
2324

2425
import numpy as np
2526
import pandas as pd
2627

2728
from .base import BaseTransform
2829
from ..utils import make_iterable
2930

30-
_search_path = [i for i in os.environ['PATH'].split(os.pathsep) if len(i) > 0]
31+
_search_path = [i for i in os.environ["PATH"].split(os.pathsep) if len(i) > 0]
3132

3233

33-
def find_elastixbin(tool: str = 'transformix') -> str:
34+
def find_elastixbin(tool: str = "transformix") -> str:
3435
"""Find directory with elastix binaries."""
3536
for path in _search_path:
3637
path = pathlib.Path(path)
@@ -45,15 +46,20 @@ def find_elastixbin(tool: str = 'transformix') -> str:
4546
raise
4647

4748

48-
_elastixbin = find_elastixbin()
49+
if platform.system() == "Windows":
50+
# On Windows, we have to search for `transformix.exe`
51+
# We can still invoke it as `transformix` via the command line though
52+
_elastixbin = find_elastixbin("transformix.exe")
53+
else:
54+
_elastixbin = find_elastixbin("transformix")
4955

5056

5157
def setup_elastix():
5258
"""Set up to make elastix work from inside a Python session.
5359
5460
Briefly: elastix requires the `LD_LIBRARY_PATH` (Linux) or `LDY_LIBRARY_PATH`
5561
(OSX) environment variables to (also) point to the directory with the
56-
elastix `lib` directory. For reasons unknown to me, these varibles do not
62+
elastix `lib` directory. For reasons unknown to me, these variables do not
5763
make it into the Python session. Hence, we have to set them here explicitly.
5864
5965
Above info is based on: https://github.com/jasper-tms/pytransformix
@@ -64,56 +70,59 @@ def setup_elastix():
6470
return
6571

6672
# Check if this variable already exists
67-
var = os.environ.get('LD_LIBRARY_PATH', os.environ.get('LDY_LIBRARY_PATH', ''))
73+
var = os.environ.get("LD_LIBRARY_PATH", os.environ.get("LDY_LIBRARY_PATH", ""))
6874

6975
# Get the actual path
70-
path = (_elastixbin.parent / 'lib').absolute()
76+
path = (_elastixbin.parent / "lib").absolute()
7177

7278
if str(path) not in var:
73-
var = f'{path}{os.pathsep}{var}' if var else str(path)
79+
var = f"{path}{os.pathsep}{var}" if var else str(path)
7480

7581
# Note that `LD_LIBRARY_PATH` works for both Linux and OSX
76-
os.environ['LD_LIBRARY_PATH'] = var
82+
os.environ["LD_LIBRARY_PATH"] = var
7783
# As per navis/issues/112
78-
os.environ['DYLD_LIBRARY_PATH'] = var
84+
os.environ["DYLD_LIBRARY_PATH"] = var
7985

8086

8187
setup_elastix()
8288

8389

8490
def requires_elastix(func):
8591
"""Check if elastix is available."""
92+
8693
@functools.wraps(func)
8794
def wrapper(*args, **kwargs):
8895
if not _elastixbin:
89-
raise ValueError("Could not find elastix binaries. Please download "
90-
"the releases page at https://github.com/SuperElastix/elastix, "
91-
"unzip at a convenient location and add that "
92-
"location to your PATH variable. Note that you "
93-
"will also have to set a LD_LIBRARY_PATH (Linux) "
94-
"or DYLD_LIBRARY_PATH (OSX) variable. See the "
95-
"elastic manual (release page) for details.")
96+
raise ValueError(
97+
"Could not find elastix binaries. Please download "
98+
"the releases page at https://github.com/SuperElastix/elastix, "
99+
"unzip at a convenient location and add that "
100+
"location to your PATH variable. Note that you "
101+
"will also have to set a LD_LIBRARY_PATH (Linux) "
102+
"or DYLD_LIBRARY_PATH (OSX) variable. See the "
103+
"elastic manual (release page) for details."
104+
)
96105
return func(*args, **kwargs)
106+
97107
return wrapper
98108

99109

100110
@requires_elastix
101111
def elastix_version(as_string=False):
102112
"""Get elastix version."""
103-
p = subprocess.run([_elastixbin / 'elastix', '--version'],
104-
capture_output=True)
113+
p = subprocess.run([_elastixbin / "elastix", "--version"], capture_output=True)
105114
if p.stderr:
106-
raise BaseException(f'Error running elastix:\n{p.stderr.decode()}')
115+
raise BaseException(f"Error running elastix:\n{p.stderr.decode()}")
107116

108-
version = p.stdout.decode('utf-8').rstrip()
117+
version = p.stdout.decode("utf-8").rstrip()
109118

110119
# Extract version from "elastix version: 5.0.1"
111-
version = version.split(':')[-1]
120+
version = version.split(":")[-1]
112121

113122
if as_string:
114123
return version
115124
else:
116-
return tuple(int(v) for v in version.split('.'))
125+
return tuple(int(v) for v in version.split("."))
117126

118127

119128
class ElastixTransform(BaseTransform):
@@ -146,44 +155,48 @@ def __init__(self, file: str, copy_files=[]):
146155
self.file = pathlib.Path(file)
147156
self.copy_files = copy_files
148157

149-
def __eq__(self, other: 'ElastixTransform') -> bool:
158+
def __eq__(self, other: "ElastixTransform") -> bool:
150159
"""Implement equality comparison."""
151160
if isinstance(other, ElastixTransform):
152161
if self.file == other.file:
153162
return True
154163
return False
155164

156-
def check_if_possible(self, on_error: str = 'raise'):
165+
def check_if_possible(self, on_error: str = "raise"):
157166
"""Check if this transform is possible."""
158167
if not _elastixbin:
159-
msg = 'Folder with elastix binaries not found. Make sure the ' \
160-
'directory is in your PATH environment variable.'
161-
if on_error == 'raise':
168+
msg = (
169+
"Folder with elastix binaries not found. Make sure the "
170+
"directory is in your PATH environment variable."
171+
)
172+
if on_error == "raise":
162173
raise BaseException(msg)
163174
return msg
164175
if not self.file.is_file():
165-
msg = f'Transformation file {self.file} not found.'
166-
if on_error == 'raise':
176+
msg = f"Transformation file {self.file} not found."
177+
if on_error == "raise":
167178
raise BaseException(msg)
168179
return msg
169180

170-
def copy(self) -> 'ElastixTransform':
181+
def copy(self) -> "ElastixTransform":
171182
"""Return copy."""
172183
# Attributes not to copy
173184
no_copy = []
174185
# Generate new empty transform
175186
x = self.__class__(self.file)
176187
# Override with this neuron's data
177-
x.__dict__.update({k: copy.copy(v) for k, v in self.__dict__.items() if k not in no_copy})
188+
x.__dict__.update(
189+
{k: copy.copy(v) for k, v in self.__dict__.items() if k not in no_copy}
190+
)
178191

179192
return x
180193

181194
def write_input_file(self, points, filepath):
182195
"""Write a numpy array in format required by transformix."""
183-
with open(filepath, 'w') as f:
184-
f.write('point\n{}\n'.format(len(points)))
196+
with open(filepath, "w") as f:
197+
f.write("point\n{}\n".format(len(points)))
185198
for x, y, z in points:
186-
f.write(f'{x:f} {y:f} {z:f}\n')
199+
f.write(f"{x:f} {y:f} {z:f}\n")
187200

188201
def read_output_file(self, filepath) -> np.ndarray:
189202
"""Load output file.
@@ -200,10 +213,10 @@ def read_output_file(self, filepath) -> np.ndarray:
200213
201214
"""
202215
points = []
203-
with open(filepath, 'r') as f:
216+
with open(filepath, "r") as f:
204217
for line in f.readlines():
205-
output = line.split('OutputPoint = [ ')[1].split(' ]')[0]
206-
points.append([float(i) for i in output.split(' ')])
218+
output = line.split("OutputPoint = [ ")[1].split(" ]")[0]
219+
points.append([float(i) for i in output.split(" ")])
207220
return np.array(points)
208221

209222
def xform(self, points: np.ndarray, return_logs=False) -> np.ndarray:
@@ -223,16 +236,20 @@ def xform(self, points: np.ndarray, return_logs=False) -> np.ndarray:
223236
Transformed points.
224237
225238
"""
226-
self.check_if_possible(on_error='raise')
239+
self.check_if_possible(on_error="raise")
227240

228241
if isinstance(points, pd.DataFrame):
229242
# Make sure x/y/z columns are present
230-
if np.any([c not in points for c in ['x', 'y', 'z']]):
231-
raise ValueError('points DataFrame must have x/y/z columns.')
232-
points = points[['x', 'y', 'z']].values
233-
elif not (isinstance(points, np.ndarray) and points.ndim == 2 and points.shape[1] == 3):
234-
raise TypeError('`points` must be numpy array of shape (N, 3) or '
235-
'pandas DataFrame with x/y/z columns')
243+
if np.any([c not in points for c in ["x", "y", "z"]]):
244+
raise ValueError("points DataFrame must have x/y/z columns.")
245+
points = points[["x", "y", "z"]].values
246+
elif not (
247+
isinstance(points, np.ndarray) and points.ndim == 2 and points.shape[1] == 3
248+
):
249+
raise TypeError(
250+
"`points` must be numpy array of shape (N, 3) or "
251+
"pandas DataFrame with x/y/z columns"
252+
)
236253

237254
# Everything happens in a temporary directory
238255
with tempfile.TemporaryDirectory() as tempdir:
@@ -244,13 +261,21 @@ def xform(self, points: np.ndarray, return_logs=False) -> np.ndarray:
244261
_ = pathlib.Path(shutil.copy(f, p))
245262

246263
# Write points to file
247-
in_file = p / 'inputpoints.txt'
264+
in_file = p / "inputpoints.txt"
248265
self.write_input_file(points, in_file)
249266

250-
out_file = p / 'outputpoints.txt'
267+
out_file = p / "outputpoints.txt"
251268

252269
# Prepare the command
253-
command = [_elastixbin / 'transformix', '-out', str(p), '-tp', str(self.file), '-def', str(in_file)]
270+
command = [
271+
_elastixbin / "transformix",
272+
"-out",
273+
str(p),
274+
"-tp",
275+
str(self.file),
276+
"-def",
277+
str(in_file),
278+
]
254279

255280
# Keep track of current working directory
256281
cwd = os.getcwd()
@@ -269,16 +294,18 @@ def xform(self, points: np.ndarray, return_logs=False) -> np.ndarray:
269294
os.chdir(cwd)
270295

271296
if return_logs:
272-
logfile = p / 'transformix.log'
297+
logfile = p / "transformix.log"
273298
if not logfile.is_file():
274-
raise FileNotFoundError('No log file found.')
299+
raise FileNotFoundError("No log file found.")
275300
with open(logfile) as f:
276301
logs = f.read()
277302
return logs
278303

279304
if not out_file.is_file():
280-
raise FileNotFoundError('Elastix transform did not produce any '
281-
f'output:\n {proc.stdout.decode()}')
305+
raise FileNotFoundError(
306+
"Elastix transform did not produce any "
307+
f"output:\n {proc.stdout.decode()}"
308+
)
282309

283310
points_xf = self.read_output_file(out_file)
284311

0 commit comments

Comments
 (0)