|
1 | 1 | # emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
|
2 | 2 | # vi: set ft=python sts=4 ts=4 sw=4 et:
|
3 | 3 | """I/O test cases."""
|
| 4 | +from subprocess import check_call |
| 5 | +from io import StringIO |
| 6 | +import filecmp |
| 7 | +import shutil |
4 | 8 | import numpy as np
|
5 | 9 | import pytest
|
6 |
| -from io import StringIO |
7 | 10 |
|
8 |
| -import filecmp |
9 | 11 | import nibabel as nb
|
10 | 12 | from nibabel.eulerangles import euler2mat
|
11 | 13 | from nibabel.affines import from_matvec
|
@@ -433,3 +435,80 @@ def test_itk_h5(testdata_path):
|
433 | 435 | )
|
434 | 436 | def test_regressions(file_type, test_file, data_path):
|
435 | 437 | file_type.from_filename(data_path / "regressions" / test_file)
|
| 438 | + |
| 439 | + |
| 440 | +@pytest.mark.parametrize("parameters", [ |
| 441 | + {"x": 0.1, "y": 0.03, "z": 0.002}, |
| 442 | + {"x": 0.001, "y": 0.3, "z": 0.002}, |
| 443 | + {"x": 0.01, "y": 0.03, "z": 0.2}, |
| 444 | +]) |
| 445 | +@pytest.mark.parametrize("dir_x", (-1, 1)) |
| 446 | +@pytest.mark.parametrize("dir_y", (-1, 1)) |
| 447 | +@pytest.mark.parametrize("dir_z", (1, -1)) |
| 448 | +@pytest.mark.parametrize("swapaxes", [ |
| 449 | + None, (0, 1), (1, 2), (0, 2), |
| 450 | +]) |
| 451 | +def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y, dir_z): |
| 452 | + tmpdir.chdir() |
| 453 | + img = nb.load(testdata_path / "someones_anatomy.nii.gz") |
| 454 | + shape = np.array(img.shape[:3]) |
| 455 | + hdr = img.header.copy() |
| 456 | + aff = img.affine.copy() |
| 457 | + data = np.asanyarray(img.dataobj, dtype="uint8") |
| 458 | + |
| 459 | + directions = (dir_x, dir_y, dir_z) |
| 460 | + if directions != (1, 1, 1): |
| 461 | + last_ijk = np.hstack((shape - 1, 1.0)) |
| 462 | + last_xyz = aff @ last_ijk |
| 463 | + aff = np.diag((dir_x, dir_y, dir_z, 1)) @ aff |
| 464 | + |
| 465 | + for ax in range(3): |
| 466 | + if (directions[ax] == -1): |
| 467 | + aff[ax, 3] = last_xyz[ax] |
| 468 | + data = np.flip(data, ax) |
| 469 | + |
| 470 | + hdr.set_qform(aff, code=1) |
| 471 | + hdr.set_sform(aff, code=1) |
| 472 | + img.__class__(data, aff, hdr).to_filename("flips.nii.gz") |
| 473 | + |
| 474 | + if swapaxes is not None: |
| 475 | + data = np.swapaxes(data, swapaxes[0], swapaxes[1]) |
| 476 | + aff[reversed(swapaxes), :] = aff[(swapaxes), :] |
| 477 | + |
| 478 | + hdr.set_qform(aff, code=1) |
| 479 | + hdr.set_sform(aff, code=1) |
| 480 | + img.__class__(data, aff, hdr).to_filename("swaps.nii.gz") |
| 481 | + |
| 482 | + R = from_matvec(euler2mat(**parameters), [0.0, 0.0, 0.0]) |
| 483 | + |
| 484 | + img_center = aff @ np.hstack((shape * 0.5, 1.0)) |
| 485 | + R[:3, 3] += (img_center - (R @ aff @ np.hstack((shape * 0.5, 1.0))))[:3] |
| 486 | + newaff = R @ aff |
| 487 | + hdr.set_qform(newaff, code=1) |
| 488 | + hdr.set_sform(newaff, code=1) |
| 489 | + img = img.__class__(data, newaff, hdr) |
| 490 | + img.to_filename("oblique.nii.gz") |
| 491 | + |
| 492 | + # Run AFNI's 3dDeoblique |
| 493 | + if not shutil.which("3dWarp"): |
| 494 | + pytest.skip("Command 3dWarp not found on host") |
| 495 | + |
| 496 | + cmd = f"3dWarp -verb -deoblique -prefix {tmpdir}/deob.nii.gz {tmpdir}/oblique.nii.gz" |
| 497 | + |
| 498 | + # resample mask |
| 499 | + assert check_call([cmd], shell=True) == 0 |
| 500 | + afni_warpdrive_inv = afni._afni_header( |
| 501 | + nb.load("deob.nii.gz"), |
| 502 | + field="WARPDRIVE_MATVEC_INV_000000", |
| 503 | + to_ras=True, |
| 504 | + ) |
| 505 | + |
| 506 | + deobnii = nb.load("deob.nii.gz") |
| 507 | + |
| 508 | + # Confirm AFNI's rotation of axis is consistent with the one we introduced |
| 509 | + assert np.allclose(afni_warpdrive_inv[:3, :3], R[:3, :3]) |
| 510 | + |
| 511 | + # Check nitransforms' estimation of warpdrive with header |
| 512 | + nt_warpdrive_inv = afni._afni_warpdrive(newaff, img.shape, forward=False) |
| 513 | + import pdb;pdb.set_trace() |
| 514 | + assert np.allclose(afni_warpdrive_inv[:3, :3], nt_warpdrive_inv[:3, :3]) |
0 commit comments