def par2referenceFSL (par_file, session, par_cycle): rigids_euler = np.array(pd.read_csv(par_file, header=None, delimiter='\s+')) number_of_cycles = rigids_euler.shape[0] rigids = np.empty((number_of_cycles, 4, 4)) for i in range(number_of_cycles): mat = euler2mat( -rigids_euler[i,0], # gieren / yaw / z -rigids_euler[i,1], # rollen / roll / y rigids_euler[i,2]) # nicken / pitch / x vec = rigids_euler[i,3:] rigids[i] = from_matvec( matrix=mat, vector=np.array((-vec[1], vec[0], -vec[2]))) reference_maps = ReferenceMaps(session.name) reference_maps.temporal_resolution = session.temporal_resolution reference_maps.slice_timing = session.slice_timing reference_maps.set_acquisition_maps(Affines(rigids)) reference_maps.shape = (session.numob, session.shape[session.ep]) # The realignment parameters (Euler angles and transformations) # saved by McFLIRT are given with respect to the centre of mass of # the reference scan cycle (Why do you have to make *everything* so # complicated, FSL?!). We need to apply a translation from the # centre of mass of the reference cycle to the coordinate system of # the session. n,x,y,z = session.data.shape indices = ((slice(0,x), slice(0,y), slice(0,z))) lattice = session.reference.apply_to_indices(indices) lattice = np.moveaxis(lattice, -1 ,0) com = (session.raw[par_cycle] * lattice).sum(axis=(1,2,3)) / session.raw[par_cycle].sum() translation_from_com = Affine(from_matvec(np.eye(3), -com)) reference_maps.reset_reference_space(x=translation_from_com) return reference_maps
def test_adapt_affine(): # Adapt affine to missing or extra input dimensions aff_3d = from_matvec(np.arange(9).reshape((3, 3)), [11, 12, 13]) # For 4x4 affine, 3D image, no-op assert_array_equal(adapt_affine(aff_3d, 3), aff_3d) # For 4x4 affine, 4D image, add extra identity dimension assert_array_equal(adapt_affine(aff_3d, 4), [[ 0, 1, 2, 0, 11], [ 3, 4, 5, 0, 12], [ 6, 7, 8, 0, 13], [ 0, 0, 0, 1, 0], [ 0, 0, 0, 0, 1]]) # For 5x5 affine, 4D image, identity aff_4d = from_matvec(np.arange(16).reshape((4, 4)), [11, 12, 13, 14]) assert_array_equal(adapt_affine(aff_4d, 4), aff_4d) # For 4x4 affine, 2D image, dropped column assert_array_equal(adapt_affine(aff_3d, 2), [[ 0, 1, 11], [ 3, 4, 12], [ 6, 7, 13], [ 0, 0, 1]]) # For 4x4 affine, 1D image, 2 dropped columnn assert_array_equal(adapt_affine(aff_3d, 1), [[ 0, 11], [ 3, 12], [ 6, 13], [ 0, 1]]) # For 3x3 affine, 2D image, identity aff_2d = from_matvec(np.arange(4).reshape((2, 2)), [11, 12]) assert_array_equal(adapt_affine(aff_2d, 2), aff_2d)
def test_load_spaces(): # Test spaces get read correctly shape = np.array((6, 5, 4, 3, 2)) zooms = np.array((2, 3, 4, 5, 6)) data = np.random.normal(size=shape) # Default with no affine in header, or in image ni_img = nib.Nifti1Image(data, None) hdr = get_header(ni_img) hdr.set_zooms(zooms) # Expected affine is from the pixdims and the center of the image. Default # is also flipped X. offsets = (1 - shape[:3]) / 2. * zooms[:3] * (-1, 1, 1) exp_aff = from_matvec(np.diag([-2, 3, 4, 5, 6]), list(offsets) + [0, 0]) in_cs = CS('ijktu', name='voxels') exp_cmap = AT(in_cs, unknown_csm(5), exp_aff) assert_equal(nifti2nipy(ni_img).coordmap, exp_cmap) an_aff = from_matvec(np.diag([1.1, 2.2, 3.3]), [10, 11, 12]) exp_aff = from_matvec(np.diag([1.1, 2.2, 3.3, 5, 6]), [10, 11, 12, 0, 0]) for label, csm in (('scanner', scanner_csm), ('aligned', aligned_csm), ('talairach', talairach_csm), ('mni', mni_csm)): hdr.set_sform(an_aff, label) assert_equal(nifti2nipy(ni_img).coordmap, AT(in_cs, csm(5), exp_aff))
def test_mm_scaling(): # Test the micron and meter scale the affine right data = np.random.normal(size=list(range(4))) xyz_aff = from_matvec(np.diag([2, 3, 4]), [11, 12, 13]) exp_aff = from_matvec(np.diag([2, 3, 4, 1]), [11, 12, 13, 0]) in_cs = CS('ijkt', name='voxels') out_cs = aligned_csm(4) # No space scaling ni_img = nib.Nifti1Image(data, xyz_aff) hdr = get_header(ni_img) assert_equal(hdr.get_xyzt_units(), ('unknown', 'unknown')) assert_equal(nifti2nipy(ni_img).coordmap, AT(in_cs, out_cs, exp_aff)) # mm is assumed hdr.set_xyzt_units('mm') assert_equal(nifti2nipy(ni_img).coordmap, AT(in_cs, out_cs, exp_aff)) # microns ! hdr.set_xyzt_units('micron') scaler = np.diag([1 / 1000., 1 / 1000., 1 / 1000., 1, 1]) assert_equal(nifti2nipy(ni_img).coordmap, AT(in_cs, out_cs, np.dot(scaler, exp_aff))) # mm again ! This test implicitly asserts that the nifti image affine is # not being changed by the conversion routine, otherwise we'd pick up the # microns scaling above. hdr.set_xyzt_units('mm') assert_equal(nifti2nipy(ni_img).coordmap, AT(in_cs, out_cs, exp_aff)) # meters ! hdr.set_xyzt_units('meter') scaler = np.diag([1000., 1000., 1000., 1, 1]) assert_equal(nifti2nipy(ni_img).coordmap, AT(in_cs, out_cs, np.dot(scaler, exp_aff)))
def to_ras(self, moving=None, reference=None): """Return a nitransforms internal RAS+ matrix.""" sa = self.structarr matrix = sa['parameters'] offset = sa['offset'] c_neg = from_matvec(np.eye(3), offset * -1.0) c_pos = from_matvec(np.eye(3), offset) return LPS.dot(c_pos.dot(matrix.dot(c_neg.dot(LPS))))
def test_save_toffset(): # Check toffset only gets set for time shape = (2, 3, 4, 5, 6, 7) data = np.random.normal(size = shape) aff = from_matvec(np.diag([2., 3, 4, 5, 6, 7]), [11, 12, 13, 14, 15, 16]) xyz_names = talairach_csm(3).coord_names in_cs = CS('ijklmn') for t_name in 't', 'time': cmap = AT(in_cs, CS(xyz_names + (t_name, 'q', 'r')), aff) ni_img = nipy2nifti(Image(data, cmap)) assert_equal(get_header(ni_img)['toffset'], 14) for time_like in ('hz', 'ppm', 'rads'): cmap = AT(in_cs, CS(xyz_names + (time_like, 'q', 'r')), aff) ni_img = nipy2nifti(Image(data, cmap)) assert_equal(get_header(ni_img)['toffset'], 0) # Check that non-matching time causes a nifti error when toffset !=0 shape_shifted = (2, 3, 4, 6, 5, 7) for t_name in 't', 'time': # No toffset, this is OK cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), np.diag([3, 4, 5, 6, 0, 7, 1])) assert_equal(nipy2nifti(Image(data, cmap)).shape, shape_shifted) # toffset with 0 on TR (time) diagonal aff_z1 = from_matvec(np.diag([2., 3, 4, 5, 0, 7]), [11, 12, 13, 14, 15, 16]) cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff_z1) # Default is to fix the zero assert_equal(nipy2nifti(Image(data, cmap)).shape, shape_shifted) assert_equal(nipy2nifti(Image(data, cmap), fix0=True).shape, shape_shifted) # Unless fix0 is False assert_raises(NiftiError, nipy2nifti, Image(data, cmap), fix0=False) # Fix doesn't work if there is more than one zero row and column aff_z2 = from_matvec(np.diag([2., 3, 4, 0, 0, 7]), [11, 12, 13, 14, 15, 16]) cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff_z2) assert_raises(NiftiError, nipy2nifti, Image(data, cmap), fix0=True) # zeros on the diagonal are not a problem for non-time, with toffset, # because we don't need to set the 'time' part of the translation vector, # and therefore we don't need to know which *output axis* is time-like for t_name in 'hz', 'ppm', 'rads': cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff_z1) assert_equal(nipy2nifti(Image(data, cmap), fix0=False).shape, shape_shifted) cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff_z2) assert_equal(nipy2nifti(Image(data, cmap), fix0=False).shape, shape_shifted)
def test_affines_save(image_orientation): """Check implementation of exporting affines to formats.""" # Generate test transform img = loadimg(SOMEONES_ANATOMY) imgaff = img.affine if image_orientation == 'LAS': newaff = imgaff.copy() newaff[0, 0] *= -1.0 newaff[0, 3] = imgaff.dot(np.hstack( (np.array(img.shape[:3]) - 1, 1.0)))[0] img = Nifti1Image(np.flip(img.get_fdata(), 0), newaff, img.header) elif image_orientation == 'LPS': newaff = imgaff.copy() newaff[0, 0] *= -1.0 newaff[1, 1] *= -1.0 newaff[:2, 3] = imgaff.dot(np.hstack( (np.array(img.shape[:3]) - 1, 1.0)))[:2] img = Nifti1Image(np.flip(np.flip(img.get_fdata(), 0), 1), newaff, img.header) elif image_orientation == 'oblique': A = shape_zoom_affine(img.shape, img.header.get_zooms(), x_flip=False) R = from_matvec(euler2mat(x=0.09, y=0.001, z=0.001)) newaff = R.dot(A) img = Nifti1Image(img.get_fdata(), newaff, img.header) img.header.set_qform(newaff, 1) img.header.set_sform(newaff, 1) T = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0]) xfm = nbl.Affine(T) xfm.reference = img itk = nbl.load(os.path.join(data_path, 'affine-%s-itk.tfm' % image_orientation), fmt='itk') fsl = np.loadtxt( os.path.join(data_path, 'affine-%s.fsl' % image_orientation)) afni = np.loadtxt( os.path.join(data_path, 'affine-%s.afni' % image_orientation)) with InTemporaryDirectory(): xfm.to_filename('M.tfm', fmt='itk') xfm.to_filename('M.fsl', fmt='fsl') xfm.to_filename('M.afni', fmt='afni') nb_itk = nbl.load('M.tfm', fmt='itk') nb_fsl = np.loadtxt('M.fsl') nb_afni = np.loadtxt('M.afni') assert_equal(itk, nb_itk) assert_almost_equal(fsl, nb_fsl) assert_almost_equal(afni, nb_afni) # Create version not aligned to canonical
def test_save_toffset(): # Check toffset only gets set for time shape = (2, 3, 4, 5, 6, 7) data = np.random.normal(size = shape) aff = from_matvec(np.diag([2., 3, 4, 5, 6, 7]), [11, 12, 13, 14, 15, 16]) xyz_names = talairach_csm(3).coord_names in_cs = CS('ijklmn') for t_name in 't', 'time': cmap = AT(in_cs, CS(xyz_names + (t_name, 'q', 'r')), aff) ni_img = nipy2nifti(Image(data, cmap)) assert_equal(ni_img.get_header()['toffset'], 14) for time_like in ('hz', 'ppm', 'rads'): cmap = AT(in_cs, CS(xyz_names + (time_like, 'q', 'r')), aff) ni_img = nipy2nifti(Image(data, cmap)) assert_equal(ni_img.get_header()['toffset'], 0) # Check that non-matching time causes a nifti error when toffset !=0 shape_shifted = (2, 3, 4, 6, 5, 7) for t_name in 't', 'time': # No toffset, this is OK cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), np.diag([3, 4, 5, 6, 0, 7, 1])) assert_equal(nipy2nifti(Image(data, cmap)).shape, shape_shifted) # toffset, non-matching error aff_z1 = from_matvec(np.diag([2., 3, 4, 5, 0, 7]), [11, 12, 13, 14, 15, 16]) cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff_z1) assert_raises(NiftiError, nipy2nifti, Image(data, cmap)) # Unless fix0 set assert_equal(nipy2nifti(Image(data, cmap), fix0=True).shape, shape_shifted) # Even this doesn't work if there is more than one zero row and column aff_z2 = from_matvec(np.diag([2., 3, 4, 0, 0, 7]), [11, 12, 13, 14, 15, 16]) cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff_z2) assert_raises(NiftiError, nipy2nifti, Image(data, cmap), fix0=True) # No problem for non-time for t_name in 'hz', 'ppm', 'rads': cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff) assert_equal(nipy2nifti(Image(data, cmap), fix0=True).shape, shape_shifted)
def test_LinearList_common(tmpdir, data_path, sw, image_orientation, get_testdata): tmpdir.chdir() angles = np.random.uniform(low=-3.14, high=3.14, size=(5, 3)) translation = np.random.uniform(low=-5., high=5., size=(5, 3)) mats = [from_matvec(euler2mat(*a), t) for a, t in zip(angles, translation)] ext = '' if sw == 'afni': factory = afni.AFNILinearTransformArray elif sw == 'fsl': factory = fsl.FSLLinearTransformArray elif sw == 'itk': ext = '.tfm' factory = itk.ITKLinearTransformArray tflist1 = factory(mats) fname = 'affine-%s.%s%s' % (image_orientation, sw, ext) with pytest.raises(FileNotFoundError): factory.from_filename(fname) tmpdir.join('singlemat.%s' % ext).write('') with pytest.raises(TransformFileError): factory.from_filename('singlemat.%s' % ext) tflist1.to_filename(fname) tflist2 = factory.from_filename(fname) assert tflist1['nxforms'] == tflist2['nxforms'] assert all([np.allclose(x1['parameters'], x2['parameters']) for x1, x2 in zip(tflist1.xforms, tflist2.xforms)])
def test_ITKLinearTransform(tmpdir, data_path): tmpdir.chdir() matlabfile = data_path / 'ds-005_sub-01_from-T1_to-OASIS_affine.mat' mat = loadmat(str(matlabfile)) with open(str(matlabfile), 'rb') as f: itkxfm = itk.ITKLinearTransform.from_fileobj(f) assert np.allclose(itkxfm['parameters'][:3, :3].flatten(), mat['AffineTransform_float_3_3'][:-3].flatten()) assert np.allclose(itkxfm['offset'], mat['fixed'].reshape((3, ))) itkxfm = itk.ITKLinearTransform.from_filename(matlabfile) assert np.allclose(itkxfm['parameters'][:3, :3].flatten(), mat['AffineTransform_float_3_3'][:-3].flatten()) assert np.allclose(itkxfm['offset'], mat['fixed'].reshape((3, ))) # Test to_filename(textfiles) itkxfm.to_filename('textfile.tfm') with open('textfile.tfm', 'r') as f: itkxfm2 = itk.ITKLinearTransform.from_fileobj(f) assert np.allclose(itkxfm['parameters'], itkxfm2['parameters']) # Test to_filename(matlab) itkxfm.to_filename('copy.mat') with open('copy.mat', 'rb') as f: itkxfm3 = itk.ITKLinearTransform.from_fileobj(f) assert np.all(itkxfm['parameters'] == itkxfm3['parameters']) rasmat = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0]) itkxfm = itk.ITKLinearTransform.from_ras(rasmat) assert np.allclose(itkxfm['parameters'], ITK_MAT * rasmat) assert np.allclose(itkxfm.to_ras(), rasmat)
def test_against_spm_resample(): # Test resampling against images resampled with SPM12 # anatomical.nii has a diagonal -2, 2 2 affine; # functional.nii has a diagonal -4, 4 4 affine; # These are a bit boring, so first add some rotations and translations to # the anatomical image affine, and then resample to the first volume in the # functional, and compare to the same thing in SPM. # See ``make_moved_anat.py`` script in this directory for input to SPM. anat = nib.load(pjoin(DATA_DIR, 'anatomical.nii')) func = nib.load(pjoin(DATA_DIR, 'functional.nii')) some_rotations = euler2mat(0.1, 0.2, 0.3) extra_affine = from_matvec(some_rotations, [3, 4, 5]) moved_anat = nib.Nifti1Image(anat.get_fdata(), extra_affine.dot(anat.affine), anat.header) one_func = nib.Nifti1Image(func.dataobj[..., 0], func.affine, func.header) moved2func = resample_from_to(moved_anat, one_func, order=1, cval=np.nan) spm_moved = nib.load(pjoin(DATA_DIR, 'resampled_anat_moved.nii')) assert_spm_resampling_close(moved_anat, moved2func, spm_moved) # Next we resample the rotated anatomical image to output space, and compare # to the same operation done with SPM (our own version of 'reorient.m' by # John Ashburner). moved2output = resample_to_output(moved_anat, 4, order=1, cval=np.nan) spm2output = nib.load(pjoin(DATA_DIR, 'reoriented_anat_moved.nii')) assert_spm_resampling_close(moved_anat, moved2output, spm2output);
def test_load_dim_info(): # Test freq, phase, slice get set correctly on load data = np.random.normal(size=list(range(3))) xyz_aff = from_matvec(np.diag([2, 3, 4]), [11, 12, 13]) in_cs = CS('ijk', name='voxels') out_cs = aligned_csm(3) # Just confirm that the default leads to no axis renaming ni_img = nib.Nifti1Image(data, xyz_aff) hdr = get_header(ni_img) assert_equal(hdr.get_dim_info(), (None, None, None)) assert_equal(nifti2nipy(ni_img).coordmap, AT(in_cs, out_cs, xyz_aff)) # But now... hdr.set_dim_info(freq=1) assert_equal(nifti2nipy(ni_img).coordmap, AT(CS(('i', 'freq', 'k'), "voxels"), out_cs, xyz_aff)) hdr.set_dim_info(freq=2) assert_equal(nifti2nipy(ni_img).coordmap, AT(CS(('i', 'j', 'freq'), "voxels"), out_cs, xyz_aff)) hdr.set_dim_info(phase=1) assert_equal(nifti2nipy(ni_img).coordmap, AT(CS(('i', 'phase', 'k'), "voxels"), out_cs, xyz_aff)) hdr.set_dim_info(slice=0) assert_equal(nifti2nipy(ni_img).coordmap, AT(CS(('slice', 'j', 'k'), "voxels"), out_cs, xyz_aff)) hdr.set_dim_info(freq=1, phase=0, slice=2) assert_equal(nifti2nipy(ni_img).coordmap, AT(CS(('phase', 'freq', 'slice'), "voxels"), out_cs, xyz_aff))
def test_save3(): # A test to ensure that when a file is saved, the affine # and the data agree. In this case, things don't agree: # i) the pixdim is off # ii) makes the affine off step = np.array([3.45,2.3,4.5,6.9]) shape = (13,5,7,3) mni_xyz = mni_csm(3).coord_names cmap = AT(CS('jkli'), CS(('t',) + mni_xyz[::-1]), from_matvec(np.diag([0,3,5,1]), step)) data = np.random.standard_normal(shape) img = api.Image(data, cmap) # with InTemporaryDirectory(): with InTemporaryDirectory(): save_image(img, TMP_FNAME) tmp = load_image(TMP_FNAME) # Detach image from file so we can delete it data = tmp.get_data().copy() img2 = api.Image(data, tmp.coordmap, tmp.metadata) del tmp assert_equal(tuple([img.shape[l] for l in [3,2,1,0]]), img2.shape) a = np.transpose(np.asarray(img), [3,2,1,0]) assert_false(np.allclose(img.affine, img2.affine)) assert_true(np.allclose(a, img2.get_data()))
def test_orthogonal_dims(): # Test whether conversion to nifti raises an error for non-orthogonal # non-spatial dimensions # This affine is all nicely diagonal aff = from_matvec(np.diag([2., 3, 4, 5, 6]), [10, 11, 12, 13, 14]) data = np.random.normal(size=(3, 4, 5, 6, 7)) img = Image(data, vox2mni(aff)) def as3d(aff): return from_matvec(aff[:3, :3], aff[:3, -1]) assert_array_equal(get_affine(nipy2nifti(img)), as3d(aff)) # Non-orthogonal spatial dimensions OK aff[:3, :3] = np.random.normal(size=(3, 3)) img = Image(data, vox2mni(aff)) assert_array_equal(get_affine(nipy2nifti(img)), as3d(aff)) # Space must be orthogonal to time etc aff[0, 3] = 0.1 assert_raises(NiftiError, nipy2nifti, img) aff[0, 3] = 0 assert_array_equal(get_affine(nipy2nifti(img)), as3d(aff)) aff[3, 0] = 0.1 assert_raises(NiftiError, nipy2nifti, img) aff[3, 0] = 0 assert_array_equal(get_affine(nipy2nifti(img)), as3d(aff)) aff[4, 0] = 0.1 assert_raises(NiftiError, nipy2nifti, img)
def test_against_spm_resample(): # Test resampling against images resampled with SPM12 # anatomical.nii has a diagonal -2, 2 2 affine; # functional.nii has a diagonal -4, 4 4 affine; # These are a bit boring, so first add some rotations and translations to # the anatomical image affine, and then resample to the first volume in the # functional, and compare to the same thing in SPM. # See ``make_moved_anat.py`` script in this directory for input to SPM. anat = nib.load(pjoin(DATA_DIR, 'anatomical.nii')) func = nib.load(pjoin(DATA_DIR, 'functional.nii')) some_rotations = euler2mat(0.1, 0.2, 0.3) extra_affine = from_matvec(some_rotations, [3, 4, 5]) moved_anat = nib.Nifti1Image(anat.get_data().astype(float), extra_affine.dot(anat.affine), anat.header) one_func = nib.Nifti1Image(func.dataobj[..., 0], func.affine, func.header) moved2func = resample_from_to(moved_anat, one_func, order=1, cval=np.nan) spm_moved = nib.load(pjoin(DATA_DIR, 'resampled_anat_moved.nii')) assert_spm_resampling_close(moved_anat, moved2func, spm_moved) # Next we resample the rotated anatomical image to output space, and compare # to the same operation done with SPM (our own version of 'reorient.m' by # John Ashburner). moved2output = resample_to_output(moved_anat, 4, order=1, cval=np.nan) spm2output = nib.load(pjoin(DATA_DIR, 'reoriented_anat_moved.nii')) assert_spm_resampling_close(moved_anat, moved2output, spm2output);
def xslice(x, y_spec, z_spec, world): """ Return an LPS slice through a 3d box with x fixed. Parameters ---------- x : float The value at which x is fixed. y_spec : sequence A sequence with 2 values of form ((float, float), int). The (float, float) components are the min and max y values; the int is the number of points. z_spec : sequence As for `y_spec` but for z world : str or CoordinateSystem CoordSysMaker or XYZSpace World 3D space to which resulting coordmap refers Returns ------- affine_transform : AffineTransform An affine transform that describes an plane in LPS coordinates with x fixed. Examples -------- >>> y_spec = ([-114,114], 115) # voxels of size 2 in y, starting at -114, ending at 114 >>> z_spec = ([-70,100], 86) # voxels of size 2 in z, starting at -70, ending at 100 >>> x30 = xslice(30, y_spec, z_spec, 'scanner') >>> x30([0,0]) array([ 30., -114., -70.]) >>> x30([114,85]) array([ 30., 114., 100.]) >>> x30 AffineTransform( function_domain=CoordinateSystem(coord_names=('i_y', 'i_z'), name='slice', coord_dtype=float64), function_range=CoordinateSystem(coord_names=('scanner-x=L->R', 'scanner-y=P->A', 'scanner-z=I->S'), name='scanner', coord_dtype=float64), affine=array([[ 0., 0., 30.], [ 2., 0., -114.], [ 0., 2., -70.], [ 0., 0., 1.]]) ) >>> bounding_box(x30, (y_spec[1], z_spec[1])) ((30.0, 30.0), (-114.0, 114.0), (-70.0, 100.0)) """ affine_range = get_world_cs(world) (ymin, ymax), yno = y_spec y_tick = (ymax-ymin) / (yno - 1.0) (zmin, zmax), zno = z_spec z_tick = (zmax-zmin) / (zno - 1.0) origin = [x, ymin, zmin] colvectors = np.asarray([[0, 0], [y_tick, 0], [0, z_tick]]) T = from_matvec(colvectors, origin) affine_domain = CoordinateSystem(['i_y', 'i_z'], 'slice') return AffineTransform(affine_domain, affine_range, T)
def from_h5obj(cls, fileobj, check=True): """Read the struct from a file object.""" xfm_list = [] h5group = fileobj["TransformGroup"] typo_fallback = "Transform" try: h5group["1"][f"{typo_fallback}Parameters"] except KeyError: typo_fallback = "Tranform" for xfm in list(h5group.values())[1:]: if xfm["TransformType"][0].startswith(b"AffineTransform"): _params = np.asanyarray(xfm[f"{typo_fallback}Parameters"]) xfm_list.append( ITKLinearTransform( parameters=from_matvec(_params[:-3].reshape(3, 3), _params[-3:]), offset=np.asanyarray( xfm[f"{typo_fallback}FixedParameters"]), )) continue if xfm["TransformType"][0].startswith( b"DisplacementFieldTransform"): _fixed = np.asanyarray(xfm[f"{typo_fallback}FixedParameters"]) shape = _fixed[:3].astype("uint16").tolist() offset = _fixed[3:6].astype("uint16") zooms = _fixed[6:9].astype("float") directions = _fixed[9:].astype("float").reshape((3, 3)) affine = from_matvec(directions * zooms, offset) field = np.asanyarray( xfm[f"{typo_fallback}Parameters"]).reshape( tuple(shape + [1, -1])) hdr = Nifti1Header() hdr.set_intent("vector") hdr.set_data_dtype("float") xfm_list.append( ITKDisplacementsField.from_image( Nifti1Image(field.astype("float"), affine, hdr))) continue raise NotImplementedError( f"Unsupported transform type {xfm['TransformType'][0]}") return xfm_list
def yslice(y, x_spec, z_spec, world): """ Return a slice through a 3d box with y fixed. Parameters ---------- y : float The value at which y is fixed. x_spec : sequence A sequence with 2 values of form ((float, float), int). The (float, float) components are the min and max x values; the int is the number of points. z_spec : sequence As for `x_spec` but for z world : str or CoordinateSystem CoordSysMaker or XYZSpace World 3D space to which resulting coordmap refers Returns ------- affine_transform : AffineTransform An affine transform that describes an plane in LPS coordinates with y fixed. Examples -------- >>> x_spec = ([-92,92], 93) # voxels of size 2 in x, starting at -92, ending at 92 >>> z_spec = ([-70,100], 86) # voxels of size 2 in z, starting at -70, ending at 100 >>> y70 = yslice(70, x_spec, z_spec, 'mni') >>> y70 AffineTransform( function_domain=CoordinateSystem(coord_names=('i_x', 'i_z'), name='slice', coord_dtype=float64), function_range=CoordinateSystem(coord_names=('mni-x=L->R', 'mni-y=P->A', 'mni-z=I->S'), name='mni', coord_dtype=float64), affine=array([[ 2., 0., -92.], [ 0., 0., 70.], [ 0., 2., -70.], [ 0., 0., 1.]]) ) >>> y70([0,0]) array([-92., 70., -70.]) >>> y70([92,85]) array([ 92., 70., 100.]) >>> bounding_box(y70, (x_spec[1], z_spec[1])) ((-92.0, 92.0), (70.0, 70.0), (-70.0, 100.0)) """ affine_range = get_world_cs(world) (xmin, xmax), xno = x_spec x_tick = (xmax-xmin) / (xno - 1.0) (zmin, zmax), zno = z_spec z_tick = (zmax-zmin) / (zno - 1.0) origin = [xmin, y, zmin] colvectors = np.asarray([[x_tick, 0], [0, 0], [0, z_tick]]) T = from_matvec(colvectors, origin) affine_domain = CoordinateSystem(['i_x', 'i_z'], 'slice') return AffineTransform(affine_domain, affine_range, T)
def zslice(z, x_spec, y_spec, world): """ Return a slice through a 3d box with z fixed. Parameters ---------- z : float The value at which z is fixed. x_spec : sequence A sequence with 2 values of form ((float, float), int). The (float, float) components are the min and max x values; the int is the number of points. y_spec : sequence As for `x_spec` but for y world : str or CoordinateSystem CoordSysMaker or XYZSpace World 3D space to which resulting coordmap refers Returns ------- affine_transform : AffineTransform An affine transform that describes a plane in LPS coordinates with z fixed. Examples -------- >>> x_spec = ([-92,92], 93) # voxels of size 2 in x, starting at -92, ending at 92 >>> y_spec = ([-114,114], 115) # voxels of size 2 in y, starting at -114, ending at 114 >>> z40 = zslice(40, x_spec, y_spec, 'unknown') >>> z40 AffineTransform( function_domain=CoordinateSystem(coord_names=('i_x', 'i_y'), name='slice', coord_dtype=float64), function_range=CoordinateSystem(coord_names=('unknown-x=L->R', 'unknown-y=P->A', 'unknown-z=I->S'), name='unknown', coord_dtype=float64), affine=array([[ 2., 0., -92.], [ 0., 2., -114.], [ 0., 0., 40.], [ 0., 0., 1.]]) ) >>> z40([0,0]) array([ -92., -114., 40.]) >>> z40([92,114]) array([ 92., 114., 40.]) >>> bounding_box(z40, (x_spec[1], y_spec[1])) ((-92.0, 92.0), (-114.0, 114.0), (40.0, 40.0)) """ affine_range = get_world_cs(world) (xmin, xmax), xno = x_spec x_tick = (xmax-xmin) / (xno - 1.0) (ymin, ymax), yno = y_spec y_tick = (ymax-ymin) / (yno - 1.0) origin = [xmin, ymin, z] colvectors = np.asarray([[x_tick, 0], [0, y_tick], [0, 0]]) T = from_matvec(colvectors, origin) affine_domain = CoordinateSystem(['i_x', 'i_y'], 'slice') return AffineTransform(affine_domain, affine_range, T)
def test_save_toffset(): # Check toffset only gets set for time shape = (2, 3, 4, 5, 6, 7) data = np.random.normal(size=shape) aff = from_matvec(np.diag([2., 3, 4, 5, 6, 7]), [11, 12, 13, 14, 15, 16]) xyz_names = talairach_csm(3).coord_names in_cs = CS('ijklmn') for t_name in 't', 'time': cmap = AT(in_cs, CS(xyz_names + (t_name, 'q', 'r')), aff) ni_img = nipy2nifti(Image(data, cmap)) assert_equal(ni_img.get_header()['toffset'], 14) for time_like in ('hz', 'ppm', 'rads'): cmap = AT(in_cs, CS(xyz_names + (time_like, 'q', 'r')), aff) ni_img = nipy2nifti(Image(data, cmap)) assert_equal(ni_img.get_header()['toffset'], 0) # Check that non-matching time causes a nifti error when toffset !=0 shape_shifted = (2, 3, 4, 6, 5, 7) for t_name in 't', 'time': # No toffset, this is OK cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), np.diag([3, 4, 5, 6, 0, 7, 1])) assert_equal(nipy2nifti(Image(data, cmap)).shape, shape_shifted) # toffset, non-matching error aff_z1 = from_matvec(np.diag([2., 3, 4, 5, 0, 7]), [11, 12, 13, 14, 15, 16]) cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff_z1) assert_raises(NiftiError, nipy2nifti, Image(data, cmap)) # Unless fix0 set assert_equal( nipy2nifti(Image(data, cmap), fix0=True).shape, shape_shifted) # Even this doesn't work if there is more than one zero row and column aff_z2 = from_matvec(np.diag([2., 3, 4, 0, 0, 7]), [11, 12, 13, 14, 15, 16]) cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff_z2) assert_raises(NiftiError, nipy2nifti, Image(data, cmap), fix0=True) # No problem for non-time for t_name in 'hz', 'ppm', 'rads': cmap = AT(CS(('i', 'j', 'k', 'u', t_name, 'v')), CS(xyz_names + ('u', t_name, 'v')), aff) assert_equal( nipy2nifti(Image(data, cmap), fix0=True).shape, shape_shifted)
def test_obliquity(): """Check the calculation of inclination of an affine axes.""" from math import pi aligned = np.diag([2.0, 2.0, 2.3, 1.0]) aligned[:-1, -1] = [-10, -10, -7] R = from_matvec(euler2mat(x=0.09, y=0.001, z=0.001), [0.0, 0.0, 0.0]) oblique = R.dot(aligned) np.testing.assert_almost_equal(obliquity(aligned), [0.0, 0.0, 0.0]) np.testing.assert_almost_equal(obliquity(oblique) * 180 / pi, [0.0810285, 5.1569949, 5.1569376])
def xslice(x, y_spec, z_spec, world): """ Return an LPS slice through a 3d box with x fixed. Parameters ---------- x : float The value at which x is fixed. y_spec : sequence A sequence with 2 values of form ((float, float), int). The (float, float) components are the min and max y values; the int is the number of points. z_spec : sequence As for `y_spec` but for z world : str or CoordinateSystem CoordSysMaker or XYZSpace World 3D space to which resulting coordmap refers Returns ------- affine_transform : AffineTransform An affine transform that describes an plane in LPS coordinates with x fixed. Examples -------- >>> y_spec = ([-114,114], 115) # voxels of size 2 in y, starting at -114, ending at 114 >>> z_spec = ([-70,100], 86) # voxels of size 2 in z, starting at -70, ending at 100 >>> x30 = xslice(30, y_spec, z_spec, 'scanner') >>> x30([0,0]) array([ 30., -114., -70.]) >>> x30([114,85]) array([ 30., 114., 100.]) >>> x30 AffineTransform( function_domain=CoordinateSystem(coord_names=('i_y', 'i_z'), name='slice', coord_dtype=float64), function_range=CoordinateSystem(coord_names=('scanner-x=L->R', 'scanner-y=P->A', 'scanner-z=I->S'), name='scanner', coord_dtype=float64), affine=array([[ 0., 0., 30.], [ 2., 0., -114.], [ 0., 2., -70.], [ 0., 0., 1.]]) ) >>> bounding_box(x30, (y_spec[1], z_spec[1])) ((30.0, 30.0), (-114.0, 114.0), (-70.0, 100.0)) """ affine_range = get_world_cs(world) (ymin, ymax), yno = y_spec y_tick = (ymax - ymin) / (yno - 1.0) (zmin, zmax), zno = z_spec z_tick = (zmax - zmin) / (zno - 1.0) origin = [x, ymin, zmin] colvectors = np.asarray([[0, 0], [y_tick, 0], [0, z_tick]]) T = from_matvec(colvectors, origin) affine_domain = CoordinateSystem(['i_y', 'i_z'], 'slice') return AffineTransform(affine_domain, affine_range, T)
def zslice(z, x_spec, y_spec, world): """ Return a slice through a 3d box with z fixed. Parameters ---------- z : float The value at which z is fixed. x_spec : sequence A sequence with 2 values of form ((float, float), int). The (float, float) components are the min and max x values; the int is the number of points. y_spec : sequence As for `x_spec` but for y world : str or CoordinateSystem CoordSysMaker or XYZSpace World 3D space to which resulting coordmap refers Returns ------- affine_transform : AffineTransform An affine transform that describes a plane in LPS coordinates with z fixed. Examples -------- >>> x_spec = ([-92,92], 93) # voxels of size 2 in x, starting at -92, ending at 92 >>> y_spec = ([-114,114], 115) # voxels of size 2 in y, starting at -114, ending at 114 >>> z40 = zslice(40, x_spec, y_spec, 'unknown') >>> z40 AffineTransform( function_domain=CoordinateSystem(coord_names=('i_x', 'i_y'), name='slice', coord_dtype=float64), function_range=CoordinateSystem(coord_names=('unknown-x=L->R', 'unknown-y=P->A', 'unknown-z=I->S'), name='unknown', coord_dtype=float64), affine=array([[ 2., 0., -92.], [ 0., 2., -114.], [ 0., 0., 40.], [ 0., 0., 1.]]) ) >>> z40([0,0]) array([ -92., -114., 40.]) >>> z40([92,114]) array([ 92., 114., 40.]) >>> bounding_box(z40, (x_spec[1], y_spec[1])) ((-92.0, 92.0), (-114.0, 114.0), (40.0, 40.0)) """ affine_range = get_world_cs(world) (xmin, xmax), xno = x_spec x_tick = (xmax - xmin) / (xno - 1.0) (ymin, ymax), yno = y_spec y_tick = (ymax - ymin) / (yno - 1.0) origin = [xmin, ymin, z] colvectors = np.asarray([[x_tick, 0], [0, y_tick], [0, 0]]) T = from_matvec(colvectors, origin) affine_domain = CoordinateSystem(['i_x', 'i_y'], 'slice') return AffineTransform(affine_domain, affine_range, T)
def yslice(y, x_spec, z_spec, world): """ Return a slice through a 3d box with y fixed. Parameters ---------- y : float The value at which y is fixed. x_spec : sequence A sequence with 2 values of form ((float, float), int). The (float, float) components are the min and max x values; the int is the number of points. z_spec : sequence As for `x_spec` but for z world : str or CoordinateSystem CoordSysMaker or XYZSpace World 3D space to which resulting coordmap refers Returns ------- affine_transform : AffineTransform An affine transform that describes an plane in LPS coordinates with y fixed. Examples -------- >>> x_spec = ([-92,92], 93) # voxels of size 2 in x, starting at -92, ending at 92 >>> z_spec = ([-70,100], 86) # voxels of size 2 in z, starting at -70, ending at 100 >>> y70 = yslice(70, x_spec, z_spec, 'mni') >>> y70 AffineTransform( function_domain=CoordinateSystem(coord_names=('i_x', 'i_z'), name='slice', coord_dtype=float64), function_range=CoordinateSystem(coord_names=('mni-x=L->R', 'mni-y=P->A', 'mni-z=I->S'), name='mni', coord_dtype=float64), affine=array([[ 2., 0., -92.], [ 0., 0., 70.], [ 0., 2., -70.], [ 0., 0., 1.]]) ) >>> y70([0,0]) array([-92., 70., -70.]) >>> y70([92,85]) array([ 92., 70., 100.]) >>> bounding_box(y70, (x_spec[1], z_spec[1])) ((-92.0, 92.0), (70.0, 70.0), (-70.0, 100.0)) """ affine_range = get_world_cs(world) (xmin, xmax), xno = x_spec x_tick = (xmax - xmin) / (xno - 1.0) (zmin, zmax), zno = z_spec z_tick = (zmax - zmin) / (zno - 1.0) origin = [xmin, y, zmin] colvectors = np.asarray([[x_tick, 0], [0, 0], [0, z_tick]]) T = from_matvec(colvectors, origin) affine_domain = CoordinateSystem(['i_x', 'i_z'], 'slice') return AffineTransform(affine_domain, affine_range, T)
def load(filename, fmt='X5', reference=None): """Load a linear transform.""" if fmt.lower() in ['itk', 'ants', 'elastix', 'nifty']: with open(filename) as itkfile: itkxfm = itkfile.read().splitlines() if 'Insight Transform File' not in itkxfm[0]: raise ValueError( "File %s does not look like an ITK transform text file." % filename) matlist = [] for nxfm in range(len(itkxfm[1:]) // 4): parameters = np.fromstring(itkxfm[nxfm * 4 + 3].split(':')[-1].strip(), dtype=float, sep=' ') offset = np.fromstring(itkxfm[nxfm * 4 + 4].split(':')[-1].strip(), dtype=float, sep=' ') if len(parameters) == 12: matrix = from_matvec(parameters[:9].reshape((3, 3)), parameters[9:]) c_neg = from_matvec(np.eye(3), offset * -1.0) c_pos = from_matvec(np.eye(3), offset) matlist.append(LPS.dot(c_pos.dot(matrix.dot(c_neg.dot(LPS))))) matrix = np.stack(matlist) # elif fmt.lower() == 'afni': # parameters = LPS.dot(self.matrix.dot(LPS)) # parameters = parameters[:3, :].reshape(-1).tolist() elif fmt.lower() in ('x5', 'bids'): raise NotImplementedError else: raise NotImplementedError if reference and isinstance(reference, str): reference = loadimg(reference) return Affine(matrix, reference)
def test_mulmat_operator(testdata_path): """Check the @ operator.""" ref = testdata_path / "someones_anatomy.nii.gz" mat1 = np.diag([2.0, 2.0, 2.0, 1.0]) mat2 = from_matvec(np.eye(3), (4, 2, -1)) aff = nitl.Affine(mat1, reference=ref) composed = aff @ mat2 assert composed.reference is None assert composed == nitl.Affine(mat1.dot(mat2)) composed = nitl.Affine(mat2) @ aff assert composed.reference == aff.reference assert composed == nitl.Affine(mat2.dot(mat1), reference=ref)
def plot_rigid_body_transformation(trans_x=0, trans_y=0, trans_z=0, rot_x=0, rot_y=0, rot_z=0): '''This plot creates an interactive demo to illustrate the parameters of a rigid body transformation''' fov = 30 radius = 10 x, y, z = np.indices((fov, fov, fov)) cube = ((x > fov // 2 - radius // 2) & (x < fov // 2 + radius // 2)) & ( (y > fov // 2 - radius // 2) & (y < fov // 2 + radius // 2)) & ((z > fov // 2 - radius // 2) & (z < fov // 2 + radius // 2)) cube = cube.astype(int) vec = np.array([trans_x, trans_y, trans_z]) rot_x = np.radians(rot_x) rot_y = np.radians(rot_y) rot_z = np.radians(rot_z) rot_axis1 = np.array([[1, 0, 0], [0, np.cos(rot_x), -np.sin(rot_x)], [0, np.sin(rot_x), np.cos(rot_x)]]) rot_axis2 = np.array([[np.cos(rot_y), 0, np.sin(rot_y)], [0, 1, 0], [-np.sin(rot_y), 0, np.cos(rot_y)]]) rot_axis3 = np.array([[np.cos(rot_z), -np.sin(rot_z), 0], [np.sin(rot_z), np.cos(rot_z), 0], [0, 0, 1]]) rotation = rot_axis1 @ rot_axis2 @ rot_axis3 affine = from_matvec(rotation, vec) i_coords, j_coords, k_coords = np.meshgrid(range(cube.shape[0]), range(cube.shape[1]), range(cube.shape[2]), indexing='ij') coordinate_grid = np.array([i_coords, j_coords, k_coords]) coords_last = coordinate_grid.transpose(1, 2, 3, 0) transformed = apply_affine(affine, coords_last) coords_first = transformed.transpose(3, 0, 1, 2) fig = plt.figure(figsize=(15, 12)) ax = plt.axes(projection='3d') ax.voxels(map_coordinates(cube, coords_first)) ax.set_xlabel('x', fontsize=16) ax.set_ylabel('y', fontsize=16) ax.set_zlabel('z', fontsize=16)
def apply_rotate(matrix, rad_x=0, rad_y=0, rad_z=0): ''' axis = x or y or z ''' rmat = dict(x=np.array([[1, 0, 0], [0, np.cos(rad_x), -np.sin(rad_x)], [0, np.sin(rad_x), np.cos(rad_x)]]).astype('float'), y=np.array([[np.cos(rad_y), 0, np.sin(rad_y)], [0, 1, 0], [-np.sin(rad_y), 0, np.cos(rad_y)]]).astype('float'), z=np.array([[np.cos(rad_z), -np.sin(rad_z), 0], [np.sin(rad_z), np.cos(rad_z), 0], [0, 0, 1]]).astype('float')) af_mat, af_vec = to_matvec(matrix) rotated_mat = rmat['z'].dot(rmat['y'].dot(rmat['x'].dot(af_mat))) rotated_vec = rmat['z'].dot(rmat['y'].dot(rmat['x'].dot(af_vec))) return from_matvec(rotated_mat, rotated_vec)
def test_is_xyz_affable(): # Whether there exists an xyz affine for this coordmap affine = np.diag([2, 4, 5, 6, 1]) cmap = AffineTransform(VARS['d_cs_r4'], VARS['r_cs_r4'], affine) assert_true(is_xyz_affable(cmap)) assert_false(is_xyz_affable(cmap.reordered_range([3, 0, 1, 2]))) assert_false(is_xyz_affable(cmap.reordered_domain([3, 0, 1, 2]))) # Can pass in own validator my_valtor = dict(blind='x', leading='y', ditch='z') r_cs = CS(('blind', 'leading', 'ditch'), 'fall') affine = from_matvec(np.arange(9).reshape((3, 3)), [11, 12, 13]) cmap = AffineTransform(VARS['d_cs_r3'], r_cs, affine) # No xyz affine if we don't use our custom dictionary assert_false(is_xyz_affable(cmap)) # Is if we do assert_true(is_xyz_affable(cmap, my_valtor))
def test_is_xyz_affable(): # Whether there exists an xyz affine for this coordmap affine = np.diag([2,4,5,6,1]) cmap = AffineTransform(VARS['d_cs_r4'], VARS['r_cs_r4'], affine) assert_true(is_xyz_affable(cmap)) assert_false(is_xyz_affable(cmap.reordered_range([3,0,1,2]))) assert_false(is_xyz_affable(cmap.reordered_domain([3,0,1,2]))) # Can pass in own validator my_valtor = dict(blind='x', leading='y', ditch='z') r_cs = CS(('blind', 'leading', 'ditch'), 'fall') affine = from_matvec(np.arange(9).reshape((3, 3)), [11, 12, 13]) cmap = AffineTransform(VARS['d_cs_r3'], r_cs, affine) # No xyz affine if we don't use our custom dictionary assert_false(is_xyz_affable(cmap)) # Is if we do assert_true(is_xyz_affable(cmap, my_valtor))
def test_save4(): # Same as test_save3 except we have reordered the 'ijk' input axes. shape = (13,5,7,3) step = np.array([3.45,2.3,4.5,6.9]) # When the input coords are in the 'ljki' order, the affines get # rearranged. Note that the 'start' below, must be 0 for # non-spatial dimensions, because we have no way to store them in # most cases. For example, a 'start' of [1,5,3,1] would be lost on # reload mni_xyz = mni_csm(3).coord_names cmap = AT(CS('tkji'), CS((('t',) + mni_xyz[::-1])), from_matvec(np.diag([2., 3, 5, 1]), step)) data = np.random.standard_normal(shape) img = api.Image(data, cmap) with InTemporaryDirectory(): save_image(img, TMP_FNAME) tmp = load_image(TMP_FNAME) data = tmp.get_data().copy() # Detach image from file so we can delete it img2 = api.Image(data, tmp.coordmap, tmp.metadata) del tmp P = np.array([[0,0,0,1,0], [0,0,1,0,0], [0,1,0,0,0], [1,0,0,0,0], [0,0,0,0,1]]) res = np.dot(P, np.dot(img.affine, P.T)) # the step part of the affine should be set correctly assert_array_almost_equal(res[:4,:4], img2.affine[:4,:4]) # start in the spatial dimensions should be set correctly assert_array_almost_equal(res[:3,-1], img2.affine[:3,-1]) # start in the time dimension should be 3.45 as in img, because NIFTI stores # the time offset in hdr[``toffset``] assert_not_equal(res[3,-1], img2.affine[3,-1]) assert_equal(res[3,-1], 3.45) # shapes should be reversed because img has coordinates reversed assert_equal(img.shape[::-1], img2.shape) # data should be transposed because coordinates are reversed assert_array_almost_equal( np.transpose(np.asarray(img2),[3,2,1,0]), np.asarray(img)) # coordinate names should be reversed as well assert_equal(img2.coordmap.function_domain.coord_names, img.coordmap.function_domain.coord_names[::-1]) assert_equal(img2.coordmap.function_domain.coord_names, ('i', 'j', 'k', 't'))
def plot_affine_cost(trans_x=0, trans_y=0): '''This function creates an interactive demo to highlight how a cost function works in image registration.''' fov = 30 radius = 15 x, y = np.indices((fov, fov)) square1 = (x < radius - 2) & (y < radius - 2) square2 = ((x > fov // 2 - radius // 2) & (x < fov // 2 + radius // 2)) & ((y > fov // 2 - radius // 2) & (y < fov // 2 + radius // 2)) square1 = square1.astype(float) square2 = square2.astype(float) vec = np.array([trans_y, trans_x]) affine = from_matvec(np.eye(2), vec) i_coords, j_coords = np.meshgrid(range(square1.shape[0]), range(square1.shape[1]), indexing='ij') coordinate_grid = np.array([i_coords, j_coords]) coords_last = coordinate_grid.transpose(1, 2, 0) transformed = apply_affine(affine, coords_last) coords_first = transformed.transpose(2, 0, 1) transformed_square = map_coordinates(square1, coords_first) f, a = plt.subplots(ncols=3, figsize=(15, 5)) a[0].imshow(transformed_square) a[0].set_xlabel('x', fontsize=16) a[0].set_ylabel('y', fontsize=16) a[0].set_title('Target Image', fontsize=18) a[1].imshow(square2) a[1].set_xlabel('x', fontsize=16) a[1].set_ylabel('y', fontsize=16) a[1].set_title('Reference Image', fontsize=18) point_x = deepcopy(trans_x) point_y = deepcopy(trans_y) sse = np.sum((transformed_square - square2)**2) a[2].bar(0, sse) a[2].set_ylim([0, 350]) a[2].set_ylabel('SSE', fontsize=18) a[2].set_xlabel('Cost Function', fontsize=18) a[2].set_xticks([]) a[2].set_title(f'Parameters: ({int(trans_x)},{int(trans_y)})', fontsize=20) plt.tight_layout()
def test_other_axes(): # With a diagonal affine, we can do PCA on any axis ncomp = 5 img = data_dict['fmridata'] in_coords = list(img.axes.coord_names) img_data = img.get_data() for axis_no, axis_name in enumerate('ijkt'): p = pca_image(img, axis_name, ncomp=ncomp) n = img.shape[axis_no] bv_key = 'basis_vectors over ' + axis_name assert_equal(_rank(p), n - 1) assert_equal(p[bv_key].shape, (n, n - 1)) # We get the expected data back dp = pca_array(img_data, axis_no, ncomp=ncomp) # We have to make sure the signs are the same; on Windows it seems the # signs can flip even between two runs on the same data pos_p = img_res2pos1(p, bv_key) pos_dp = res2pos1(dp) img_bps = pos_p['basis_projections'] assert_almost_equal(pos_dp['basis_vectors'], pos_p[bv_key]) assert_almost_equal(pos_dp['basis_projections'], img_bps.get_data()) # And we've replaced the expected axis exp_coords = in_coords[:] exp_coords[exp_coords.index(axis_name)] = 'PCA components' assert_equal(img_bps.axes.coord_names, exp_coords) # If the affine is not diagonal, we'll get an error aff = from_matvec(np.arange(16).reshape(4,4)) nd_cmap = AffineTransform(img.axes, img.reference, aff) nd_img = Image(img_data, nd_cmap) for axis_name in 'ijkt': assert_raises(AxisError, pca_image, nd_img, axis_name) # Only for the non-diagonal parts aff = np.array([[1, 2, 0, 0, 10], [2, 1, 0, 0, 11], [0, 0, 3, 0, 12], [0, 0, 0, 4, 13], [0, 0, 0, 0, 1]]) nd_cmap = AffineTransform(img.axes, img.reference, aff) nd_img = Image(img_data, nd_cmap) for axis_name in 'ij': assert_raises(AxisError, pca_image, nd_img, axis_name) for axis_name in 'kt': p = pca_image(img, axis_name, ncomp=ncomp) exp_coords = in_coords[:] exp_coords[exp_coords.index(axis_name)] = 'PCA components' assert_equal(p['basis_projections'].axes.coord_names, exp_coords)
def apply_flip(matrix, axis, mat=True, vec=True): '''axis = x or y or z''' flip_idx = dict(x=0, y=1, z=2) orig_mat, orig_vec = to_matvec(matrix) aff_mat = np.ones(3) aff_mat[flip_idx[axis]] = -1 aff_mat = np.diag(aff_mat) if mat: flip_mat = aff_mat.dot(orig_mat) else: flip_mat = orig_mat if vec: flip_vec = aff_mat.dot(orig_vec) else: flip_vec = orig_vec return from_matvec(flip_mat, flip_vec)
def test_apply_linear_transform(tmpdir, get_testdata, image_orientation, sw_tool): """Check implementation of exporting affines to formats.""" tmpdir.chdir() img = get_testdata[image_orientation] # Generate test transform T = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0]) xfm = nitl.Affine(T) xfm.reference = img ext = "" if sw_tool == "itk": ext = ".tfm" elif sw_tool == "fs": ext = ".lta" img.to_filename("img.nii.gz") xfm_fname = "M.%s%s" % (sw_tool, ext) xfm.to_filename(xfm_fname, fmt=sw_tool) cmd = APPLY_LINEAR_CMD[sw_tool]( transform=os.path.abspath(xfm_fname), reference=os.path.abspath("img.nii.gz"), moving=os.path.abspath("img.nii.gz"), ) # skip test if command is not available on host exe = cmd.split(" ", 1)[0] if not shutil.which(exe): pytest.skip("Command {} not found on host".format(exe)) exit_code = check_call([cmd], shell=True) assert exit_code == 0 sw_moved = nb.load("resampled.nii.gz") nt_moved = xfm.apply(img, order=0) diff = sw_moved.get_fdata() - nt_moved.get_fdata() # A certain tolerance is necessary because of resampling at borders assert (np.abs(diff) > 1e-3).sum() / diff.size < TESTS_BORDER_TOLERANCE nt_moved = xfm.apply("img.nii.gz", order=0) diff = sw_moved.get_fdata() - nt_moved.get_fdata() # A certain tolerance is necessary because of resampling at borders assert (np.abs(diff) > 1e-3).sum() / diff.size < TESTS_BORDER_TOLERANCE
def test_Linear_common(tmpdir, data_path, sw, image_orientation, get_testdata): tmpdir.chdir() moving = get_testdata[image_orientation] reference = get_testdata[image_orientation] ext = '' if sw == 'afni': factory = afni.AFNILinearTransform elif sw == 'fsl': factory = fsl.FSLLinearTransform elif sw == 'itk': reference = None moving = None ext = '.tfm' factory = itk.ITKLinearTransform elif sw == 'fs': ext = '.lta' factory = fs.LinearTransformArray with pytest.raises(TransformFileError): factory.from_string('') fname = 'affine-%s.%s%s' % (image_orientation, sw, ext) # Test the transform loaders are implemented xfm = factory.from_filename(data_path / fname) with open(str(data_path / fname)) as f: text = f.read() f.seek(0) xfm = factory.from_fileobj(f) # Test to_string assert text == xfm.to_string() xfm.to_filename(fname) assert filecmp.cmp(fname, str((data_path / fname).resolve())) # Test from_ras RAS = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0]) xfm = factory.from_ras(RAS, reference=reference, moving=moving) assert np.allclose(xfm.to_ras(reference=reference, moving=moving), RAS)
def par2referenceSPM(par_file, session): rigids_euler = np.array(pd.read_csv(par_file, header=None, delimiter='\s+')) number_of_cycles = rigids_euler.shape[0] rigids = np.empty((number_of_cycles, 4, 4)) for i in range(number_of_cycles): mat = euler2mat( -rigids_euler[i, 3], # gieren / yaw / z -rigids_euler[i, 4], # rollen / roll / y -rigids_euler[i, 5]) # nicken / pitch / x vec = rigids_euler[i, :3] rigids[i] = from_matvec(matrix=mat, vector=np.array((-vec[1], -vec[0], -vec[2]))) reference_maps = ReferenceMaps(session.name) reference_maps.temporal_resolution = session.temporal_resolution reference_maps.slice_timing = session.slice_timing reference_maps.set_acquisition_maps(Affines(rigids)) reference_maps.shape = (session.numob, session.shape[session.ep]) return reference_maps
def test_save4(): # Same as test_save3 except we have reordered the 'ijk' input axes. shape = (13, 5, 7, 3) step = np.array([3.45, 2.3, 4.5, 6.9]) # When the input coords are in the 'ljki' order, the affines get # rearranged. Note that the 'start' below, must be 0 for # non-spatial dimensions, because we have no way to store them in # most cases. For example, a 'start' of [1,5,3,1] would be lost on # reload mni_xyz = mni_csm(3).coord_names cmap = AT(CS('tkji'), CS((('t', ) + mni_xyz[::-1])), from_matvec(np.diag([2., 3, 5, 1]), step)) data = np.random.standard_normal(shape) img = api.Image(data, cmap) with InTemporaryDirectory(): save_image(img, TMP_FNAME) tmp = load_image(TMP_FNAME) data = tmp.get_data().copy() # Detach image from file so we can delete it img2 = api.Image(data, tmp.coordmap, tmp.metadata) del tmp P = np.array([[0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0], [1, 0, 0, 0, 0], [0, 0, 0, 0, 1]]) res = np.dot(P, np.dot(img.affine, P.T)) # the step part of the affine should be set correctly assert_array_almost_equal(res[:4, :4], img2.affine[:4, :4]) # start in the spatial dimensions should be set correctly assert_array_almost_equal(res[:3, -1], img2.affine[:3, -1]) # start in the time dimension should be 3.45 as in img, because NIFTI stores # the time offset in hdr[``toffset``] assert_not_equal(res[3, -1], img2.affine[3, -1]) assert_equal(res[3, -1], 3.45) # shapes should be reversed because img has coordinates reversed assert_equal(img.shape[::-1], img2.shape) # data should be transposed because coordinates are reversed assert_array_almost_equal(np.transpose(img2.get_data(), [3, 2, 1, 0]), img.get_data()) # coordinate names should be reversed as well assert_equal(img2.coordmap.function_domain.coord_names, img.coordmap.function_domain.coord_names[::-1]) assert_equal(img2.coordmap.function_domain.coord_names, ('i', 'j', 'k', 't'))
def test_xyz_affine(): # Getting an xyz affine from coordmaps aff3d = from_matvec(np.arange(9).reshape((3,3)), [15,16,17]) cmap3d = AffineTransform(VARS['d_cs_r3'], VARS['r_cs_r3'], aff3d) rzs = np.c_[np.arange(12).reshape((4,3)), [0,0,0,12]] aff4d = from_matvec(rzs, [15,16,17,18]) cmap4d = AffineTransform(VARS['d_cs_r4'], VARS['r_cs_r4'], aff4d) # Simplest case of 3D affine -> affine unchanged assert_array_equal(xyz_affine(cmap3d), aff3d) # 4D (5, 5) affine -> 3D equivalent assert_array_equal(xyz_affine(cmap4d), aff3d) # Any dimensions not spatial, AxesError r_cs = CS(('mni-x', 'mni-y', 'mni-q'), 'mni') funny_cmap = AffineTransform(VARS['d_cs_r3'],r_cs, aff3d) assert_raises(AxesError, xyz_affine, funny_cmap) r_cs = CS(('mni-x', 'mni-q', 'mni-z'), 'mni') funny_cmap = AffineTransform(VARS['d_cs_r3'],r_cs, aff3d) assert_raises(AxesError, xyz_affine, funny_cmap) # We insist that the coordmap is in output xyz order permutations = (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0) for perm in permutations: assert_raises(AxesError, xyz_affine, cmap3d.reordered_range(perm)) # The input order doesn't matter, as long as the xyz axes map to the first # three input axes for perm in permutations: assert_array_equal(xyz_affine( cmap3d.reordered_domain(perm)), aff3d[:, perm + (-1,)]) # But if the corresponding input axes not in the first three, an axis error wrong_inputs = cmap4d.reordered_domain([0, 1, 3, 2]) assert_raises(AxesError, xyz_affine, wrong_inputs) # xyzs must be orthogonal to dropped axis for i in range(3): aff = aff4d.copy() aff[i,3] = 1 cmap = AffineTransform(VARS['d_cs_r4'], VARS['r_cs_r4'], aff) assert_raises(AffineError, xyz_affine, cmap) # And if reordered assert_raises(AxesError, xyz_affine, cmap.reordered_range([2,0,1,3])) # Non-square goes to square aff54 = np.array([[0, 1, 2, 15], [3, 4, 5, 16], [6, 7, 8, 17], [0, 0, 0, 18], [0, 0, 0, 1]]) cmap = AffineTransform(VARS['d_cs_r3'], VARS['r_cs_r4'], aff54) assert_array_equal(xyz_affine(cmap), aff3d) aff57 = np.array([[0, 1, 2, 0, 0, 0, 15], [3, 4, 5, 0, 0, 0, 16], [6, 7, 8, 0, 0, 0, 17], [0, 0, 0, 0, 0, 0, 18], [0, 0, 0, 0, 0, 0, 1]]) d_cs_r6 = CS('ijklmn', 'voxels') cmap = AffineTransform(d_cs_r6, VARS['r_cs_r4'], aff57) assert_array_equal(xyz_affine(cmap), aff3d) # Non-affine raises SpaceTypeError cmap_cmap = CoordinateMap(VARS['d_cs_r4'], VARS['r_cs_r4'], lambda x:x*3) assert_raises(SpaceTypeError, xyz_affine, cmap_cmap) # Not enough dimensions - SpaceTypeError d_cs_r2 = CS('ij', 'voxels') r_cs_r2 = CS(VARS['r_names'][:2], 'mni') cmap = AffineTransform(d_cs_r2, r_cs_r2, np.array([[2,0,10],[0,3,11],[0,0,1]])) assert_raises(AxesError, xyz_affine, cmap) # Can pass in own validator my_valtor = dict(blind='x', leading='y', ditch='z') r_cs = CS(('blind', 'leading', 'ditch'), 'fall') cmap = AffineTransform(VARS['d_cs_r3'],r_cs, aff3d) assert_raises(AxesError, xyz_affine, cmap) assert_array_equal(xyz_affine(cmap, my_valtor), aff3d) # Slices in x, y, z coordmaps raise error because of missing spatial # dimensions arr = np.arange(120).reshape((2, 3, 4, 5)) aff = np.diag([2, 3, 4, 5, 1]) img = Image(arr, vox2mni(aff)) assert_raises(AxesError, xyz_affine, img[1].coordmap) assert_raises(AxesError, xyz_affine, img[:,1].coordmap) assert_raises(AxesError, xyz_affine, img[:,:,1].coordmap)
""" Make anatomical image with altered affine * Add some rotations and translations to affine; * Save as ``.nii`` file so SPM can read it. See ``resample_using_spm.m`` for processing of this generated image by SPM. """ import numpy as np import nibabel as nib from nibabel.eulerangles import euler2mat from nibabel.affines import from_matvec img = nib.load('anatomical.nii') some_rotations = euler2mat(0.1, 0.2, 0.3) extra_affine = from_matvec(some_rotations, [3, 4, 5]) moved_anat = nib.Nifti1Image(img.dataobj, extra_affine.dot(img.affine), img.header) moved_anat.set_data_dtype(np.float32) nib.save(moved_anat, 'anat_moved.nii')
def proc_file(infile, opts): # figure out the output filename, and see if it exists basefilename = splitext_addext(os.path.basename(infile))[0] if opts.outdir is not None: # set output path basefilename = os.path.join(opts.outdir, basefilename) # prep a file if opts.compressed: verbose("Using gzip compression") outfilename = basefilename + ".nii.gz" else: outfilename = basefilename + ".nii" if os.path.isfile(outfilename) and not opts.overwrite: raise IOError('Output file "%s" exists, use --overwrite to ' "overwrite it" % outfilename) # load the PAR header and data scaling = "dv" if opts.scaling == "off" else opts.scaling infile = fname_ext_ul_case(infile) pr_img = pr.load(infile, permit_truncated=opts.permit_truncated, scaling=scaling, strict_sort=opts.strict_sort) pr_hdr = pr_img.header affine = pr_hdr.get_affine(origin=opts.origin) slope, intercept = pr_hdr.get_data_scaling(scaling) if opts.scaling != "off": verbose('Using data scaling "%s"' % opts.scaling) # get original scaling, and decide if we scale in-place or not if opts.scaling == "off": slope = np.array([1.0]) intercept = np.array([0.0]) in_data = pr_img.dataobj.get_unscaled() out_dtype = pr_hdr.get_data_dtype() elif not np.any(np.diff(slope)) and not np.any(np.diff(intercept)): # Single scalefactor case slope = slope.ravel()[0] intercept = intercept.ravel()[0] in_data = pr_img.dataobj.get_unscaled() out_dtype = pr_hdr.get_data_dtype() else: # Multi scalefactor case slope = np.array([1.0]) intercept = np.array([0.0]) in_data = np.array(pr_img.dataobj) out_dtype = np.float64 # Reorient data block to LAS+ if necessary ornt = io_orientation(np.diag([-1, 1, 1, 1]).dot(affine)) if np.all(ornt == [[0, 1], [1, 1], [2, 1]]): # already in LAS+ t_aff = np.eye(4) else: # Not in LAS+ t_aff = inv_ornt_aff(ornt, pr_img.shape) affine = np.dot(affine, t_aff) in_data = apply_orientation(in_data, ornt) bvals, bvecs = pr_hdr.get_bvals_bvecs() if not opts.keep_trace: # discard Philips DTI trace if present if bvecs is not None: bad_mask = np.logical_and(bvals != 0, (bvecs == 0).all(axis=1)) if bad_mask.sum() > 0: pl = "s" if bad_mask.sum() != 1 else "" verbose("Removing %s DTI trace volume%s" % (bad_mask.sum(), pl)) good_mask = ~bad_mask in_data = in_data[..., good_mask] bvals = bvals[good_mask] bvecs = bvecs[good_mask] # Make corresponding NIfTI image nimg = nifti1.Nifti1Image(in_data, affine, pr_hdr) nhdr = nimg.header nhdr.set_data_dtype(out_dtype) nhdr.set_slope_inter(slope, intercept) nhdr.set_sform(affine, code=1) nhdr.set_qform(affine, code=1) if "parse" in opts.minmax: # need to get the scaled data verbose("Loading (and scaling) the data to determine value range") if opts.minmax[0] == "parse": nhdr["cal_min"] = in_data.min() * slope + intercept else: nhdr["cal_min"] = float(opts.minmax[0]) if opts.minmax[1] == "parse": nhdr["cal_max"] = in_data.max() * slope + intercept else: nhdr["cal_max"] = float(opts.minmax[1]) # container for potential NIfTI1 header extensions if opts.store_header: # dump the full PAR header content into an extension with open(infile, "rb") as fobj: # contents must be bytes hdr_dump = fobj.read() dump_ext = nifti1.Nifti1Extension("comment", hdr_dump) nhdr.extensions.append(dump_ext) verbose("Writing %s" % outfilename) nibabel.save(nimg, outfilename) # write out bvals/bvecs if requested if opts.bvs: if bvals is None and bvecs is None: verbose("No DTI volumes detected, bvals and bvecs not written") elif bvecs is None: verbose("DTI volumes detected, but no diffusion direction info was" "found. Writing .bvals file only.") with open(basefilename + ".bvals", "w") as fid: # np.savetxt could do this, but it's just a loop anyway for val in bvals: fid.write("%s " % val) fid.write("\n") else: verbose("Writing .bvals and .bvecs files") # Transform bvecs with reorientation affine orig2new = npl.inv(t_aff) bv_reorient = from_matvec(to_matvec(orig2new)[0], [0, 0, 0]) bvecs = apply_affine(bv_reorient, bvecs) with open(basefilename + ".bvals", "w") as fid: # np.savetxt could do this, but it's just a loop anyway for val in bvals: fid.write("%s " % val) fid.write("\n") with open(basefilename + ".bvecs", "w") as fid: for row in bvecs.T: for val in row: fid.write("%s " % val) fid.write("\n") # export data labels varying along the 4th dimensions if requested if opts.vol_info: labels = pr_img.header.get_volume_labels() if len(labels) > 0: vol_keys = list(labels.keys()) with open(basefilename + ".ordering.csv", "w") as csvfile: csvwriter = csv.writer(csvfile, delimiter=",") csvwriter.writerow(vol_keys) for vals in zip(*[labels[k] for k in vol_keys]): csvwriter.writerow(vals) # write out dwell time if requested if opts.dwell_time: try: dwell_time = calculate_dwell_time( pr_hdr.get_water_fat_shift(), pr_hdr.get_echo_train_length(), opts.field_strength ) except MRIError: verbose("No EPI factors, dwell time not written") else: verbose("Writing dwell time (%r sec) calculated assuming %sT " "magnet" % (dwell_time, opts.field_strength)) with open(basefilename + ".dwell_time", "w") as fid: fid.write("%r\n" % dwell_time)
def test_resample_to_output(): # Test routine to sample iamges to output space # Image aligned to output axes - no-op data = np.arange(24).reshape((2, 3, 4)) img = Nifti1Image(data, np.eye(4)) # Check default resampling img2 = resample_to_output(img) assert_array_equal(img2.shape, (2, 3, 4)) assert_array_equal(img2.affine, np.eye(4)) assert_array_equal(img2.dataobj, data) # Check resampling with different voxel size specifications for vox_sizes in (None, 1, [1, 1, 1]): img2 = resample_to_output(img, vox_sizes) assert_array_equal(img2.shape, (2, 3, 4)) assert_array_equal(img2.affine, np.eye(4)) assert_array_equal(img2.dataobj, data) img2 = resample_to_output(img, vox_sizes) # Check 2D works img_2d = Nifti1Image(data[0], np.eye(4)) for vox_sizes in (None, 1, (1, 1), (1, 1, 1)): img3 = resample_to_output(img_2d, vox_sizes) assert_array_equal(img3.shape, (3, 4, 1)) assert_array_equal(img3.affine, np.eye(4)) assert_array_equal(img3.dataobj, data[0][..., None]) # Even 1D img_1d = Nifti1Image(data[0, 0], np.eye(4)) img3 = resample_to_output(img_1d) assert_array_equal(img3.shape, (4, 1, 1)) assert_array_equal(img3.affine, np.eye(4)) assert_array_equal(img3.dataobj, data[0, 0][..., None, None]) # But 4D does not img_4d = Nifti1Image(data.reshape(2, 3, 2, 2), np.eye(4)) assert_raises(ValueError, resample_to_output, img_4d) # Run vox2vox_out tests, checking output shape, coordinate transform for in_shape, in_aff, vox, out_shape, out_aff in get_outspace_params(): # Allow for expansion of image shape from < 3D in_n_dim = len(in_shape) if in_n_dim < 3: in_shape = in_shape + (1,) * (3 - in_n_dim) if not vox is None: vox = vox + (1,) * (3 - in_n_dim) assert len(out_shape) == in_n_dim out_shape = out_shape + (1,) * (3 - in_n_dim) img = Nifti1Image(np.ones(in_shape), in_aff) out_img = resample_to_output(img, vox) assert_all_in(in_shape, in_aff, out_img.shape, out_img.affine) assert_equal(out_img.shape, out_shape) assert_almost_equal(out_img.affine, out_aff) # Check data is as expected with some transforms # Flip first axis out_img = resample_to_output(Nifti1Image(data, np.diag([-1, 1, 1, 1]))) assert_array_equal(out_img.dataobj, np.flipud(data)) # Subsample voxels out_img = resample_to_output(Nifti1Image(data, np.diag([4, 5, 6, 1]))) exp_out = spnd.affine_transform(data, [1/4, 1/5, 1/6], output_shape = (5, 11, 19)) assert_array_equal(out_img.dataobj, exp_out) # Unsubsample with voxel sizes out_img = resample_to_output(Nifti1Image(data, np.diag([4, 5, 6, 1])), [4, 5, 6]) assert_array_equal(out_img.dataobj, data) # A rotation to test nearest, order, cval rot_3 = from_matvec(euler2mat(np.pi / 4), [0, 0, 0]) rot_3_img = Nifti1Image(data, rot_3) out_img = resample_to_output(rot_3_img) exp_shape = (4, 4, 4) assert_equal(out_img.shape, exp_shape) exp_aff = np.array([[1, 0, 0, -2 * np.cos(np.pi / 4)], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) assert_almost_equal(out_img.affine, exp_aff) rzs, trans = to_matvec(np.dot(npl.inv(rot_3), exp_aff)) exp_out = spnd.affine_transform(data, rzs, trans, exp_shape) assert_almost_equal(out_img.dataobj, exp_out) # Order assert_almost_equal( resample_to_output(rot_3_img, order=0).dataobj, spnd.affine_transform(data, rzs, trans, exp_shape, order=0)) # Cval assert_almost_equal( resample_to_output(rot_3_img, cval=99).dataobj, spnd.affine_transform(data, rzs, trans, exp_shape, cval=99)) # Mode assert_almost_equal( resample_to_output(rot_3_img, mode='nearest').dataobj, spnd.affine_transform(data, rzs, trans, exp_shape, mode='nearest')) # out_class img_ni1 = Nifti2Image(data, np.eye(4)) img_ni2 = Nifti2Image(data, np.eye(4)) # Default is Nifti1Image assert_equal( resample_to_output(img_ni2).__class__, Nifti1Image) # Can be overriden assert_equal( resample_to_output(img_ni1, out_class=Nifti2Image).__class__, Nifti2Image) # None specifies out_class from input assert_equal( resample_to_output(img_ni2, out_class=None).__class__, Nifti2Image)
def as3d(aff): return from_matvec(aff[:3, :3], aff[:3, -1])
def test_resample_from_to(): # Test resampling from image to image / image space data = np.arange(24).reshape((2, 3, 4)) affine = np.diag([-4, 5, 6, 1]) img = Nifti1Image(data, affine) img.header['descrip'] = 'red shirt image' out = resample_from_to(img, img) assert_almost_equal(img.dataobj, out.dataobj) assert_array_equal(img.affine, out.affine) # Check resampling reverses effect of flipping axes # This will also test translations flip_ornt = np.array([[0, 1], [1, 1], [2, 1]]) for axis in (0, 1, 2): ax_flip_ornt = flip_ornt.copy() ax_flip_ornt[axis, 1] = -1 aff_flip_i = inv_ornt_aff(ax_flip_ornt, (2, 3, 4)) flipped_img = Nifti1Image(flip_axis(data, axis), np.dot(affine, aff_flip_i)) out = resample_from_to(flipped_img, ((2, 3, 4), affine)) assert_almost_equal(img.dataobj, out.dataobj) assert_array_equal(img.affine, out.affine) # A translation of one voxel on each axis trans_aff = from_matvec(np.diag([-4, 5, 6]), [4, -5, -6]) trans_img = Nifti1Image(data, trans_aff) out = resample_from_to(trans_img, img) exp_out = np.zeros_like(data) exp_out[:-1, :-1, :-1] = data[1:, 1:, 1:] assert_almost_equal(out.dataobj, exp_out) out = resample_from_to(img, trans_img) trans_exp_out = np.zeros_like(data) trans_exp_out[1:, 1:, 1:] = data[:-1, :-1, :-1] assert_almost_equal(out.dataobj, trans_exp_out) # Test mode with translation of first axis only # Default 'constant' mode first trans1_aff = from_matvec(np.diag([-4, 5, 6]), [4, 0, 0]) trans1_img = Nifti1Image(data, trans1_aff) out = resample_from_to(img, trans1_img) exp_out = np.zeros_like(data) exp_out[1:, :, :] = data[:-1, :, :] assert_almost_equal(out.dataobj, exp_out) # Then 'nearest' mode out = resample_from_to(img, trans1_img, mode='nearest') exp_out[0, :, :] = exp_out[1, :, :] assert_almost_equal(out.dataobj, exp_out) # Test order trans_p_25_aff = from_matvec(np.diag([-4, 5, 6]), [1, 0, 0]) trans_p_25_img = Nifti1Image(data, trans_p_25_aff) # Suprising to me, but all points outside are set to 0, even with NN out = resample_from_to(img, trans_p_25_img, order=0) exp_out = np.zeros_like(data) exp_out[1:, :, :] = data[1, :, :] assert_almost_equal(out.dataobj, exp_out) out = resample_from_to(img, trans_p_25_img) exp_out = spnd.affine_transform(data, [1, 1, 1], [-0.25, 0, 0], order=3) assert_almost_equal(out.dataobj, exp_out) # Test cval out = resample_from_to(img, trans_img, cval=99) exp_out = np.zeros_like(data) + 99 exp_out[1:, 1:, 1:] = data[:-1, :-1, :-1] assert_almost_equal(out.dataobj, exp_out) # Out class out = resample_from_to(img, trans_img) assert_equal(out.__class__, Nifti1Image) # By default, type of from_img makes no difference n1_img = Nifti2Image(data, affine) out = resample_from_to(n1_img, trans_img) assert_equal(out.__class__, Nifti1Image) # Passed as keyword arg out = resample_from_to(img, trans_img, out_class=Nifti2Image) assert_equal(out.__class__, Nifti2Image) # If keyword arg is None, use type of from_img out = resample_from_to(n1_img, trans_img, out_class=None) assert_equal(out.__class__, Nifti2Image) # to_img type irrelevant in all cases n1_trans_img = Nifti2Image(data, trans_aff) out = resample_from_to(img, n1_trans_img, out_class=None) assert_equal(out.__class__, Nifti1Image) # From 2D to 3D, error, the fixed affine is not invertible img_2d = Nifti1Image(data[:, :, 0], affine) assert_raises(AffineError, resample_from_to, img_2d, img) # 3D to 2D, we don't need to invert the fixed matrix out = resample_from_to(img, img_2d) assert_array_equal(out.dataobj, data[:, :, 0]) # Same for tuple as to_img imput out = resample_from_to(img, (img_2d.shape, img_2d.affine)) assert_array_equal(out.dataobj, data[:, :, 0]) # 4D input and output also OK data_4d = np.arange(24 * 5).reshape((2, 3, 4, 5)) img_4d = Nifti1Image(data_4d, affine) out = resample_from_to(img_4d, img_4d) assert_almost_equal(data_4d, out.dataobj) assert_array_equal(img_4d.affine, out.affine) # Errors trying to match 3D to 4D assert_raises(ValueError, resample_from_to, img_4d, img) assert_raises(ValueError, resample_from_to, img, img_4d)
def nifti2nipy(ni_img): """ Return NIPY image from NIFTI image `ni_image` Parameters ---------- ni_img : nibabel.Nifti1Image NIFTI image Returns ------- img : :class:`Image` nipy image Raises ------ NiftiError : if image is < 3D Notes ----- Lacking any other information, we take the input coordinate names for axes 0:7 to be ('i', 'j', 'k', 't', 'u', 'v', 'w'). If the image is 1D or 2D then we have a problem. If there's a defined (sform, qform) affine, this has 3 input dimensions, and we have to guess what the extra input dimensions are. If we don't have a defined affine, we don't know what the output dimensions are. For example, if the image is 2D, and we don't have an affine, are these X and Y or X and Z or Y and Z? In the presence of ambiguity, resist the temptation to guess - raise a NiftiError. If there is a time-like axis, name the input and corresponding output axis for the type of axis ('t', 'hz', 'ppm', 'rads'). Otherwise remove the 't' axis from both input and output names, and squeeze the length 1 dimension from the input data. If there's a 't' axis get ``toffset`` and put into affine at position [3, -1]. If ``dim_info`` is set coherently, set input axis names to 'slice', 'freq', 'phase' from ``dim_info``. Get the output spatial coordinate names from the 'scanner', 'aligned', 'talairach', 'mni' XYZ spaces (see :mod:`nipy.core.reference.spaces`). We construct the N-D affine by taking the XYZ affine and adding scaling diagonal elements from ``pixdim``. If the space units in NIFTI ``xyzt_units`` are 'microns' or 'meters' we adjust the affine to mm units, but warn because this might be a mistake. If the time units in NIFTI `xyzt_units` are 'msec' or 'usec', scale the time axis ``pixdim`` values accordingly. Ignore the intent-related fields for now, but warn that we are doing so if there appears to be specific information in there. """ hdr = ni_img.get_header() affine = ni_img.get_affine() # Affine will not be None from a loaded image, but just in case if affine is None: affine = hdr.get_best_affine() else: affine = affine.copy() data = ni_img.get_data() shape = list(ni_img.shape) ndim = len(shape) if ndim < 3: raise NiftiError("With less than 3 dimensions we cannot be sure " "which input and output dimensions you intend for " "the coordinate map. Please fix this image with " "nibabel or some other tool") # For now we only warn if intent is set to an unexpected value intent, _, _ = hdr.get_intent() if intent != 'none': warnings.warn('Ignoring intent field meaning "%s"' % intent, UserWarning) # Which space? world_label = hdr.get_value_label('sform_code') if world_label == 'unknown': world_label = hdr.get_value_label('qform_code') world_space = XFORM2SPACE.get(world_label, ncrs.unknown_space) # Get information from dim_info input_names3 = list('ijk') freq, phase, slice = hdr.get_dim_info() if not freq is None: input_names3[freq] = 'freq' if not phase is None: input_names3[phase] = 'phase' if not slice is None: input_names3[slice] = 'slice' # Add to mm scaling, with warning space_units, time_like_units = hdr.get_xyzt_units() if space_units in ('micron', 'meter'): warnings.warn('"%s" space scaling in NIFTI ``xyt_units field; ' 'applying scaling to affine, but this may not be what ' 'you want' % space_units, UserWarning) if space_units == 'micron': affine[:3] /= 1000. elif space_units == 'meter': affine[:3] *= 1000. input_cs3 = CS(input_names3, name='voxels') output_cs3 = world_space.to_coordsys_maker()(3) cmap3 = AT(input_cs3, output_cs3, affine) if ndim == 3: return Image(data, cmap3, {'header': hdr}) space_units, time_like_units = hdr.get_xyzt_units() units_info = TIME_LIKE_UNITS.get(time_like_units, None) n_ns = ndim - 3 ns_zooms = list(hdr.get_zooms()[3:]) ns_trans = [0] * n_ns ns_names = tuple('uvw') # Have we got a time axis? if (shape[3] == 1 and ndim > 4 and units_info is None): # Squeeze length 1 no-time axis shape.pop(3) ns_zooms.pop(0) ns_trans.pop(0) data = data.reshape(shape) n_ns -= 1 else: # have time-like if units_info is None: units_info = TIME_LIKE_UNITS['sec'] time_name = units_info['name'] if units_info['scaling'] != 1: ns_zooms[0] *= units_info['scaling'] if time_name == 't': # Get time offset ns_trans[0] = hdr['toffset'] ns_names = (time_name,) + ns_names output_cs = CS(ns_names[:n_ns]) input_cs = CS(ns_names[:n_ns]) aff = from_matvec(np.diag(ns_zooms), ns_trans) ns_cmap = AT(input_cs, output_cs, aff) cmap = cm_product(cmap3, ns_cmap, input_name=cmap3.function_domain.name, output_name=cmap3.function_range.name) return Image(data, cmap, {'header': hdr})
def resample(image, target, mapping, shape, order=3, mode='constant', cval=0.0): """ Resample `image` to `target` CoordinateMap Use a "world-to-world" mapping `mapping` and spline interpolation of a `order`. Here, "world-to-world" refers to the fact that mapping should be a callable that takes a physical coordinate in "target" and gives a physical coordinate in "image". Parameters ---------- image : Image instance image that is to be resampled. target : CoordinateMap coordinate map for output image. mapping : callable or tuple or array transformation from target.function_range to image.coordmap.function_range, i.e. 'world-to-world mapping'. Can be specified in three ways: a callable, a tuple (A, b) representing the mapping y=dot(A,x)+b or a representation of this mapping as an affine array, in homogeneous coordinates. shape : sequence of int shape of output array, in target.function_domain. order : int, optional what order of interpolation to use in ``scipy.ndimage``. mode : str, optional Points outside the boundaries of the input are filled according to the given mode ('constant', 'nearest', 'reflect' or 'wrap'). Default is 'constant'. cval : scalar, optional Value used for points outside the boundaries of the input if mode='constant'. Default is 0.0. Returns ------- output : Image instance Image has interpolated data and output.coordmap == target. """ if not callable(mapping): if type(mapping) is type(()): mapping = from_matvec(*mapping) # image world to target world mapping TW2IW = AffineTransform(target.function_range, image.coordmap.function_range, mapping) else: if isinstance(mapping, AffineTransform): TW2IW = mapping else: TW2IW = CoordinateMap(target.function_range, image.coordmap.function_range, mapping) # target voxel to image world mapping TV2IW = compose(TW2IW, target) # CoordinateMap describing mapping from target voxel to # image world coordinates if not isinstance(TV2IW, AffineTransform): # interpolator evaluates image at values image.coordmap.function_range, # i.e. physical coordinates rather than voxel coordinates grid = ArrayCoordMap.from_shape(TV2IW, shape) interp = ImageInterpolator(image, order=order, mode=mode, cval=cval) idata = interp.evaluate(grid.transposed_values) del(interp) else: # it is an affine transform, but, what if we compose? TV2IV = compose(image.coordmap.inverse(), TV2IW) if isinstance(TV2IV, AffineTransform): # still affine A, b = to_matvec(TV2IV.affine) idata = affine_transform(image.get_data(), A, offset=b, output_shape=shape, order=order, mode=mode, cval=cval) else: # not affine anymore interp = ImageInterpolator(image, order=order, mode=mode, cval=cval) grid = ArrayCoordMap.from_shape(TV2IV, shape) idata = interp.evaluate(grid.values) del(interp) return Image(idata, copy.copy(target))
def xyz_affine(coordmap, name2xyz=None): """ Return (4, 4) affine mapping voxel coordinates to XYZ from `coordmap` If no (4, 4) affine "makes sense"(TM) for this `coordmap` then raise errors listed below. A (4, 4) affine makes sense if the first three output axes are recognizably X, Y, and Z in that order AND they there are corresponding input dimensions, AND the corresponding input dimensions are the first three input dimension (in any order). Thus the input axes have to be 3D. Parameters ---------- coordmap : ``CoordinateMap`` instance name2xyz : None or mapping, optional Object such that ``name2xyz[ax_name]`` returns 'x', or 'y' or 'z' or raises a KeyError for a str ``ax_name``. None means use module default. Returns ------- xyz_aff : (4,4) array voxel to X, Y, Z affine mapping Raises ------ SpaceTypeError : if this is not an affine coordinate map AxesError : if not all of x, y, z recognized in `coordmap` output, or they are in the wrong order, or the x, y, z axes do not correspond to the first three input axes. AffineError : if axes dropped from the affine contribute to x, y, z coordinates. Notes ----- We could also try and "make sense" (TM) of a coordmap that had X, Y and Z outputs, but not in that order, nor all in the first three axes. In that case we could just permute the affine to get the output order we need. But, that could become confusing if the returned affine has different output coordinates than the passed `coordmap`. And it's more complicated. So, let's not do that for now. Examples -------- >>> cmap = vox2mni(np.diag([2,3,4,5,1])) >>> cmap AffineTransform( function_domain=CoordinateSystem(coord_names=('i', 'j', 'k', 'l'), name='voxels', coord_dtype=float64), function_range=CoordinateSystem(coord_names=('mni-x=L->R', 'mni-y=P->A', 'mni-z=I->S', 't'), name='mni', coord_dtype=float64), affine=array([[ 2., 0., 0., 0., 0.], [ 0., 3., 0., 0., 0.], [ 0., 0., 4., 0., 0.], [ 0., 0., 0., 5., 0.], [ 0., 0., 0., 0., 1.]]) ) >>> xyz_affine(cmap) array([[ 2., 0., 0., 0.], [ 0., 3., 0., 0.], [ 0., 0., 4., 0.], [ 0., 0., 0., 1.]]) """ if name2xyz is None: name2xyz = known_names try: affine = coordmap.affine except AttributeError: raise SpaceTypeError('Need affine coordinate map') order = xyz_order(coordmap.function_range, name2xyz) if order[:3] != [0, 1, 2]: raise AxesError('First 3 output axes must be X, Y, Z') # Check equivalent input axes ornt = io_orientation(affine) if set(ornt[:3, 0]) != set((0, 1, 2)): raise AxesError('First 3 input axes must correspond to X, Y, Z') # Check that dropped dimensions don't provide xyz coordinate info extra_cols = affine[:3,3:-1] if not np.allclose(extra_cols, 0): raise AffineError('Dropped dimensions not orthogonal to xyz') return from_matvec(affine[:3,:3], affine[:3,-1])