Commit 5917cf19 authored by Paul McCarthy's avatar Paul McCarthy 🚵
Browse files

Merge branch 'rel/3.7.1' into 'v3.7'

Rel/3.7.1

See merge request fsl/fslpy!314
parents 33999995 7a054c5b
Pipeline #11474 passed with stages
in 5 minutes and 16 seconds
...@@ -171,6 +171,12 @@ test:3.9: ...@@ -171,6 +171,12 @@ test:3.9:
<<: *test_template <<: *test_template
# test:3.10:
# stage: test
# image: pauldmccarthy/fsleyes-py310-wxpy4-gtk3
# <<: *test_template
test:build-pypi-dist: test:build-pypi-dist:
stage: test stage: test
image: pauldmccarthy/fsleyes-py37-wxpy4-gtk3 image: pauldmccarthy/fsleyes-py37-wxpy4-gtk3
......
...@@ -2,6 +2,26 @@ This document contains the ``fslpy`` release history in reverse chronological ...@@ -2,6 +2,26 @@ This document contains the ``fslpy`` release history in reverse chronological
order. order.
3.7.1 (Friday 12th November 2021)
---------------------------------
Changed
^^^^^^^
* BIDS and ``dcm2niix`` ``.json`` sidecar files with control characters
are now accepted (!312).
Fixed
^^^^^
* Fixed an issue with temporary input files created by :mod:`fsl.wrappers`
functions not being deleted (!313).
3.7.0 (Friday 20th August 2021) 3.7.0 (Friday 20th August 2021)
------------------------------- -------------------------------
...@@ -11,7 +31,7 @@ Added ...@@ -11,7 +31,7 @@ Added
* New :mod:`fsl.wrappers.fsl_sub` wrapper function for the ``fsl_sub`` * New :mod:`fsl.wrappers.fsl_sub` wrapper function for the ``fsl_sub``
command. command (!309).
Changed Changed
...@@ -20,19 +40,19 @@ Changed ...@@ -20,19 +40,19 @@ Changed
* Performance of the :mod:`.imglob`, :mod:`.imln`, :mod:`imtest`, :mod:`.imrm` * Performance of the :mod:`.imglob`, :mod:`.imln`, :mod:`imtest`, :mod:`.imrm`
and :mod:`.remove_ext` scripts has been improved, by re-organising them to and :mod:`.remove_ext` scripts has been improved, by re-organising them to
avoid unnecessary and expensive imports such as ``numpy``. avoid unnecessary and expensive imports such as ``numpy`` (!310).
* The default behaviour of the :func:`fsl.utils.run.run` function (and hence * The default behaviour of the :func:`fsl.utils.run.run` function (and hence
that of all :mod:`fsl.wrappers` functions) has been changed so that the that of all :mod:`fsl.wrappers` functions) has been changed so that the
standard output and error of the called command is now forwarded to the standard output and error of the called command is now forwarded to the
calling Python process, in addition to being returned from ``run`` as calling Python process, in addition to being returned from ``run`` as
strings. In other words, the default behaviour of ``run('cmd')``, is now strings. In other words, the default behaviour of ``run('cmd')``, is now
equivalent to ``run('cmd', log={"tee":True})``. The previous default equivalent to ``run('cmd', log={"tee":True})``. The previous default
behaviour can be achieved with ``run('cmd', log={"tee":False})``. behaviour can be achieved with ``run('cmd', log={"tee":False})`` (!309).
* The :func:`fsl.utils.run.run` and :func:`fsl.utils.run.runfsl` functions * The :func:`fsl.utils.run.run` and :func:`fsl.utils.run.runfsl` functions
(and hence all :mod:`fsl.wrappers` functions) have been modified to use (and hence all :mod:`fsl.wrappers` functions) have been modified to use
``fsl.wrappers.fsl_sub`` instead of ``fsl.utils.fslsub.submit``. This is an ``fsl.wrappers.fsl_sub`` instead of ``fsl.utils.fslsub.submit``. This is an
internal change which should not affect the usage of the ``run``, ``runfsl`` internal change which should not affect the usage of the ``run``, ``runfsl``
or wrapper functions. or wrapper functions (!309).
Deprecated Deprecated
...@@ -41,10 +61,10 @@ Deprecated ...@@ -41,10 +61,10 @@ Deprecated
* :class:`fsl.utils.fslsub.SubmitParams` and :func:`fsl.utils.fslsub.submit` * :class:`fsl.utils.fslsub.SubmitParams` and :func:`fsl.utils.fslsub.submit`
have been deprecated in favour of using the ``fsl.wrappers.fsl_sub`` wrapper have been deprecated in favour of using the ``fsl.wrappers.fsl_sub`` wrapper
function. function (!309).
* The :func:`fsl.utils.fslsub.info` function has been deprecated in favour of * The :func:`fsl.utils.fslsub.info` function has been deprecated in favour of
using the ``fsl_sub.report`` function, from the separate `fsl_sub using the ``fsl_sub.report`` function, from the separate `fsl_sub
<https://git.fmrib.ox.ac.uk/fsl/fsl_sub>`_ Python library. <https://git.fmrib.ox.ac.uk/fsl/fsl_sub>`_ Python library (!309).
3.6.4 (Tuesday 3rd August 2021) 3.6.4 (Tuesday 3rd August 2021)
......
...@@ -19,7 +19,7 @@ programming library written in Python. It is used by `FSLeyes ...@@ -19,7 +19,7 @@ programming library written in Python. It is used by `FSLeyes
<https://git.fmrib.ox.ac.uk/fsl/fsleyes/fsleyes/>`_. <https://git.fmrib.ox.ac.uk/fsl/fsleyes/fsleyes/>`_.
``fslpy`` is tested against Python versions 3.7, 3.8 and 3.9. ``fslpy`` is tested against Python versions 3.7, 3.8, 3.9, and 3.10.
Installation Installation
......
...@@ -1569,7 +1569,7 @@ def loadMetadata(image): ...@@ -1569,7 +1569,7 @@ def loadMetadata(image):
jsonfile = op.join(dirname, '{}.json'.format(basename)) jsonfile = op.join(dirname, '{}.json'.format(basename))
if op.exists(jsonfile): if op.exists(jsonfile):
with open(jsonfile, 'rt') as f: with open(jsonfile, 'rt') as f:
return json.load(f) return json.load(f, strict=False)
return {} return {}
......
...@@ -187,7 +187,7 @@ def isBIDSFile(filename, strict=True): ...@@ -187,7 +187,7 @@ def isBIDSFile(filename, strict=True):
def loadMetadataFile(filename): def loadMetadataFile(filename):
"""Load ``filename`` (assumed to be JSON), returning its contents. """ """Load ``filename`` (assumed to be JSON), returning its contents. """
with open(filename, 'rt') as f: with open(filename, 'rt') as f:
return json.load(f) return json.load(f, strict=False)
def loadMetadata(filename): def loadMetadata(filename):
......
...@@ -47,7 +47,7 @@ import re ...@@ -47,7 +47,7 @@ import re
import string import string
__version__ = '3.7.0' __version__ = '3.7.1'
"""Current version number, as a string. """ """Current version number, as a string. """
......
...@@ -1046,9 +1046,16 @@ def fileOrImage(*args, **kwargs): ...@@ -1046,9 +1046,16 @@ def fileOrImage(*args, **kwargs):
# in-memory image - we have # in-memory image - we have
# to save it out to a file # to save it out to a file
if infile is None: if infile is None or not op.exists(infile):
hd, infile = tempfile.mkstemp(fslimage.defaultExt()) hd, infile = tempfile.mkstemp(fslimage.defaultExt(),
dir=workdir)
os.close(hd) os.close(hd)
# Create a copy of the input image and
# save that, so the original doesn't
# get associated with the temp file
val = nib.nifti1.Nifti1Image(
np.asanyarray(val.dataobj), None, val.header)
val.to_filename(infile) val.to_filename(infile)
return infile return infile
...@@ -1110,7 +1117,7 @@ def fileOrArray(*args, **kwargs): ...@@ -1110,7 +1117,7 @@ def fileOrArray(*args, **kwargs):
infile = None infile = None
if isinstance(val, np.ndarray): if isinstance(val, np.ndarray):
hd, infile = tempfile.mkstemp('.txt') hd, infile = tempfile.mkstemp('.txt', dir=workdir)
os.close(hd) os.close(hd)
np.savetxt(infile, val, fmt='%0.18f') np.savetxt(infile, val, fmt='%0.18f')
...@@ -1176,6 +1183,7 @@ def fileOrText(*args, **kwargs): ...@@ -1176,6 +1183,7 @@ def fileOrText(*args, **kwargs):
if not isinstance(val, pathlib.Path): if not isinstance(val, pathlib.Path):
with tempfile.NamedTemporaryFile(mode='w', with tempfile.NamedTemporaryFile(mode='w',
suffix='.txt', suffix='.txt',
dir=workdir,
delete=False) as f: delete=False) as f:
f.write(val) f.write(val)
infile = f.name infile = f.name
......
...@@ -110,6 +110,21 @@ def test_loadMetadata(): ...@@ -110,6 +110,21 @@ def test_loadMetadata():
assert fslbids.loadMetadata(t1) == {**meta4, **meta2, **meta1} assert fslbids.loadMetadata(t1) == {**meta4, **meta2, **meta1}
def test_loadMetadata_control_characters():
dd = Path('dataset_description.json')
t1 = Path('sub-01/func/sub-01_task-stim_bold.nii.gz')
json1 = Path('sub-01/func/sub-01_task-stim_bold.json')
meta1 = {"a" : "1", "b" : "2\x19\x20"}
smeta1 = '{"a" : "1", "b" : "2\x19\x20"}'
with tempdir():
dd.touch()
Path(op.dirname(t1)).mkdir(parents=True)
t1.touch()
assert fslbids.loadMetadata(t1) == {}
json1.write_text(smeta1)
assert fslbids.loadMetadata(t1) == meta1
def test_loadMetadata_symlinked(): def test_loadMetadata_symlinked():
ddreal = Path('a') ddreal = Path('a')
......
...@@ -1484,6 +1484,21 @@ def test_loadMeta_badJSON(): ...@@ -1484,6 +1484,21 @@ def test_loadMeta_badJSON():
assert list(img.metaKeys()) == [] assert list(img.metaKeys()) == []
def test_loadMeta_control_characters():
with tempdir():
make_image('image.nii.gz')
with open('image.json', 'wt') as f:
f.write('{"a" : 1, "b" : "abc\x19\x1b"}')
# bad json should not cause failure
img = fslimage.Image('image.nii.gz', loadMeta=True)
assert list(img.metaKeys()) == ['a', 'b']
assert img.getMeta('a') == 1
assert img.getMeta('b') == 'abc\x19\x1b'
def test_loadMetadata(): def test_loadMetadata():
with tempdir(): with tempdir():
make_image('image.nii.gz') make_image('image.nii.gz')
......
...@@ -876,3 +876,61 @@ def test_cmdwrapper_cmdonly_assert(): ...@@ -876,3 +876,61 @@ def test_cmdwrapper_cmdonly_assert():
assert func()[0].strip() == 'hello' assert func()[0].strip() == 'hello'
os.remove('file') os.remove('file')
assert func(cmdonly=True) == ['echo', 'hello'] assert func(cmdonly=True) == ['echo', 'hello']
def test_fileOrArray_all_tempfiles_cleared():
tempfiles = []
@wutils.fileOrArray('in_', 'out')
def array(in_, out):
tempfiles.extend((in_, out))
i = np.loadtxt(in_)
i = i + 1
np.savetxt(out, i)
arr = np.array([[1, 2], [ 3, 4]])
arrout = array(arr, wutils.LOAD).out
assert np.all(np.isclose(arrout, arr + 1))
assert len(tempfiles) == 2
assert not any(op.exists(f) for f in tempfiles)
def test_fileOrImage_all_tempfiles_cleared():
tempfiles = []
@wutils.fileOrImage('in_', 'out')
def image(in_, out):
tempfiles.extend((in_, out))
i = nib.load(in_)
i = nib.Nifti1Image(i.get_fdata() + 1, np.eye(4))
i.to_filename(out)
arr = np.array([[1, 2], [ 3, 4]])
img = nib.nifti1.Nifti1Image(arr, np.eye(4))
imgout = image(img, wutils.LOAD).out
assert np.all(np.isclose(imgout.get_fdata(), img.get_fdata() + 1))
assert len(tempfiles) == 2
assert not any(op.exists(f) for f in tempfiles)
def test_fileOrText_all_tempfiles_cleared():
tempfiles = []
@wutils.fileOrText('in_', 'out')
def text(in_, out):
tempfiles.extend((in_, out))
with open(in_, 'rt') as inf, open(out, 'wt') as outf:
outf.write(inf.read())
outf.write('456')
txt = '123'
txtout = text( txt, wutils.LOAD).out
assert txtout == '123456'
assert len(tempfiles) == 2
assert not any(op.exists(f) for f in tempfiles)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment