Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Sean Fitzgibbon
fslpy
Commits
db453f52
Commit
db453f52
authored
Apr 22, 2020
by
Paul McCarthy
🚵
Browse files
Merge branch 'enh/wsl' into 'master'
Enh/wsl See merge request fsl/fslpy!228
parents
b4ae9ac1
bdce436f
Changes
6
Hide whitespace changes
Inline
Side-by-side
CHANGELOG.rst
View file @
db453f52
...
...
@@ -6,6 +6,18 @@ order.
-------------------------
Added
^^^^^
* New :func:`.winpath` and :func:`wslpath` functions for working with paths
when using FSL in a Windows Subsystem for Linux (WSL) environment.
* New :func:`.wslcmd` function for generating a path to a FSL command installed
in a WSL environment.
* New :meth:`.Platform.fslwsl` attribute for detecting whether FSL is installed
in a WSL environment.
Fixed
^^^^^
...
...
fsl/utils/path.py
View file @
db453f52
...
...
@@ -23,6 +23,8 @@ paths.
removeDuplicates
uniquePrefix
commonBase
wslpath
winpath
"""
...
...
@@ -30,6 +32,9 @@ import os.path as op
import
os
import
glob
import
operator
import
re
from
fsl.utils.platform
import
platform
class
PathError
(
Exception
):
...
...
@@ -524,3 +529,58 @@ def commonBase(paths):
return
base
raise
PathError
(
'No common base'
)
def
wslpath
(
winpath
):
"""
Convert Windows path (or a command line argument containing a Windows path)
to the equivalent WSL path (e.g. ``c:
\\
Users`` -> ``/mnt/c/Users``). Also supports
paths in the form ``
\\
wsl$
\\
(distro)
\\
users
\\
...``
:param winpath: Command line argument which may (or may not) contain a Windows path. It is assumed to be
either of the form <windows path> or --<arg>=<windows path>. Note that we don't need to
handle --arg <windows path> or -a <windows path> since in these cases the argument
and the path will be parsed as separate entities.
:return: If ``winpath`` matches a Windows path, the converted argument (including the --<arg>= portion).
Otherwise returns ``winpath`` unchanged.
"""
match
=
re
.
match
(
r
"^(--[\w-]+=)?\\\\wsl\$[\\\/][^\\^\/]+(.*)$"
,
winpath
)
if
match
:
arg
,
path
=
match
.
group
(
1
,
2
)
if
arg
is
None
:
arg
=
""
return
arg
+
path
.
replace
(
"
\\
"
,
"/"
)
match
=
re
.
match
(
r
"^(--[\w-]+=)?([a-zA-z]):(.+)$"
,
winpath
)
if
match
:
arg
,
drive
,
path
=
match
.
group
(
1
,
2
,
3
)
if
arg
is
None
:
arg
=
""
return
arg
+
"/mnt/"
+
drive
.
lower
()
+
path
.
replace
(
"
\\
"
,
"/"
)
return
winpath
def
winpath
(
wslpath
):
"""
Convert a WSL-local filepath (for example ``/usr/local/fsl/``) into a path that can be used from
Windows.
If ``self.fslwsl`` is ``False``, simply returns ``wslpath`` unmodified
Otherwise, uses ``FSLDIR`` to deduce the WSL distro in use for FSL.
This requires WSL2 which supports the ``
\\
wsl$\`` network path.
wslpath is assumed to be an absolute path.
"""
if
not
platform
.
fslwsl
:
return
wslpath
else
:
match
=
re
.
match
(
r
"^\\\\wsl\$\\([^\\]+).*$"
,
platform
.
fsldir
)
if
match
:
distro
=
match
.
group
(
1
)
else
:
distro
=
None
if
not
distro
:
raise
RuntimeError
(
"Could not identify WSL installation from FSLDIR (%s)"
%
platform
.
fsldir
)
return
"
\\\\
wsl$
\\
"
+
distro
+
wslpath
.
replace
(
"/"
,
"
\\
"
)
fsl/utils/platform.py
View file @
db453f52
...
...
@@ -292,6 +292,12 @@ class Platform(notifier.Notifier):
return
os
.
environ
.
get
(
'FSLDEVDIR'
,
None
)
@
property
def
fslwsl
(
self
):
"""Boolean flag indicating whether FSL is installed in Windows Subsystem for Linux """
return
self
.
fsldir
is
not
None
and
self
.
fsldir
.
startswith
(
"
\\\\
wsl$"
)
@
fsldir
.
setter
def
fsldir
(
self
,
value
):
"""Changes the value of the :attr:`fsldir` property, and notifies any
...
...
fsl/utils/run.py
View file @
db453f52
...
...
@@ -20,21 +20,22 @@
"""
import
sys
import
shlex
import
logging
import
threading
import
contextlib
import
collections
import
subprocess
as
sp
import
os.path
as
op
import
six
import
sys
import
shlex
import
logging
import
threading
import
contextlib
import
collections.abc
as
abc
import
subprocess
as
sp
import
os.path
as
op
import
os
import
six
from
fsl.utils.platform
import
platform
as
fslplatform
import
fsl.utils.fslsub
as
fslsub
import
fsl.utils.tempdir
as
tempdir
import
fsl.utils.path
as
fslpath
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -202,7 +203,7 @@ def run(*args, **kwargs):
if
submit
is
True
:
submit
=
dict
()
if
submit
is
not
None
and
not
isinstance
(
submit
,
collections
.
Mapping
):
if
submit
is
not
None
and
not
isinstance
(
submit
,
abc
.
Mapping
):
raise
ValueError
(
'submit must be a mapping containing '
'options for fsl.utils.fslsub.submit'
)
...
...
@@ -359,7 +360,12 @@ def runfsl(*args, **kwargs):
args
=
prepareArgs
(
args
)
for
prefix
in
prefixes
:
cmdpath
=
op
.
join
(
prefix
,
args
[
0
])
if
op
.
isfile
(
cmdpath
):
if
fslplatform
.
fslwsl
:
wslargs
=
wslcmd
(
cmdpath
,
*
args
)
if
wslargs
is
not
None
:
args
=
wslargs
break
elif
op
.
isfile
(
cmdpath
):
args
[
0
]
=
cmdpath
break
...
...
@@ -372,6 +378,53 @@ def runfsl(*args, **kwargs):
return
run
(
*
args
,
**
kwargs
)
def
wslcmd
(
cmdpath
,
*
args
):
"""
Convert a command + arguments into an equivalent set of arguments that will run the command
under Windows Subsystem for Linux
:param cmdpath: Fully qualified path to the command. This is essentially a WSL path not a Windows
one since FSLDIR is specified as a WSL path, however it may have backslashes
as path separators due to previous use of ``os.path.join``
:param args: Sequence of command arguments (the first of which is the unqualified command name)
:return: If ``cmdpath`` exists and is executable in WSL, return a sequence of command arguments
which when executed will run the command in WSL. Windows paths in the argument list will
be converted to WSL paths. If ``cmdpath`` was not executable in WSL, returns None
"""
# Check if command exists in WSL (remembering that the command path may include FSLDIR which
# is a Windows path)
cmdpath
=
fslpath
.
wslpath
(
cmdpath
)
retcode
=
sp
.
call
([
"wsl"
,
"test"
,
"-x"
,
cmdpath
])
if
retcode
==
0
:
# Form a new argument list and convert any Windows paths in it into WSL paths
wslargs
=
[
fslpath
.
wslpath
(
arg
)
for
arg
in
args
]
wslargs
[
0
]
=
cmdpath
local_fsldir
=
fslpath
.
wslpath
(
fslplatform
.
fsldir
)
if
fslplatform
.
fsldevdir
:
local_fsldevdir
=
fslpath
.
wslpath
(
fslplatform
.
fsldevdir
)
else
:
local_fsldevdir
=
None
# Prepend important environment variables - note that it seems we cannot
# use WSLENV for this due to its insistance on path mapping. FIXME FSLDEVDIR?
local_path
=
"$PATH"
if
local_fsldevdir
:
local_path
+=
":%s/bin"
%
local_fsldevdir
local_path
+=
":%s/bin"
%
local_fsldir
prepargs
=
[
"wsl"
,
"PATH=%s"
%
local_path
,
"FSLDIR=%s"
%
local_fsldir
,
"FSLOUTPUTTYPE=%s"
%
os
.
environ
.
get
(
"FSLOUTPUTTYPE"
,
"NIFTI_GZ"
)
]
if
local_fsldevdir
:
prepargs
.
append
(
"FSLDEVDIR=%s"
%
local_fsldevdir
)
return
prepargs
+
wslargs
else
:
# Command was not found in WSL with this path
return
None
def
wait
(
job_ids
):
"""Proxy for :func:`.fslsub.wait`. """
return
fslsub
.
wait
(
job_ids
)
tests/test_fsl_utils_path.py
View file @
db453f52
...
...
@@ -13,6 +13,7 @@ import shutil
import
tempfile
import
pytest
import
mock
import
fsl.utils.path
as
fslpath
import
fsl.data.image
as
fslimage
...
...
@@ -1395,3 +1396,19 @@ def test_commonBase():
for
ft
in
failtests
:
with
pytest
.
raises
(
fslpath
.
PathError
):
fslpath
.
commonBase
(
ft
)
def
test_wslpath
():
assert
fslpath
.
wslpath
(
'c:
\\
Users
\\
Fishcake
\\
image.nii.gz'
)
==
'/mnt/c/Users/Fishcake/image.nii.gz'
assert
fslpath
.
wslpath
(
'--input=x:
\\
transfers
\\
scratch
\\
image_2.nii'
)
==
'--input=/mnt/x/transfers/scratch/image_2.nii'
assert
fslpath
.
wslpath
(
'
\\\\
wsl$
\\
centos 7
\\
users
\\
fsl
\\
file.nii'
)
==
'/users/fsl/file.nii'
assert
fslpath
.
wslpath
(
'--file=
\\\\
wsl$
\\
centos 7
\\
home
\\
fsl
\\
img.nii.gz'
)
==
'--file=/home/fsl/img.nii.gz'
assert
fslpath
.
wslpath
(
'
\\\\
wsl$/centos 7/users
\\
fsl
\\
file.nii'
)
==
'/users/fsl/file.nii'
def
test_winpath
():
"""
See comment for ``test_fslwsl`` for why we are overwriting FSLDIR
"""
with
mock
.
patch
.
dict
(
'os.environ'
,
**
{
'FSLDIR'
:
'
\\\\
wsl$
\\
my cool linux distro v2.0
\\
usr
\\
local
\\
fsl'
}):
assert
fslpath
.
winpath
(
"/home/fsl/myfile.dat"
)
==
'
\\\\
wsl$
\\
my cool linux distro v2.0
\\
home
\\
fsl
\\
myfile.dat'
with
mock
.
patch
.
dict
(
'os.environ'
,
**
{
'FSLDIR'
:
'/opt/fsl'
}):
assert
fslpath
.
winpath
(
"/home/fsl/myfile.dat"
)
==
'/home/fsl/myfile.dat'
tests/test_platform.py
View file @
db453f52
...
...
@@ -212,3 +212,17 @@ def test_detect_ssh():
p
=
fslplatform
.
Platform
()
assert
not
p
.
inSSHSession
assert
not
p
.
inVNCSession
def
test_fslwsl
():
"""
Note that ``Platform.fsldir`` requires the directory in ``FSLDIR`` to exist and
sets ``FSLDIR`` to ``None`` if it doesn't. So we create a ``Platform`` first
and then overwrite ``FSLDIR``. This is a bit of a hack but the logic we are testing
here is whether ``Platform.fslwsl`` recognizes a WSL ``FSLDIR`` string
"""
p
=
fslplatform
.
Platform
()
with
mock
.
patch
.
dict
(
'os.environ'
,
**
{
'FSLDIR'
:
'
\\\\
wsl$
\\
my cool linux distro v1.0
\\
usr
\\
local
\\
fsl'
}):
assert
p
.
fslwsl
with
mock
.
patch
.
dict
(
'os.environ'
,
**
{
'FSLDIR'
:
'/usr/local/fsl'
}):
assert
not
p
.
fslwsl
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment