Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • paulmc/fslpy
  • ndcn0236/fslpy
  • seanf/fslpy
3 results
Show changes
Showing
with 1180 additions and 277 deletions
......@@ -13,7 +13,7 @@ import contextlib
import textwrap as tw
import itertools as it
from .. import testdir
from fsl.tests import testdir
import fsl.utils.filetree as filetree
import fsl.utils.filetree.query as ftquery
......
......@@ -5,12 +5,13 @@
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import os.path as op
import textwrap
import math
import os.path as op
import textwrap as tw
import pytest
import tests
import fsl.tests as tests
import fsl.data.fixlabels as fixlabels
goodfiles = []
......@@ -109,6 +110,20 @@ None,
['Unclassified noise']],
[2, 5, 6, 7]))
goodfiles.append(("""
path/to/analysis.ica
[2, 3, 5, 7]
""",
'path/to/analysis.ica',
[['Signal'],
['Unclassified noise'],
['Unclassified noise'],
['Signal'],
['Unclassified noise'],
['Signal'],
['Unclassified noise']],
[2, 3, 5, 7]))
goodfiles.append(("""
2, 5, 6, 7
""",
......@@ -134,9 +149,59 @@ path/to/analysis.ica
['Signal', 'Blob']],
[1]))
# Missing component line
goodfiles.append(("""
path/to/analysis.ica
2, Unclassified noise, True
3, Signal, False
[2]
""",
'path/to/analysis.ica',
[['Unknown'],
['Unclassified noise'],
['Signal']],
[2]))
# Missing component line (again)
goodfiles.append(("""
path/to/analysis.ica
1, Unclassified noise, True
2, Unclassified noise, True
5, Signal, False
[1, 2]
""",
'path/to/analysis.ica',
[['Unclassified noise'],
['Unclassified noise'],
['Unknown'],
['Unknown'],
['Signal']],
[1, 2]))
# Classification probabilities
goodfiles.append(("""
path/to/analysis.ica
1, Unclassified noise, True, 0.2
2, Unclassified noise, True, 0.1
3, Signal, False, 0.8
[1, 2]
""",
'path/to/analysis.ica',
[['Unclassified noise'],
['Unclassified noise'],
['Signal']],
[1, 2],
[0.2, 0.1, 0.8]))
def test_loadLabelFile_good():
for filecontents, expMelDir, expLabels, expIdxs in goodfiles:
for test in goodfiles:
filecontents, expMelDir, expLabels, expIdxs = test[:4]
if len(test) > 4: probs = test[4]
else: probs = None
with tests.testdir() as testdir:
......@@ -161,6 +226,11 @@ def test_loadLabelFile_good():
for exp, res in zip(expLabels, resLabels):
assert exp == res
if probs is not None:
resMelDir, resLabels, resProbs = fixlabels.loadLabelFile(
fname, returnProbabilities=True)
assert resProbs == probs
......@@ -216,24 +286,6 @@ path/to/analysis.ica
[1]
""")
# Missing component line
badfiles.append("""
path/to/analysis.ica
2, Unclassified noise, True
3, Signal, False
[2]
""")
# Missing component line (again)
badfiles.append("""
path/to/analysis.ica
1, Unclassified noise, True
2, Unclassified noise, True
5, Signal, False
[1, 2]
""")
# Missing noisy list
badfiles.append("""
......@@ -289,7 +341,8 @@ def test_loadLabelFile_bad():
def test_loadLabelFile_customLabels():
included = [2, 3, 4, 5]
contents = '[{}]\n'.format([i + 1 for i in included])
contents = ','.join([str(i + 1) for i in included])
contents = f'[{contents}]\n'
defIncLabel = 'Unclassified noise'
defExcLabel = 'Signal'
......@@ -323,16 +376,74 @@ def test_loadLabelFile_customLabels():
assert ilbls[0] == excLabel
def test_saveLabelFile():
def test_loadLabelFile_probabilities():
def lists_equal(a, b):
if len(a) != len(b):
return False
for av, bv in zip(a, b):
if av == bv:
continue
if math.isnan(av) and math.isnan(bv):
continue
if math.isnan(av) and (not math.isnan(bv)):
return False
if (not math.isnan(av)) and math.isnan(bv):
return False
return True
nan = math.nan
testcases = [
("""
analysis.ica
1, Signal, False
2, Unclassified noise, True
3, Signal, False
[2]
""", [nan, nan, nan]),
("""
analysis.ica
1, Signal, False, 0.1
2, Unclassified noise, True, 0.2
3, Signal, False, 0.3
[2]
""", [0.1, 0.2, 0.3]),
("""
analysis.ica
1, Signal, False, 0.1
2, Unclassified noise, True
3, Signal, False, 0.3
[2]
""", [0.1, nan, 0.3]),
("""
[1, 2, 3]
""", [nan, nan, nan]),
]
for contents, expprobs in testcases:
with tests.testdir() as testdir:
fname = op.join(testdir, 'labels.txt')
with open(fname, 'wt') as f:
f.write(tw.dedent(contents).strip())
_, _, gotprobs = fixlabels.loadLabelFile(
fname, returnProbabilities=True)
assert lists_equal(gotprobs, expprobs)
def test_saveLabelFile():
labels = [['Label1', 'Label2', 'Label3'],
['Signal'],
['Noise'],
['Label1'],
['Unknown']]
expected = textwrap.dedent("""
expected = tw.dedent("""
1, Label1, Label2, Label3, True
2, Signal, False
3, Noise, True
......@@ -364,7 +475,7 @@ def test_saveLabelFile():
# Custom signal labels
sigLabels = ['Label1']
exp = textwrap.dedent("""
exp = tw.dedent("""
.
1, Label1, Label2, Label3, False
2, Signal, True
......@@ -377,3 +488,31 @@ def test_saveLabelFile():
fixlabels.saveLabelFile(labels, fname, signalLabels=sigLabels)
with open(fname, 'rt') as f:
assert f.read().strip() == exp
def test_saveLabelFile_probabilities():
labels = [['Label1', 'Label2', 'Label3'],
['Signal'],
['Noise'],
['Label1'],
['Unknown']]
probs = [0.1, 0.2, 0.3, 0.4, 0.5]
expected = tw.dedent("""
1, Label1, Label2, Label3, True, 0.100000
2, Signal, False, 0.200000
3, Noise, True, 0.300000
4, Label1, True, 0.400000
5, Unknown, False, 0.500000
[1, 3, 4]
""").strip()
with tests.testdir() as testdir:
fname = op.join(testdir, 'fname.txt')
exp = '.\n{}'.format(expected)
fixlabels.saveLabelFile(labels, fname, probabilities=probs)
with open(fname, 'rt') as f:
got = f.read().strip()
assert got == exp
......@@ -16,11 +16,12 @@ import nibabel.freesurfer as nibfs
import pytest
import fsl.version as fslver
import fsl.data.freesurfer as fslfs
from .test_mesh import (CUBE_VERTICES, CUBE_TRIANGLES_CCW)
from fsl.tests.test_mesh import (CUBE_VERTICES, CUBE_TRIANGLES_CCW)
from . import tempdir, touch
from fsl.tests import tempdir, touch
def gen_freesurfer_geometry(fname, verts, tris):
......@@ -174,11 +175,10 @@ def test_loadVertexData_mgh():
def test_loadVertexData_annot():
import nibabel.info as nibinfo
nibver = tuple(fslver.parseVersionString(nib.__version__))
# assume nibabel 2.*
# nibabel 2.2.1 is broken w.r.t. .annot files.
if nibinfo._version_minor == 2 and nibinfo._version_micro <= 1:
if (2, 2, 0) <= nibver <= (2, 2, 1):
return
verts = np.array(CUBE_VERTICES)
......
......@@ -26,10 +26,10 @@ import fsl.data.dtifit as dtifit
import fsl.data.melodicanalysis as melanalysis
import fsl.data.featanalysis as featanalysis
from . import (touch,
make_mock_feat_analysis,
make_mock_melodic_analysis,
make_mock_dtifit_analysis)
from fsl.tests import (touch,
make_mock_feat_analysis,
make_mock_melodic_analysis,
make_mock_dtifit_analysis)
def test_guessType():
......
......@@ -5,19 +5,20 @@
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
from __future__ import print_function
import os
import os.path as op
import shutil
import pathlib
import tempfile
from unittest import mock
import pytest
import fsl.utils.path as fslpath
import fsl.data.image as fslimage
from . import testdir
from fsl.tests import testdir
def make_dummy_file(path):
......@@ -149,7 +150,8 @@ def test_hasExt():
]
for path, aexts, expected in tests:
assert fslpath.hasExt(path, aexts) == expected
assert fslpath.hasExt(path, aexts) == expected
assert fslpath.hasExt(pathlib.Path(path), aexts) == expected
def test_addExt_imageFiles_mustExist_shouldPass():
......@@ -247,18 +249,15 @@ def test_addExt_imageFiles_mustExist_shouldPass():
for f in files_to_create:
make_dummy_image_file(op.join(workdir, f))
print('files_to_create: ', files_to_create)
print('workdir: ', os.listdir(workdir))
print('prefix: ', prefix)
print('expected: ', expected)
result = fslpath.addExt(op.join(workdir, prefix),
allowedExts,
mustExist=True,
fileGroups=groups)
print('result: ', result)
result = fslpath.addExt(op.join(workdir, prefix),
allowedExts,
mustExist=True,
fileGroups=groups)
assert result == op.join(workdir, expected)
result = fslpath.addExt(pathlib.Path(op.join(workdir, prefix)),
allowedExts,
mustExist=True,
fileGroups=groups)
assert result == op.join(workdir, expected)
cleardir(workdir)
......@@ -335,20 +334,15 @@ def test_addExt_otherFiles_mustExist_shouldPass():
for f in files_to_create:
make_dummy_file(op.join(workdir, f))
print('files_to_create: ', files_to_create)
print('prefix: ', prefix)
print('allowedExts: ', allowedExts)
print('fileGroups: ', fileGroups)
print('workdir: ', os.listdir(workdir))
print('expected: ', expected)
result = fslpath.addExt(op.join(workdir, prefix),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
print('result: ', result)
result = fslpath.addExt(op.join(workdir, prefix),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
assert result == op.join(workdir, expected)
result = fslpath.addExt(pathlib.Path(op.join(workdir, prefix)),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
assert result == op.join(workdir, expected)
cleardir(workdir)
......@@ -421,18 +415,16 @@ def test_addExt_imageFiles_mustExist_shouldFail():
for f in files_to_create:
make_dummy_file(op.join(workdir, f))
print('files_to_create: ', files_to_create)
print('prefix: ', prefix)
print('workdir: ', os.listdir(workdir))
with pytest.raises(fslpath.PathError):
result = fslpath.addExt(op.join(workdir, prefix),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
print('result: ', result)
fslpath.addExt(op.join(workdir, prefix),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
with pytest.raises(fslpath.PathError):
fslpath.addExt(pathlib.Path(op.join(workdir, prefix)),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
finally:
shutil.rmtree(workdir)
......@@ -480,23 +472,19 @@ def test_addExt_otherFiles_mustExist_shouldFail():
for f in files_to_create:
make_dummy_file(op.join(workdir, f))
print('files_to_create: ', files_to_create)
print('prefix: ', prefix)
print('workdir: ', os.listdir(workdir))
with pytest.raises(fslpath.PathError):
result = fslpath.addExt(op.join(workdir, prefix),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
print('result: ', result)
fslpath.addExt(op.join(workdir, prefix),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
with pytest.raises(fslpath.PathError):
fslpath.addExt(pathlib.Path(op.join(workdir, prefix)),
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
finally:
shutil.rmtree(workdir)
pass
def test_addExt_noExist():
......@@ -543,11 +531,14 @@ def test_addExt_noExist():
]
for prefix, defaultExt, allowedExts, expected in tests:
assert fslpath.addExt(prefix,
allowedExts,
defaultExt=defaultExt,
mustExist=False) == expected
assert fslpath.addExt(pathlib.Path(prefix),
allowedExts,
defaultExt=defaultExt,
mustExist=False) == expected
def test_addExt_unambiguous():
......@@ -581,14 +572,19 @@ def test_addExt_unambiguous():
expected = expected.split()
with testdir(create) as td:
result = fslpath.addExt(prefix,
allowedExts=exts,
fileGroups=groups,
defaultExt=defaultExt,
unambiguous=False)
assert sorted(expected) == sorted(result)
result = fslpath.addExt(pathlib.Path(prefix),
allowedExts=exts,
fileGroups=groups,
defaultExt=defaultExt,
unambiguous=False)
assert sorted(expected) == sorted(result)
def test_removeExt():
......@@ -621,7 +617,8 @@ def test_removeExt():
if len(test) == 2: allowed = allowedExts
else: allowed = test[2]
assert fslpath.removeExt(path, allowed) == output
assert fslpath.removeExt(path, allowed) == output
assert fslpath.removeExt(pathlib.Path(path), allowed) == output
def test_getExt():
......@@ -657,8 +654,8 @@ def test_getExt():
if len(test) == 2: allowed = allowedExts
else: allowed = test[2]
print(filename, '==', output)
assert fslpath.getExt(filename, allowed) == output
assert fslpath.getExt(filename, allowed) == output
assert fslpath.getExt(pathlib.Path(filename), allowed) == output
def test_splitExt():
......@@ -703,13 +700,14 @@ def test_splitExt():
for test in tests:
filename = test[0]
pfilename = pathlib.Path(filename)
outbase, outext = test[1]
if len(test) == 2: allowed = allowedExts
else: allowed = test[2]
print(filename, '==', (outbase, outext))
assert fslpath.splitExt(filename, allowed) == (outbase, outext)
assert fslpath.splitExt(filename, allowed) == (outbase, outext)
assert fslpath.splitExt(pfilename, allowed) == (outbase, outext)
# firstDot=True
tests = [
......@@ -720,7 +718,9 @@ def test_splitExt():
]
for f, exp in tests:
assert fslpath.splitExt(f, firstDot=True) == exp
pf = pathlib.Path(f)
assert fslpath.splitExt(f, firstDot=True) == exp
assert fslpath.splitExt(pf, firstDot=True) == exp
def test_getFileGroup_imageFiles_shouldPass():
......@@ -797,11 +797,6 @@ def test_getFileGroup_imageFiles_shouldPass():
with open(op.join(workdir, fn), 'wt') as f:
f.write('{}\n'.format(fn))
print()
print('files_to_create: ', files_to_create)
print('path: ', path)
print('files_to_expect: ', files_to_expect)
fullPaths = fslpath.getFileGroup(
op.join(workdir, path),
allowedExts=allowedExts,
......@@ -917,13 +912,6 @@ def test_getFileGroup_otherFiles_shouldPass():
with open(op.join(workdir, fn), 'wt') as f:
f.write('{}\n'.format(fn))
print()
print('files_to_create: ', files_to_create)
print('path: ', path)
print('allowedExts: ', allowedExts)
print('fileGroups: ', fileGroups)
print('files_to_expect: ', files_to_expect)
fullPaths = fslpath.getFileGroup(
op.join(workdir, path),
allowedExts=allowedExts,
......@@ -1005,32 +993,22 @@ def test_getFileGroup_shouldFail():
with open(op.join(workdir, fn), 'wt') as f:
f.write('{}\n'.format(fn))
print()
print('files_to_create: ', files_to_create)
print('path: ', path)
print('allowedExts: ', allowedExts)
print('fileGroups: ', fileGroups)
with pytest.raises(fslpath.PathError):
fullPaths = fslpath.getFileGroup(
fslpath.getFileGroup(
op.join(workdir, path),
allowedExts=allowedExts,
fileGroups=fileGroups,
fullPaths=True,
unambiguous=unambiguous)
print('fullPaths: ', fullPaths)
with pytest.raises(fslpath.PathError):
exts = fslpath.getFileGroup(
fslpath.getFileGroup(
op.join(workdir, path),
allowedExts=allowedExts,
fileGroups=fileGroups,
fullPaths=False,
unambiguous=unambiguous)
print('exts: ', exts)
cleardir(workdir)
finally:
......@@ -1116,16 +1094,9 @@ def test_removeDuplicates_imageFiles_shouldPass():
paths = paths.split()
expected = expected.split()
print()
print('files_to_create: ', files_to_create)
print('paths: ', paths)
print('expected: ', expected)
paths = [op.join(workdir, p) for p in paths]
result = fslpath.removeDuplicates(paths, allowedExts, groups)
print('result: ', result)
assert result == [op.join(workdir, e) for e in expected]
cleardir(workdir)
......@@ -1195,20 +1166,10 @@ def test_removeDuplicates_otherFiles_shouldPass():
for f in files_to_create:
make_dummy_file(op.join(workdir, f))
print('files_to_create: {}'.format(files_to_create))
print('paths: {}'.format(paths))
print('allowedExts: {}'.format(allowedExts))
print('fileGroups: {}'.format(fileGroups))
print('workdir: {}'.format(os.listdir(workdir)))
print('expected: {}'.format(expected))
result = fslpath.removeDuplicates([op.join(workdir, p) for p in paths],
allowedExts=allowedExts,
fileGroups=fileGroups)
print('result: {}'.format(result))
assert result == [op.join(workdir, e) for e in expected]
cleardir(workdir)
......@@ -1255,17 +1216,10 @@ def test_removeDuplicates_shouldFail():
with open(op.join(workdir, fn), 'wt') as f:
f.write('{}\n'.format(fn))
print()
print('files_to_create: ', files_to_create)
print('path: ', path)
print('allowedExts: ', allowedExts)
print('fileGroups: ', fileGroups)
with pytest.raises(fslpath.PathError):
result = fslpath.removeDuplicates(path,
allowedExts=allowedExts,
fileGroups=fileGroups)
print('result: ', result)
fslpath.removeDuplicates(path,
allowedExts=allowedExts,
fileGroups=fileGroups)
finally:
shutil.rmtree(workdir)
......@@ -1395,3 +1349,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'
......@@ -13,16 +13,17 @@ import sys
import textwrap as tw
import contextlib
import argparse
import pytest
import fsl
from fsl.utils import fslsub
import fsl.version as fv
from fsl.utils import fslsub, run
from fsl.utils.tempdir import tempdir
from . import mockFSLDIR
from fsl.tests import mockFSLDIR
mock_fsl_sub = """
#!{}
#!/usr/bin/env python3
import random
import os
......@@ -62,8 +63,7 @@ with open('{{}}.o{{}}'.format(cmd, jobid), 'w') as stdout, \
print(str(jobid))
sys.exit(0)
""".format(sys.executable, op.dirname(fsl.__file__)).strip()
""".format(op.dirname(op.join(fv.__file__, '..'))).strip()
@contextlib.contextmanager
def fslsub_mockFSLDIR():
......@@ -101,7 +101,6 @@ def test_submit():
os.chmod(cmd, 0o755)
jid = fslsub.submit(cmd)
fslsub.wait(jid)
stdout, stderr = fslsub.output(jid)
assert stdout.strip() == 'standard output'
......@@ -174,19 +173,70 @@ def test_add_to_parser():
assert res_flags[idx + start_index] == part
def myfunc():
print('standard output')
print('standard error', file=sys.stderr)
def test_func_to_cmd():
with fslsub_mockFSLDIR(), tempdir():
cmd = fslsub.func_to_cmd(myfunc, (), {})
jid = fslsub.submit(cmd)
example_qstat_reply = """==============================================================
job_number: 9985061
exec_file: job_scripts/9985061
owner: user
sge_o_home: /home/fs0/user
sge_o_log_name: user
sge_o_shell: /bin/bash
sge_o_workdir: /home/fs0/user
account: sge
cwd: /home/fs0/user
mail_options: a
notify: FALSE
job_name: echo
jobshare: 0
hard_queue_list: long.q
restart: y
job_args: test
script_file: echo
binding: set linear:slots
job_type: binary,noshell
scheduling info: queue instance "<some queue>" dropped because it is temporarily not available
queue instance "<some queue>" dropped because it is disabled
==============================================================
job_number: 9985062
exec_file: job_scripts/9985062
owner: user
sge_o_home: /home/fs0/user
sge_o_log_name: user
sge_o_shell: /bin/bash
sge_o_workdir: /home/fs0/user
account: sge
cwd: /home/fs0/user
mail_options: a
notify: FALSE
job_name: echo
jobshare: 0
hard_queue_list: long.q
restart: y
job_args: test
script_file: echo
binding: set linear:slots
job_type: binary,noshell
scheduling info: queue instance "<some queue>" dropped because it is temporarily not available
queue instance "<some queue>" dropped because it is disabled
"""
fslsub.wait(jid)
stdout, stderr = fslsub.output(jid)
assert stdout.strip() == 'standard output'
assert stderr.strip() == 'standard error'
def test_info():
valid_job_ids = ['9985061', '9985062']
res = fslsub._parse_qstat(','.join(valid_job_ids), example_qstat_reply)
assert len(res) == 2
for job_id in valid_job_ids:
assert res[job_id] is not None
assert res[job_id]['account'] == 'sge'
assert res[job_id]['job_type'] == 'binary,noshell'
assert len(res[job_id]['scheduling info'].splitlines()) == 2
for line in res[job_id]['scheduling info'].splitlines():
assert line.startswith('queue instance ')
res2 = fslsub._parse_qstat(','.join(valid_job_ids + ['1']), example_qstat_reply)
assert len(res2) == 3
for job_id in valid_job_ids:
assert res[job_id] == res2[job_id]
assert res2['1'] is None
with pytest.raises(ValueError):
fslsub._parse_qstat(valid_job_ids[0], example_qstat_reply)
......@@ -16,7 +16,8 @@ import pytest
import fsl.data.gifti as gifti
from . import tempdir
from fsl.tests import tempdir
from fsl.tests import test_mesh
def test_GiftiMesh_create():
......@@ -288,12 +289,12 @@ TEST_VERTS = np.array([
[0, 0, 0],
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
[0, 0, 1]], dtype=np.float32)
TEST_IDXS = np.array([
[0, 1, 2],
[0, 3, 1],
[0, 2, 3],
[1, 3, 2]])
[1, 3, 2]], dtype=np.int32)
TEST_VERT_ARRAY = nib.gifti.GiftiDataArray(
TEST_VERTS, intent='NIFTI_INTENT_POINTSET')
......@@ -302,8 +303,8 @@ TEST_IDX_ARRAY = nib.gifti.GiftiDataArray(
def test_GiftiMesh_surface_and_data():
data1 = np.random.randint(0, 10, len(TEST_VERTS))
data2 = np.random.randint(0, 10, len(TEST_VERTS))
data1 = np.random.randint(0, 10, len(TEST_VERTS), dtype=np.int32)
data2 = np.random.randint(0, 10, len(TEST_VERTS), dtype=np.int32)
expdata = np.vstack([data1, data2]).T
verts = TEST_VERT_ARRAY
tris = TEST_IDX_ARRAY
......@@ -364,7 +365,6 @@ def test_GiftiMesh_multiple_vertices():
def test_GiftiMesh_needsFixing():
from . import test_mesh
verts = test_mesh.CUBE_VERTICES
idxs = test_mesh.CUBE_TRIANGLES_CW
......@@ -381,3 +381,21 @@ def test_GiftiMesh_needsFixing():
surf = gifti.GiftiMesh(fname, fixWinding=True)
assert np.all(np.isclose(surf.indices, idxs_fixed))
def test_loadGiftiMesh_onetriangle():
verts = np.array([[0, 0, 0], [1, 1, 1], [0, 1, 0]], dtype=np.float32)
idxs = np.array([[0, 1, 2]], dtype=np.int32)
verts = nib.gifti.GiftiDataArray(verts, intent='NIFTI_INTENT_POINTSET')
idxs = nib.gifti.GiftiDataArray(idxs, intent='NIFTI_INTENT_TRIANGLE')
gimg = nib.gifti.GiftiImage(darrays=[verts, idxs])
with tempdir():
fname = op.abspath('test.gii')
gimg.to_filename(fname)
gimg, tris, verts, _ = gifti.loadGiftiMesh('test.gii')
verts = verts[0]
assert verts.shape == (3, 3)
assert tris.shape == (1, 3)
......@@ -9,11 +9,10 @@ import gc
import time
import threading
import random
from six.moves import reload_module
import importlib
import pytest
import mock
from unittest import mock
import fsl.utils.idle as idle
from fsl.utils.platform import platform as fslplatform
......@@ -38,6 +37,11 @@ def _run_with_wx(func, *args, **kwargs):
if callAfterApp is not None:
callAfterApp()
# canHaveGui caches its return val,
# so clear it otherwise we may
# affect subsequent tests
idle._canHaveGui.cache_clear()
def wrap():
try:
......@@ -64,6 +68,8 @@ def _run_with_wx(func, *args, **kwargs):
idle.idleLoop.reset()
idle._canHaveGui.cache_clear()
if raised[0] and propagateRaise:
raise raised[0]
......@@ -413,16 +419,15 @@ def test_idle_alwaysQueue4():
import fsl.utils.platform
with mock.patch.dict('sys.modules', {'wx' : None}):
# idle uses the platform module to
# determine whether a GUI is available,
# so we have to reload it
reload_module(fsl.utils.platform)
# The idle._canHaveGui caches its result,
# so we need to invalidate it
idle._canHaveGui.cache_clear()
idle.idle(task, alwaysQueue=True)
with pytest.raises(ImportError):
import wx
reload_module(fsl.utils.platform)
importlib.reload(fsl.utils.platform)
assert called[0]
......@@ -600,6 +605,37 @@ def test_TaskThread_onFinish():
assert onFinishCalled[0]
def test_TaskThread_onError():
taskCalled = [False]
onFinishCalled = [False]
onErrorCalled = [False]
def task():
taskCalled[0] = True
raise Exception('Task error')
def onFinish():
onFinishCalled[0] = True
def onError(e):
onErrorCalled[0] = str(e)
tt = idle.TaskThread()
tt.start()
tt.enqueue(task, onFinish=onFinish, onError=onError)
time.sleep(0.5)
tt.stop()
tt.join()
assert taskCalled[0]
assert onErrorCalled[0] == 'Task error'
assert not onFinishCalled[0]
def test_TaskThread_isQueued():
called = [False]
......
......@@ -12,13 +12,15 @@ import json
import os.path as op
import itertools as it
from pathlib import Path
import pytest
import numpy as np
import numpy.linalg as npla
import nibabel as nib
from nibabel.spatialimages import ImageFileError
from nibabel.filebasedimages import ImageFileError
import fsl.data.constants as constants
import fsl.data.image as fslimage
......@@ -27,14 +29,10 @@ import fsl.transform.affine as affine
from fsl.utils.tempdir import tempdir
from . import make_random_image
from . import make_dummy_file
try:
from unittest import mock
except ImportError:
import mock
from fsl.tests import make_random_image
from fsl.tests import make_dummy_file
from unittest import mock
try:
import indexed_gzip as igzip
......@@ -91,14 +89,6 @@ def make_image(filename=None,
return img
# Need to test:
# - Create image from existing nibabel image
# - Create image from numpy array
# - calcRange
# - loadData
def test_load():
"""Create an Image from a file name. """
......@@ -148,6 +138,7 @@ def test_load():
# Not raising an error means the test passes
for fname in shouldPass:
fslimage.Image(op.join(testdir, fname))
fslimage.Image(Path(testdir) / fname)
# These should raise an error
for fname, exc in shouldRaise:
......@@ -155,6 +146,35 @@ def test_load():
fslimage.Image(op.join(testdir, fname))
def test_load_from_symlink():
# (actual image file, symlink to image file)
tests = [
('image.nii.gz', 'data'),
('a/b/image.nii.gz', 'data'),
('image.nii.gz', 'a/b/data'),
('image.nii.gz', 'a/b/data.bin'),
('image.nii.gz', 'a/b/data.nii'),
]
for imagefile, symlink in tests:
with tempdir() as td:
fdir = op.dirname(imagefile) or '.'
sdir = op.dirname(symlink) or '.'
os.makedirs(fdir, exist_ok=True)
os.makedirs(sdir, exist_ok=True)
make_random_image(imagefile)
os.symlink(op.relpath(imagefile, sdir), symlink)
i1 = fslimage.Image(symlink)
i2 = fslimage.Image(Path(symlink))
assert i1.dataSource == op.abspath(imagefile)
assert i2.dataSource == op.abspath(imagefile)
def test_create():
# Test creating:
......@@ -205,6 +225,44 @@ def test_create():
assert np.all(np.isclose(img.pixdim, (2, 3, 4)))
def test_create_niftiversion():
data = np.random.random((10, 10, 10))
with mock.patch.dict(os.environ, FSLOUTPUTTYPE='NIFTI_GZ'):
img = fslimage.Image(data)
assert img.niftiVersion == 1
img = fslimage.Image(data, version=1)
assert img.niftiVersion == 1
img = fslimage.Image(data, version=2)
assert img.niftiVersion == 2
with mock.patch.dict(os.environ, FSLOUTPUTTYPE='NIFTI2_GZ'):
img = fslimage.Image(data)
assert img.niftiVersion == 2
img = fslimage.Image(data, version=1)
assert img.niftiVersion == 1
img = fslimage.Image(data, version=2)
assert img.niftiVersion == 2
def test_name_dataSource():
with tempdir():
expName = 'image'
expDataSource = op.abspath('image.nii.gz')
make_image('image.nii.gz')
tests = ['image', 'image.nii.gz', op.abspath('image'),
op.abspath('image.nii.gz')]
tests = tests + [Path(t) for t in tests]
for t in tests:
i = fslimage.Image(t)
assert i.name == expName
assert i.dataSource == expDataSource
def test_bad_create():
class BadThing(object):
......@@ -246,6 +304,11 @@ def _test_Image_atts(imgtype):
allowedExts = fslimage.ALLOWED_EXTENSIONS
fileGroups = fslimage.FILE_GROUPS
typeMap = {np.uint8 : constants.NIFTI_DT_UINT8,
np.int16 : constants.NIFTI_DT_INT16,
np.int32 : constants.NIFTI_DT_INT32,
np.float32 : constants.NIFTI_DT_FLOAT32,
np.float64 : constants.NIFTI_DT_FLOAT64}
# (file, dims, pixdims, dtype)
dtypes = [np.uint8, np.int16, np.int32, np.float32, np.double]
......@@ -300,6 +363,7 @@ def _test_Image_atts(imgtype):
path = op.abspath(op.join(testdir, path))
i = fslimage.Image(path)
assert i.editable
assert not i.iscomplex
assert tuple(i.shape) == tuple(expdims)
assert tuple(i.data.shape) == tuple(expdims)
......@@ -307,14 +371,15 @@ def _test_Image_atts(imgtype):
assert tuple(i.nibImage.shape) == tuple(dims)
assert tuple(i.nibImage.header.get_zooms()) == tuple(pixdims)
assert i.nvals == 1
assert i.ndim == expndims
assert i.dtype == dtype
assert i.name == op.basename(path)
assert i.dataSource == fslpath.addExt(path,
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
assert i.nvals == 1
assert i.ndim == expndims
assert i.dtype == dtype
assert i.niftiDataType == typeMap[dtype]
assert i.name == op.basename(path)
assert i.dataSource == fslpath.addExt(path,
allowedExts=allowedExts,
mustExist=True,
fileGroups=fileGroups)
i = None
......@@ -493,17 +558,51 @@ def test_splitExt():
def test_defaultExt():
fslOutputTypes = ['NIFTI', 'NIFTI_PAIR', 'NIFTI_GZ']
exts = ['.nii', '.img', '.nii.gz']
fslOutputTypes = ['NIFTI', 'NIFTI_PAIR', 'NIFTI_GZ', 'NIFTI_PAIR_GZ',
'NIFTI2', 'NIFTI2_PAIR', 'NIFTI2_GZ', 'NIFTI2_PAIR_GZ']
exts = ['.nii', '.img', '.nii.gz', '.img.gz'] * 2
env = os.environ.copy()
env.pop('FSLOUTPUTTYPE', None)
with mock.patch('os.environ', env):
assert fslimage.defaultExt() == '.nii.gz'
for o, e in zip(fslOutputTypes, exts):
env['FSLOUTPUTTYPE'] = o
assert fslimage.defaultExt() == e
os.environ.pop('FSLOUTPUTTYPE', None)
assert fslimage.defaultExt() == '.nii.gz'
for o, e in zip(fslOutputTypes, exts):
def test_defaultImageType():
os.environ['FSLOUTPUTTYPE'] = o
fslOutputTypes = [None,
'NIFTI', 'NIFTI_PAIR', 'NIFTI_GZ', 'NIFTI_PAIR_GZ',
'NIFTI2', 'NIFTI2_PAIR', 'NIFTI2_GZ', 'NIFTI2_PAIR_GZ']
exts = ['.nii.gz'] + \
['.nii', '.img', '.nii.gz', '.img.gz'] * 2
assert fslimage.defaultExt() == e
env = os.environ.copy()
with tempdir(), mock.patch('os.environ', env):
for o, e in zip(fslOutputTypes, exts):
if o is None:
env.pop('FSLOUTPUTTYPE', None)
else:
env['FSLOUTPUTTYPE'] = o
if o is None or 'NIFTI2' not in o:
exptype = nib.Nifti1Image
else:
exptype = nib.Nifti2Image
data = np.random.randint(1, 10, (30, 30, 30), dtype=np.int32)
img = fslimage.Image(data)
assert type(img.nibImage) == exptype
img.save('image')
assert op.exists('image' + e)
def test_fixExt():
......@@ -815,12 +914,8 @@ def _test_Image_changeData(imgtype):
def onSaveState(*a):
notified['save'] = True
def onDataRange(*a):
notified['dataRange'] = True
img.register('name1', onData, 'data')
img.register('name2', onSaveState, 'saveState')
img.register('name3', onDataRange, 'dataRange')
# Calculate the actual data range
data = np.asanyarray(img.nibImage.dataobj)
......@@ -861,13 +956,11 @@ def _test_Image_changeData(imgtype):
img[minx, miny, minz] = newdmin
break
assert notified.get('data', False)
assert notified.get('dataRange', False)
assert notified.get('data', False)
assert np.isclose(img[minx, miny, minz], newdmin)
assert np.all(np.isclose(img.dataRange, (newdmin, dmax)))
notified.pop('data')
notified.pop('dataRange')
# random value above the data range,
# making sure not to overwrite the
......@@ -878,13 +971,10 @@ def _test_Image_changeData(imgtype):
img[maxx, maxy, maxz] = newdmax
break
assert notified.get('data', False)
assert notified.get('dataRange', False)
assert notified.get('data', False)
assert np.isclose(img[maxx, maxy, maxz], newdmax)
assert np.all(np.isclose(img.dataRange, (newdmin, newdmax)))
img.deregister('name1', 'data')
img.deregister('name2', 'data')
img.deregister('name3', 'data')
img = None
......@@ -965,11 +1055,11 @@ def _test_Image_5D(imgtype):
img = None
def test_Image_voxToScaledVox_analyze(): _test_Image_voxToScaledVox(0)
def test_Image_voxToScaledVox_nifti1(): _test_Image_voxToScaledVox(1)
def test_Image_voxToScaledVox_nifti2(): _test_Image_voxToScaledVox(2)
def test_Image_voxToFSL_analyze(): _test_Image_voxToFSL(0)
def test_Image_voxToFSL_nifti1(): _test_Image_voxToFSL(1)
def test_Image_voxToFSL_nifti2(): _test_Image_voxToFSL(2)
def _test_Image_voxToScaledVox(imgtype):
def _test_Image_voxToFSL(imgtype):
dims = [(10, 10, 10)]
pixdims = [(-1, 1, 1),
......@@ -998,8 +1088,8 @@ def _test_Image_voxToScaledVox(imgtype):
expected = expect(imgtype, dim, pixdim)
invexpected = npla.inv(expected)
assert np.all(np.isclose(expected, img.voxToScaledVoxMat))
assert np.all(np.isclose(invexpected, img.scaledVoxToVoxMat))
assert np.all(np.isclose(expected, img.getAffine('voxel', 'fsl')))
assert np.all(np.isclose(invexpected, img.getAffine('fsl', 'voxel')))
img = None
......@@ -1324,32 +1414,55 @@ def test_generateAffines():
v2w = affine.compose(np.random.random(3),
np.random.random(3),
np.random.random(3))
w2v = npla.inv(v2w)
shape = (10, 10, 10)
pixdim = (1, 1, 1)
got, isneuro = fslimage.Nifti.generateAffines(v2w, shape, pixdim)
w2v = npla.inv(v2w)
exp = {}
exp['voxel', 'scaled'] = np.eye(4)
exp['voxel', 'world'] = v2w
exp['world', 'voxel'] = npla.inv(v2w)
exp['world', 'scaled'] = npla.inv(v2w)
exp['scaled', 'voxel'] = np.eye(4)
exp['scaled', 'world'] = v2w
assert isneuro == (npla.det(v2w) > 0)
if not isneuro:
v2f = np.eye(4)
f2v = np.eye(4)
f2w = v2w
w2f = w2v
exp['voxel', 'fsl'] = np.eye(4)
exp['scaled', 'fsl'] = np.eye(4)
exp['world', 'fsl'] = npla.inv(v2w)
exp['fsl', 'voxel'] = np.eye(4)
exp['fsl', 'scaled'] = np.eye(4)
exp['fsl', 'world'] = v2w
else:
v2f = affine.scaleOffsetXform([-1, 1, 1], [9, 0, 0])
f2v = npla.inv(v2f)
f2w = affine.concat(v2w, f2v)
w2f = affine.concat(v2f, w2v)
assert np.all(np.isclose(v2w, got['voxel', 'world']))
assert np.all(np.isclose(w2v, got['world', 'voxel']))
assert np.all(np.isclose(v2f, got['voxel', 'fsl']))
assert np.all(np.isclose(f2v, got['fsl', 'voxel']))
assert np.all(np.isclose(f2w, got['fsl' , 'world']))
assert np.all(np.isclose(w2f, got['world', 'fsl']))
exp['voxel', 'fsl'] = v2f
exp['scaled', 'fsl'] = affine.concat(exp['voxel', 'fsl'],
exp['scaled', 'voxel'])
exp['world', 'fsl'] = affine.concat(exp['voxel', 'fsl'],
exp['world', 'voxel'])
exp['fsl', 'voxel'] = npla.inv(exp['voxel', 'fsl'])
exp['fsl', 'scaled'] = npla.inv(exp['scaled', 'fsl'])
exp['fsl', 'world'] = npla.inv(exp['world', 'fsl'])
spaces = ['voxel', 'fsl', 'scaled', 'world']
for from_, to in it.product(spaces, spaces):
if from_ == to: expxfm = np.eye(4)
else: expxfm = exp[from_, to]
gotxfm = got[from_, to]
assert np.all(np.isclose(gotxfm, expxfm))
def test_identifyAffine():
......@@ -1358,14 +1471,28 @@ def test_identifyAffine():
assert identify(None, None, 'ho', 'hum') == ('ho', 'hum')
xform = affine.compose(0.1 + 5 * np.random.random(3),
-10 + 20 * np.random.random(3),
-np.pi / 2 + np.pi * np.random.random(3))
img = fslimage.Image(make_random_image(None, xform=xform))
for from_, to in it.permutations(('voxel', 'fsl', 'world'), 2):
assert identify(img, img.getAffine(from_, to)) == (from_, to)
# Construct an affine which causes
# all coordinate systems to be different
xform = np.diag([-1, 1, 1, 1])
while npla.det(xform) <= 0:
scales = 0.1 + 5 * np.random.random(3)
offsets = -10 + 20 * np.random.random(3)
rotations = -np.pi / 2 + np.pi * np.random.random(3)
xform = affine.compose(scales, offsets, rotations)
img = fslimage.Image(make_random_image(None, pixdims=scales, xform=xform))
for from_, to in it.permutations(('voxel', 'scaled', 'fsl', 'world'), 2):
aff = img.getAffine(from_, to)
got = identify(img, aff)
# The fsl->scaled and scaled->fsl affines are
# equivalent, as they just encode an inversion
# along the first axis.
if sorted((from_, to)) == ['fsl', 'scaled']:
assert got in ((from_, to), (to, from_))
else:
assert got == (from_, to)
assert identify(img, img.getAffine('voxel', 'world'), from_='voxel') == ('voxel', 'world')
assert identify(img, img.getAffine('voxel', 'world'), to='world') == ('voxel', 'world')
......@@ -1432,6 +1559,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')
......@@ -1488,3 +1630,17 @@ def test_adjust():
imgb = affine.axisBounds(img.shape, img.voxToWorldMat)
adjb = affine.axisBounds(adj.shape, adj.voxToWorldMat)
assert np.all(np.isclose(imgb, adjb, rtol=1e-5, atol=1e-5))
def test_strval():
tests = [
(b'abcde\x13\xf7fgh\0', 'abcdefgh'),
(b'abcde\0fghij\0', 'abcde'),
]
with tempdir():
for aux_file, expect in tests:
i = make_image('image.nii', dims=(10, 10, 10), pixdims=(1, 1, 1))
i.header['aux_file'] = aux_file
i.to_filename('image.nii')
i = fslimage.Image('image.nii')
assert i.strval('aux_file') == expect
#!/usr/bin/env python
#
# Test/verify data access semantics through Image.__getitem__ and
# Image.__setitem__
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import pytest
import numpy as np
import nibabel as nib
import fsl.data.image as fslimage
from fsl.utils.tempdir import tempdir
def random_shape():
shape = []
ndims = np.random.randint(1, 6)
maxes = [100, 100, 100, 5, 5]
for dmax in maxes[:ndims]:
shape.append(np.random.randint(1, dmax + 1))
return shape
def random_data_from_slice(shape, slc, dtype=None):
data = (np.random.random(shape) * 50)[slc]
if dtype is not None: return data.astype(dtype)
else: return data
def random_slice(shape):
slc = []
for sz in shape:
if sz == 1:
lo, hi = 0, 1
else:
lo = np.random.randint(0, sz - 1)
hi = np.random.randint(lo + 1, sz)
slc.append(slice(lo, hi))
return tuple(slc)
def create_image(filename, shape, **kwargs):
data = np.random.random(shape)
nib.Nifti1Image(data, header=None, affine=np.eye(4))\
.to_filename(filename)
return fslimage.Image(filename, **kwargs), data
# Read only data access should defer entirely
# to nibabel, which should keep the image
# data on disk via either indexed_gzip (for
# .nii.gz), or memmap (for .nii)
def test_image_readonly_compressed():
_test_image_readonly('.nii.gz')
def test_image_readonly_uncompressed():
_test_image_readonly('.nii')
# suffix in ('.nii', '.nii.gz')
def _test_image_readonly(suffix):
filename = f'image{suffix}'
shape = random_shape()
with tempdir():
img, data = create_image(filename, shape)
for _ in range(50):
slc = random_slice(shape)
sample = img[slc]
assert np.all(sample == data[slc].reshape(sample.shape))
assert not img.nibImage.in_memory
# When image data is written, this causes the
# image data to be loaded into memory, with a
# reference/ cache maintained by the Image
# class. If access is entriely through the
# Image class, the nibabel image should never
# use its own cache. Whether the data is
# compressed/uncompressed is irrelevant.
def test_image_read_write_compressed():
_test_image_read_write('.nii.gz')
def test_image_read_write_uncompressed():
_test_image_read_write('.nii')
def _test_image_read_write(suffix):
filename = f'image{suffix}'
shape = random_shape()
with tempdir():
img, _ = create_image(filename, shape)
slc = random_slice(shape)
data = random_data_from_slice(shape, slc, img.dtype)
assert not img.inMemory
img[slc] = data
assert img.inMemory
assert not img.nibImage.in_memory
assert np.all(np.isclose(img[slc].reshape(data.shape), data))
# Custom data manager - Image class
# does not promise anything
class NoOpDataManager(fslimage.DataManager):
def __init__(self, shape):
self.__shape = shape
def copy(self, nibImage):
self.__shape = nibImage.shape
return self
def __getitem__(self, slc):
return random_data_from_slice(self.__shape, slc)
def __setitem__(self, slc, value):
pass
def test_image_read_write_datamanager():
filename = 'image.nii.gz'
shape = random_shape()
with tempdir():
dm = NoOpDataManager(shape)
img, _ = create_image(filename, shape, dataMgr=dm)
slc = random_slice(shape)
data = img[slc]
img[slc] = data
# true for this specific DataManager
assert img.editable
assert not img.inMemory
assert not img.nibImage.in_memory
def test_Image_without_nibabel_object():
filename = f'image.nii.gz'
shape = random_shape()
with tempdir():
dm = NoOpDataManager(shape)
img, _ = create_image(filename, shape)
hdr = img.header
# We must provide a header and a data manager
with pytest.raises(ValueError):
fslimage.Image()
with pytest.raises(ValueError):
fslimage.Image(header=hdr)
with pytest.raises(ValueError):
fslimage.Image(dataMgr=dm)
img = fslimage.Image(header=hdr, dataMgr=dm)
def test_3D_indexing(shape=None, img=None):
# Test that a 3D image looks like a 3D image
if shape is None:
shape = (21, 22, 23)
elif len(shape) < 3:
shape = tuple(list(shape) + [1] * (3 - len(shape)))
if img is None:
data = np.random.random(shape)
nibImg = nib.Nifti1Image(data, np.eye(4))
img = fslimage.Image(nibImg)
assert tuple(img[:] .shape) == tuple(shape)
assert tuple(img[:, :] .shape) == tuple(shape)
assert tuple(img[:, :, :].shape) == tuple(shape)
assert tuple(img[:, 0, 0].shape) == (shape[0], )
assert tuple(img[0, :, 0].shape) == (shape[1], )
assert tuple(img[0, 0, :].shape) == (shape[2], )
assert tuple(img[0, :, :].shape) == (shape[1], shape[2])
assert tuple(img[:, 0, :].shape) == (shape[0], shape[2])
assert tuple(img[:, :, 0].shape) == (shape[0], shape[1])
assert type(img[0, 0, 0]) == np.float64
mask1 = np.zeros(shape, dtype=bool)
mask1[0, 0, 0] = True
mask1[1, 0, 0] = True
assert tuple(img[mask1].shape) == (2, )
img[0, 0, 0] = 999
img[:, 0, 0] = [999] * shape[0]
img[0, :, 0] = [999] * shape[1]
img[0, 0, :] = [999] * shape[2]
img[:, 0, 0] = np.array([999] * shape[0])
img[0, :, 0] = np.array([999] * shape[1])
img[0, 0, :] = np.array([999] * shape[2])
img[0, :, :] = np.ones((shape[1], shape[2]))
img[:, 0, :] = np.ones((shape[0], shape[2]))
img[:, :, 0] = np.ones((shape[0], shape[1]))
img[0, :, :] = [[999] * shape[2]] * shape[1]
img[:, 0, :] = [[999] * shape[2]] * shape[0]
img[:, :, 0] = [[999] * shape[1]] * shape[0]
def test_3D_4D_indexing():
# Test indexing with a trailing fourth
# dimension of length 1 - it should
# look like a 3D image, but should
# still accept (valid) 4D slicing.
# __getitem__ and __setitem__ on
# - 3D index
# - 4D index
# - 3D boolean array
# - 4D boolean array
#
padShape = (21, 22, 23, 1)
shape = padShape[:3]
data = np.random.random(shape)
nibImg = nib.Nifti1Image(data, np.eye(4))
img = fslimage.Image(nibImg)
test_3D_indexing(shape, img)
assert tuple(img[:, :, :, :].shape) == tuple(shape)
assert tuple(img[:, 0, 0, 0].shape) == (shape[0], )
assert tuple(img[:, 0, 0, :].shape) == (shape[0], )
assert tuple(img[:, :, 0, 0].shape) == (shape[0], shape[1])
assert tuple(img[:, :, 0, :].shape) == (shape[0], shape[1])
assert type(img[0, 0, 0, 0]) == np.float64
assert type(img[0, 0, 0, :]) == np.float64
mask = np.zeros(padShape, dtype=bool)
mask[0, 0, 0, 0] = True
assert type(img[mask]) == np.ndarray
assert img[mask].shape == (1, )
def test_3D_len_one_indexing(shape=None, img=None):
# Testing a 3D image with a third
# dimension of length 1 - it should
# look like a 3D image, but should
# still accept (valid) 2D slicing.
if shape is None:
shape = (20, 20, 1)
elif len(shape) < 3:
shape = tuple(list(shape) + [1] * (3 - len(shape)))
if img is None:
data = np.random.random(shape)
nibImg = nib.Nifti1Image(data, np.eye(4))
img = fslimage.Image(nibImg)
test_3D_indexing(shape, img)
assert type(img[0, 0, :]) == np.ndarray
assert type(img[0, 0]) == np.ndarray
assert type(img[0, 0, 0]) == np.float64
mask = np.zeros(shape[:2], dtype=bool)
mask[0, 0] = True
assert type(img[mask]) == np.ndarray
assert img[mask].shape == (1, )
mask = np.zeros(shape, dtype=bool)
mask[0, 0, 0] = True
assert type(img[mask]) == np.ndarray
assert img[mask].shape == (1, )
def test_2D_indexing():
# Testing a 2D image - it should
# look just like a 3D image (the
# same as is tested above).
shape = (20, 20)
data = np.random.random(shape[:2])
nibImg = nib.Nifti1Image(data, np.eye(4))
img = fslimage.Image(nibImg)
test_3D_len_one_indexing(shape, img)
def test_1D_indexing():
# Testing a 1D image - it should
# look just like a 3D image (the
# same as is tested above).
shape = (20,)
data = np.random.random(shape)
nibImg = nib.Nifti1Image(data, np.eye(4))
img = fslimage.Image(nibImg)
test_3D_len_one_indexing(shape, img)
def test_4D_indexing(shape=None, img=None):
if shape is None:
shape = (20, 21, 22, 23)
if img is None:
data = np.random.random(shape)
nibImg = nib.Nifti1Image(data, affine=np.eye(4))
img = fslimage.Image(nibImg)
assert tuple(img[:] .shape) == tuple(shape)
assert tuple(img[:, :] .shape) == tuple(shape)
assert tuple(img[:, :, :] .shape) == tuple(shape)
assert tuple(img[:, :, :, :].shape) == tuple(shape)
assert tuple(img[:, 0, 0, 0].shape) == (shape[0], )
assert tuple(img[0, :, 0, 0].shape) == (shape[1], )
assert tuple(img[0, 0, :, 0].shape) == (shape[2], )
assert tuple(img[0, 0, 0, :].shape) == (shape[3], )
assert tuple(img[0, :, :, :].shape) == (shape[1], shape[2], shape[3])
assert tuple(img[:, 0, :, :].shape) == (shape[0], shape[2], shape[3])
assert tuple(img[:, :, 0, :].shape) == (shape[0], shape[1], shape[3])
assert tuple(img[:, :, :, 0].shape) == (shape[0], shape[1], shape[2])
assert type(img[0, 0, 0, 0]) == np.float64
mask1 = np.zeros(shape, dtype=bool)
mask1[0, 0, 0, 0] = True
mask1[1, 0, 0, 0] = True
assert tuple(img[mask1].shape) == (2, )
img[0, 0, 0, 0] = 999
img[:, 0, 0, 0] = [999] * shape[0]
img[0, :, 0, 0] = [999] * shape[1]
img[0, 0, :, 0] = [999] * shape[2]
img[0, 0, 0, :] = [999] * shape[3]
img[:, 0, 0, 0] = np.array([999] * shape[0])
img[0, :, 0, 0] = np.array([999] * shape[1])
img[0, 0, :, 0] = np.array([999] * shape[2])
img[0, 0, 0, :] = np.array([999] * shape[3])
img[0, :, :, :] = np.ones((shape[1], shape[2], shape[3]))
img[:, 0, :, :] = np.ones((shape[0], shape[2], shape[3]))
img[:, :, 0, :] = np.ones((shape[0], shape[1], shape[3]))
img[:, :, :, 0] = np.ones((shape[0], shape[1], shape[2]))
img[0, :, :, :] = [[[999] * shape[3]] * shape[2]] * shape[1]
img[:, 0, :, :] = [[[999] * shape[3]] * shape[2]] * shape[0]
img[:, :, 0, :] = [[[999] * shape[3]] * shape[1]] * shape[0]
img[:, :, :, 0] = [[[999] * shape[2]] * shape[1]] * shape[0]
def test_expectedShape():
tests = [
((slice(None), ), (10,),
(1, (10, ))),
((slice(None), slice(None)),
(10, 10), (2, (10, 10))),
((slice(None), slice(None), slice(None)),
(10, 10, 10), (3, (10, 10, 10))),
((slice(None), slice(None), slice(None)),
(10, 10, 10), (3, (10, 10, 10))),
((slice(None), slice(None), slice(None), slice(None)),
(10, 10, 10, 10), (4, (10, 10, 10, 10))),
((1, slice(None), slice(None)),
(10, 10, 10), (2, (10, 10))),
((slice(1, 3), slice(None), slice(None)),
(10, 10, 10), (3, (2, 10, 10))),
((slice(None), 1, slice(None)),
(10, 10, 10), (2, (10, 10))),
((slice(None), slice(1, 3), slice(None)),
(10, 10, 10), (3, (10, 2, 10))),
((slice(None), slice(None), 1),
(10, 10, 10), (2, (10, 10))),
((slice(None), slice(None), slice(1, 3), ),
(10, 10, 10), (3, (10, 10, 2))),
((slice(None), slice(None), slice(1, 20), ),
(10, 10, 10), (3, (10, 10, 9))),
]
for slc, shape, exp in tests:
explen, exp = exp
gotlen, got = fslimage.expectedShape(slc, shape)
assert explen == gotlen
assert tuple(exp) == tuple(got)
def test_edit_notify():
# fsl/fslpy!413
# Notifier listeners should be passed
# the _normalised_ slice object, with
# trailing dimensions of length 1
# removed
testShapes = [
(10, 10, 1),
(10, 10, 1, 1),
(10, 10, 10),
(10, 10, 10, 1),
(10, 10, 10, 1, 1),
(10, 10, 10, 10),
(10, 10, 10, 10, 1),
(10, 10, 10, 10, 1, 1)
]
def genSlice(shape):
slc = []
shape = fslimage.canonicalShape(shape)
for sz in shape:
if sz == 1:
slc.append(slice(None))
else:
start = np.random.randint(0, sz - 1)
end = np.random.randint(start + 1, sz + 1)
slc.append(slice(start, end))
return tuple(slc)
callbackValue = [None]
def imageEdited(i, topic, value):
callbackValue[0] = value
for shape in testShapes:
data = np.random.randint(1, 10, shape, dtype=np.int16)
image = fslimage.Image(data)
slc = genSlice(shape)
image.register('listener', imageEdited, 'data')
image[slc] = np.full(fslimage.expectedShape(slc, image.shape)[1], -1)
assert callbackValue[0] == slc
callbackValue[0] = None
......@@ -12,7 +12,7 @@ import fsl.data.image as fslimage
import fsl.transform.affine as affine
import fsl.utils.image.resample as resample
from . import make_random_image
from fsl.tests import make_random_image
def random_affine():
return affine.compose(
......@@ -75,7 +75,7 @@ def test_resample(seed):
(np.isclose(np.modf(origtestcoords)[0], 0.5)))
out = np.any(out, axis=1) | (resvals == 0)
origtestcoords = np.array(origtestcoords.round(), dtype=np.int)
origtestcoords = np.array(origtestcoords.round(), dtype=int)
origtestcoords = origtestcoords[~out, :]
restestcoords = restestcoords[ ~out, :]
......@@ -166,8 +166,8 @@ def test_resampleToPixdims():
img = fslimage.Image(make_random_image(dims=(10, 10, 10)))
imglo, imghi = affine.axisBounds(img.shape, img.voxToWorldMat)
oldpix = np.array(img.pixdim, dtype=np.float)
oldshape = np.array(img.shape, dtype=np.float)
oldpix = np.array(img.pixdim, dtype=float)
oldshape = np.array(img.shape, dtype=float)
for i, origin in it.product(range(25), ('centre', 'corner')):
......@@ -215,16 +215,16 @@ def test_resampleToReference2():
# More specific test - output
# data gets transformed correctly
# into reference space
img = np.zeros((5, 5, 5), dtype=np.float)
img = np.zeros((5, 5, 5), dtype=float)
img[1, 1, 1] = 1
img = fslimage.Image(img)
refv2w = affine.scaleOffsetXform([1, 1, 1], [-1, -1, -1])
ref = np.zeros((5, 5, 5), dtype=np.float)
ref = np.zeros((5, 5, 5), dtype=float)
ref = fslimage.Image(ref, xform=refv2w)
res = resample.resampleToReference(img, ref, order=0)
exp = np.zeros((5, 5, 5), dtype=np.float)
exp = np.zeros((5, 5, 5), dtype=float)
exp[2, 2, 2] = 1
assert np.all(np.isclose(res[0], exp))
......@@ -234,7 +234,7 @@ def test_resampleToReference3():
# Test resampling image to ref
# with mismatched dimensions
imgdata = np.random.randint(0, 65536, (5, 5, 5))
imgdata = np.random.randint(0, 65536, (5, 5, 5), dtype=np.int32)
img = fslimage.Image(imgdata, xform=affine.scaleOffsetXform(
(2, 2, 2), (0.5, 0.5, 0.5)))
......@@ -272,7 +272,7 @@ def test_resampleToReference4():
# will bring them into alignment
img2ref = affine.scaleOffsetXform([2, 2, 2], [10, 10, 10])
imgdata = np.random.randint(0, 65536, (5, 5, 5))
imgdata = np.random.randint(0, 65536, (5, 5, 5), dtype=np.int32)
refdata = np.zeros((5, 5, 5))
img = fslimage.Image(imgdata)
ref = fslimage.Image(refdata, xform=img2ref)
......
......@@ -46,7 +46,7 @@ def test_roi():
]
for inshape, bounds, outshape, offset in tests:
data = np.random.randint(1, 10, inshape)
data = np.random.randint(1, 10, inshape, dtype=np.int32)
image = fslimage.Image(data, xform=np.eye(4))
result = roi.roi(image, bounds)
......@@ -84,7 +84,7 @@ def test_roi():
# - not enough bounds
# - too many bounds
# - hi >= lo
data = np.random.randint(1, 10, (10, 10, 10))
data = np.random.randint(1, 10, (10, 10, 10), dtype=np.int32)
image = fslimage.Image(data, xform=np.eye(4))
with pytest.raises(ValueError): roi.roi(image, [(0, 10), (0, 10)])
with pytest.raises(ValueError): roi.roi(image, [(0, 10), (0, 10), (0, 10), (0, 10)])
......
......@@ -7,22 +7,26 @@
from __future__ import print_function
import gzip
import itertools as it
import os.path as op
import os
import shutil
import tempfile
from unittest import mock
import os.path as op
import os
import shutil
import tempfile
import numpy as np
import numpy as np
import nibabel as nib
import fsl.utils.imcp as imcp
import fsl.data.image as fslimage
import fsl.utils.imcp as imcp
import fsl.utils.tempdir as tempdir
import fsl.data.image as fslimage
from . import make_random_image
from . import make_dummy_file
from . import looks_like_image
from fsl.tests import (make_random_image,
make_dummy_file,
looks_like_image,
sha256)
real_print = print
......@@ -315,3 +319,60 @@ def test_imcp_shouldPass(move=False):
def test_immv_shouldPass():
test_imcp_shouldPass(move=True)
def test_imcp_data_unmodified():
"""Test that the data in an imcp'd image file is not modified. """
dtypes = [
np.int16,
np.int32,
np.float32,
np.float64]
slints = [(None, None), (1, 0), (3, 1.5)]
for dtype, (slope, inter) in it.product(dtypes, slints):
with tempdir.tempdir():
data = np.random.randint(1, 100, (10, 10, 10)).astype(dtype)
hdr = nib.Nifti1Header()
hdr.set_data_dtype(dtype)
hdr.set_data_shape((10, 10, 10))
hdr.set_slope_inter(slope, inter)
hdr.set_sform(np.eye(4))
# write header/data separately, as otherwise
# nibabel will automatically rescale the data
with open('image.nii', 'wb') as f:
hdr.write_to(f)
f.write(data.tobytes())
# Input/output formats the same,
# should induce a straight file copy
imcp.imcp('image.nii', 'copied.nii', useDefaultExt=False)
# uncompresed->compressed will cause imcp
# to load in the image, rather than doing a
# file copy
with mock.patch.dict(os.environ, FSLOUTPUTTYPE='NIFTI_GZ'):
imcp.imcp('image.nii', 'converted.nii.gz', useDefaultExt=True)
# copied files should be identical
assert sha256('image.nii') == sha256('copied.nii')
# Converted files should have the same
# data, slope, and intercept. Read result
# header/data separately to avoid nibabel
# auto-rescaling.
with gzip.open('converted.nii.gz', 'rb') as f:
gothdr = nib.Nifti1Header.from_fileobj(f)
databytes = f.read()
gotdata = np.frombuffer(databytes, dtype=dtype).reshape((10, 10, 10))
# Data should be binary identical
assert np.all(gotdata == data)
if slope is None: slope = 1
if inter is None: inter = 0
assert np.all(np.isclose(gothdr.get_slope_inter(), (slope, inter)))
File moved