2020import subprocess
2121import shutil
2222import tempfile
23+ import platform
2324
2425import numpy as np
2526import pandas as pd
2627
2728from .base import BaseTransform
2829from ..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
5157def 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
8187setup_elastix ()
8288
8389
8490def 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
101111def 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
119128class 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