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:
<<: *test_template
# test:3.10:
# stage: test
# image: pauldmccarthy/fsleyes-py310-wxpy4-gtk3
# <<: *test_template
test:build-pypi-dist:
stage: test
image: pauldmccarthy/fsleyes-py37-wxpy4-gtk3
......
......@@ -2,6 +2,26 @@ This document contains the ``fslpy`` release history in reverse chronological
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)
-------------------------------
......@@ -11,7 +31,7 @@ Added
* New :mod:`fsl.wrappers.fsl_sub` wrapper function for the ``fsl_sub``
command.
command (!309).
Changed
......@@ -20,19 +40,19 @@ Changed
* Performance of the :mod:`.imglob`, :mod:`.imln`, :mod:`imtest`, :mod:`.imrm`
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
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
calling Python process, in addition to being returned from ``run`` as
strings. In other words, the default behaviour of ``run('cmd')``, is now
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
(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
internal change which should not affect the usage of the ``run``, ``runfsl``
or wrapper functions.
or wrapper functions (!309).
Deprecated
......@@ -41,10 +61,10 @@ Deprecated
* :class:`fsl.utils.fslsub.SubmitParams` and :func:`fsl.utils.fslsub.submit`
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
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)
......
......@@ -19,7 +19,7 @@ programming library written in Python. It is used by `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
......
......@@ -1569,7 +1569,7 @@ def loadMetadata(image):
jsonfile = op.join(dirname, '{}.json'.format(basename))
if op.exists(jsonfile):
with open(jsonfile, 'rt') as f:
return json.load(f)
return json.load(f, strict=False)
return {}
......
......@@ -187,7 +187,7 @@ def isBIDSFile(filename, strict=True):
def loadMetadataFile(filename):
"""Load ``filename`` (assumed to be JSON), returning its contents. """
with open(filename, 'rt') as f:
return json.load(f)
return json.load(f, strict=False)
def loadMetadata(filename):
......
......@@ -47,7 +47,7 @@ import re
import string
__version__ = '3.7.0'
__version__ = '3.7.1'
"""Current version number, as a string. """
......
......@@ -1046,9 +1046,16 @@ def fileOrImage(*args, **kwargs):
# in-memory image - we have
# to save it out to a file
if infile is None:
hd, infile = tempfile.mkstemp(fslimage.defaultExt())
if infile is None or not op.exists(infile):
hd, infile = tempfile.mkstemp(fslimage.defaultExt(),
dir=workdir)
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)
return infile
......@@ -1110,7 +1117,7 @@ def fileOrArray(*args, **kwargs):
infile = None
if isinstance(val, np.ndarray):
hd, infile = tempfile.mkstemp('.txt')
hd, infile = tempfile.mkstemp('.txt', dir=workdir)
os.close(hd)
np.savetxt(infile, val, fmt='%0.18f')
......@@ -1176,6 +1183,7 @@ def fileOrText(*args, **kwargs):
if not isinstance(val, pathlib.Path):
with tempfile.NamedTemporaryFile(mode='w',
suffix='.txt',
dir=workdir,
delete=False) as f:
f.write(val)
infile = f.name
......
......@@ -110,6 +110,21 @@ def test_loadMetadata():
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():
ddreal = Path('a')
......
......@@ -1484,6 +1484,21 @@ def test_loadMeta_badJSON():
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():
with tempdir():
make_image('image.nii.gz')
......
......@@ -876,3 +876,61 @@ def test_cmdwrapper_cmdonly_assert():
assert func()[0].strip() == 'hello'
os.remove('file')
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