def test_hemi_init(): td = get_testdir() ins = Surface(op.join(td, 'in.surf.gii')) outs = Surface(op.join(td, 'out.surf.gii')) hemi = toblerone.Hemisphere(ins, outs, 'L') hemi2 = toblerone.Hemisphere(ins, outs, 'L') assert id(hemi.inSurf.points) != id(hemi2.inSurf.points)
def test_convert(): td = get_testdir() s = Surface(op.join(td, 'in.surf.gii')) s.save('test.vtk') s2 = Surface('test.vtk') assert np.allclose(s.points, s2.points) os.remove('test.vtk')
def test_discriminated_laplacian(): td = get_testdir() s = Surface(op.join(td, 'in.surf.gii')) spc = toblerone.ImageSpace(op.join(td, 'ref.nii.gz')) for w in range(4): lap = s.discriminated_laplacian(spc, distance_weight=w, in_weight=100) assert (lap[np.diag_indices(lap.shape[0])] < 0).min(), 'positive diagonal'
def test_proj_properties(): td = get_testdir() ins = Surface(op.join(td, 'in.surf.gii')) outs = Surface(op.join(td, 'out.surf.gii')) spc = toblerone.ImageSpace(op.join(td, 'ref.nii.gz')) hemi = toblerone.Hemisphere(ins, outs, 'L') proj = toblerone.projection.Projector(hemi, spc) assert proj.n_hemis == 1 assert 'L' in proj.hemi_dict assert hemi.midsurface() assert proj['LPS'] hemi2 = toblerone.Hemisphere(ins, outs, 'R') proj = toblerone.projection.Projector([hemi, hemi2], spc) assert proj.n_hemis == 2 assert proj['RWS'] assert ('L' in proj.hemi_dict) & ('R' in proj.hemi_dict) for h, s in zip(proj.iter_hemis, ['L', 'R']): assert h.side == s assert proj.n_surf_points == 2 * ins.n_points
def test_structure(): td = get_testdir() spc = toblerone.ImageSpace(op.join(td, 'ref.nii.gz')) ins = Surface(op.join(td, 'in.surf.gii'), name='L') s2r = np.identity(4) fracs = pvestimation.structure(surf=op.join(td, 'in.surf.gii'), ref=spc, struct2ref=s2r, cores=1, flirt=True, coords='fsl', struct=op.join(td, 'ref.nii.gz')) superfactor = 10 spc_high = spc.resize_voxels(1.0 / superfactor) voxelised = np.zeros(spc_high.size.prod(), dtype=NP_FLOAT) ins.index_on(spc_high) ins.indexed.voxelised = ins.voxelise(spc_high, 1) reindex_in = ins.reindexing_filter(spc_high) voxelised[reindex_in[1]] = ( ins.indexed.voxelised[reindex_in[0]]).astype(NP_FLOAT) voxelised = voxelised.reshape(spc_high.size) truth = sum_array_blocks(voxelised, 3 * [superfactor]) / superfactor**3 np.testing.assert_array_almost_equal(fracs, truth, 2)
def test_mesh_laplacian(): td = get_testdir() s = Surface(op.join(td, 'in.surf.gii')) try: s.mesh_laplacian(-1) except Exception as e: assert isinstance( e, ValueError), 'negative distance weight should give ValueError' for w in range(4): lap = s.mesh_laplacian(distance_weight=w) assert (lap[np.diag_indices(lap.shape[0])] < 0).min(), 'positive diagonal' outs = Surface(op.join(td, 'out.surf.gii')) spc = toblerone.ImageSpace(op.join(td, 'ref.nii.gz')) hemi = toblerone.Hemisphere(s, outs, 'L') hemi2 = toblerone.Hemisphere(s, outs, 'R') proj = toblerone.projection.Projector([hemi, hemi2], spc) for w in range(4): lap = proj.mesh_laplacian(w) n = proj.hemi_dict['L'].n_points assert not slice_sparse(lap, slice(0, n), slice(n, 2 * n)).nnz assert not slice_sparse(lap, slice(n, 2 * n), slice(0, n)).nnz assert not (lap[diag_indices(2 * n)] > 0).any()
def test_projector_rois(): td = get_testdir() ins = op.join(td, 'in.surf.gii') outs = op.join(td, 'out.surf.gii') hemi = toblerone.Hemisphere(ins, outs, 'L') spc = toblerone.ImageSpace(op.join(td, 'ref.nii.gz')) fracs = nibabel.load(op.join(td, 'sph_fractions.nii.gz')).get_fdata() hemi.pvs = fracs.reshape(-1, 3) puta = Surface.manual(0.25 * hemi.inSurf.points, hemi.inSurf.tris, 'L_Puta') rois = {'L_Puta': puta} proj = toblerone.projection.Projector(hemi, spc, rois=rois, cores=8) ndata = np.ones(proj.n_nodes) ndata[-1] = 2 vdata = proj.node2vol(ndata, True) spc.save_image(vdata, 'n2v.nii.gz') ndata = proj.vol2node(vdata, True) print(ndata)
def convert_surface(): """ CLI for converting surface formats """ parser = CommonParser( 'surf', 'coords', 'struct', 'out', description="""Convert a surface file (.white/.pial/.vtk/.surf.gii). NB FreeSurfer files will have the c_ras offset automatically applied during conversion.""") parsed = parser.parse_args() if parsed.coords == 'fsl' and parsed.struct: insurf = Surface(parsed.surf, 'fsl', parsed.struct) else: insurf = Surface(parsed.surf) insurf.save(parsed.out)
def test_adjacency(): td = get_testdir() s = Surface(op.join(td, 'in.surf.gii')) for w in range(4): adj = s.adjacency_matrix(w) assert not (adj.data < 0).any(), 'negative value in adjacency matrix' try: s.adjacency_matrix(-1) except Exception as e: assert isinstance( e, ValueError), 'negative distance weight should give ValueError' outs = Surface(op.join(td, 'out.surf.gii')) spc = toblerone.ImageSpace(op.join(td, 'ref.nii.gz')) hemi = toblerone.Hemisphere(s, outs, 'L') hemi2 = toblerone.Hemisphere(s, outs, 'R') proj = toblerone.projection.Projector([hemi, hemi2], spc) n = proj.hemi_dict['L'].n_points for w in range(4): adj = proj.adjacency_matrix(w) assert not slice_sparse(adj, slice(0, n), slice(n, 2 * n)).nnz assert not slice_sparse(adj, slice(n, 2 * n), slice(0, n)).nnz
def structure(ref, struct2ref, **kwargs): """ Estimate PVs for a structure defined by a single surface. All arguments are kwargs. Required args: ref (str/regtricks ImageSpace): voxel grid in which to estimate PVs. struct2ref (str/np.array/rt.Registration): registration between space of surface and reference (see -flirt and -stuct). Use 'I' for identity. surf (str): path to surface (see coords argument below) Optional args: flirt (bool): denoting struct2ref is FLIRT transform; if so, set struct. coords (str): convention by which surface is defined: default is 'world' (mm coords), for FIRST surfaces set as 'fsl' and provide struct argument struct (str): path to structural image from which surfaces were derived cores (int): number of cores to use, default 8 supersample (int/array): single or 3 values, supersampling factor Returns: (np.array) PV image, sized equal to reference space """ # Check we either have a surface object or path to one if not bool(kwargs.get('surf')): raise RuntimeError( "surf kwarg must be a Surface object or path to one") coords = kwargs.get('coords', 'world') if coords == 'fsl' and not kwargs.get('struct'): raise RuntimeError("Structural image must be supplied for FIRST surfs") if type(kwargs['surf']) is str: surf = Surface(kwargs['surf'], name=op.split(kwargs['surf'])[1]) if kwargs.get('coords', 'world') == 'fsl': struct_spc = ImageSpace(kwargs['struct']) surf = surf.transform(struct_spc.FSL2world) elif type(kwargs['surf']) is not Surface: raise RuntimeError( "surf kwarg must be a Surface object or path to one") else: surf = kwargs['surf'] # Either create local copy of ImageSpace object or init from path if isinstance(ref, ImageSpace): ref_space = copy.deepcopy(ref) else: ref_space = ImageSpace(ref) if kwargs.get('supersample') is None: supersampler = np.maximum(np.floor(ref_space.vox_size.round(1) / 0.75), 1).astype(np.int32) else: supersampler = kwargs.get('supersample') * np.ones(3) pvs = estimators._structure(surf, ref_space, struct2ref, supersampler, bool(kwargs.get('ones')), kwargs['cores']) return pvs
def complete(ref, struct2ref, **kwargs): """ Estimate PVs for cortex and all structures identified by FIRST within a reference image space. Use FAST to fill in non-surface PVs. All arguments are kwargs. Required args: ref (str/regtricks ImageSpace): voxel grid in which to estimate PVs. struct2ref (str/np.array/rt.Registration): registration between space of surface and reference (see -flirt and -stuct). Use 'I' for identity. fslanat: path to fslanat directory. This REPLACES firstdir/fastdir/struct. firstdir (str): FIRST directory in which .vtk surfaces are located fastdir (str): FAST directory in which _pve_0/1/2 are located struct (str): path to structural image from which FIRST surfaces were dervied fsdir (str): FreeSurfer subject directory, OR: LWS/LPS/RWS/RPS (str): paths to individual surfaces (L/R white/pial) Optional args: flirt (bool): denoting struct2ref is FLIRT transform; if so, set struct. coords (str): convention by which surface is defined: default is 'world' (mm coords), for FIRST surfaces set as 'fsl' and provide struct argument struct (str): path to structural image from which surfaces were derived cores (int): number of cores to use, default 8 supersample (int/array): single or 3 values, supersampling factor Returns: (dict) PVs associated with each individual structure and also the overall combined result ('stacked') """ print("Estimating PVs for", ref.file_name) # If anat dir then various subdirs are loaded by @enforce_common_args # If not then direct load below if not bool(kwargs.get('fsdir')): if not all([bool(kwargs.get(k)) for k in ['LWS', 'LPS', 'RWS', 'RPS']]): raise RuntimeError("If fsdir not given, " + "provide paths for LWS,LPS,RWS,RPS") if not bool(kwargs.get('fslanat')): if not (bool(kwargs.get('fastdir')) and bool(kwargs.get('firstdir'))): raise RuntimeError( "If not using anat dir, fastdir/firstdir required") # Resample FASTs to reference space. Then redefine CSF as 1-(GM+WM) fast_paths = utils._loadFASTdir(kwargs['fastdir']) fast_spc = fast_paths['FAST_GM'] fast = np.stack([ nibabel.load(fast_paths[f'FAST_{p}']).get_fdata() for p in ['GM', 'WM'] ], axis=-1) fasts_transformed = rt.Registration(struct2ref).apply_to_array( fast, fast_spc, ref) output = dict(FAST_GM=fasts_transformed[..., 0], FAST_WM=fasts_transformed[..., 1]) output['FAST_CSF'] = np.maximum( 0, 1 - (output['FAST_WM'] + output['FAST_GM'])) # Process subcortical structures first. FIRSTsurfs = utils._loadFIRSTdir(kwargs['firstdir']) subcortical = [] struct_spc = ImageSpace(kwargs['struct']) for name, surf in FIRSTsurfs.items(): s = Surface(surf, name) s = s.transform(struct_spc.FSL2world) subcortical.append(s) disp = "Structures found: " + ", ".join([s.name for s in subcortical] + ['Cortex']) print(disp) # To estimate against each subcortical structure, we apply the following # partial func to each using a map() call. Carry kwargs from this func desc = 'Subcortical structures' estimator = functools.partial(__structure_wrapper, ref=ref, struct2ref=struct2ref, **kwargs) # This is equivalent to a map(estimator, subcortical) call # All the extra stuff (tqdm etc) is used for progress bar results = [ pv for _, pv in tqdm.tqdm(enumerate(map(estimator, subcortical)), total=len(subcortical), desc=desc, bar_format=core.BAR_FORMAT, ascii=True) ] output.update(dict(zip([s.name for s in subcortical], results))) # Now do the cortex, then stack the whole lot ctx = cortex(ref=ref, struct2ref=struct2ref, **kwargs) for i, t in enumerate(['_GM', '_WM', '_nonbrain']): output['cortex' + t] = (ctx[:, :, :, i]) stacked = estimators.stack_images( {k: v for k, v in output.items() if k != 'BrStem'}) output['GM'] = stacked[:, :, :, 0] output['WM'] = stacked[:, :, :, 1] output['nonbrain'] = stacked[:, :, :, 2] output['stacked'] = stacked return output
def load(cls, path): """ Load Projector from path in HDF5 format. This is useful for performing repeated analyses with the same voxel grid and cortical surfaces. """ f = h5py.File(path, 'r') p = cls.__new__(cls) # Recreate the reference ImageSpace first p.spc = ImageSpace.manual(f['ref_spc_vox2world'][()], f['ref_spc_size'][()]) if 'ref_spc_fname' in f: p.spc.fname = f['ref_spc_fname'][()] n_vox = p.spc.size.prod() # Now read out hemisphere specific properties p._hemi_pvs = [] p.vox_tri_mats = [] p.vtx_tri_mats = [] p.hemi_dict = {} p._roi_pvs = {} for s in SIDES: hemi_key = f"{s}_hemi" if hemi_key in f: # Read out the surfaces, create the Hemisphere ins, outs = [ Surface.manual( f[hemi_key][f'{s}{n}S_points'][()], f[hemi_key][f'{s}{n}S_tris'][()], f'{s}{n}S') for n in ['W', 'P'] ] p.hemi_dict[s] = Hemisphere(ins, outs, s) # Read out the PVs array for the hemi p._hemi_pvs.append(f[hemi_key][f"{s}_pvs"][()]) # Recreate the sparse voxtri and vtxtri matrices. # They are stored as a 3 x N array, where top row # is row indices, second is column, then data voxtri = f[hemi_key][f"{s}_vox_tri"][()] assert voxtri.shape[0] == 3, 'expected 3 rows' voxtri = sparse.coo_matrix( (voxtri[2,:], (voxtri[0,:], voxtri[1,:])), shape=(n_vox, ins.tris.shape[0])) p.vox_tri_mats.append(voxtri.tocsr()) # Same convention as above vtxtri = f[hemi_key][f"{s}_vtx_tri"][()] assert vtxtri.shape[0] == 3, 'expected 3 rows' vtxtri = sparse.coo_matrix( (vtxtri[2,:], (vtxtri[0,:], vtxtri[1,:])), shape=(ins.n_points, ins.tris.shape[0])) p.vtx_tri_mats.append(vtxtri.tocsr()) if "subcortical_pvs" in f: g = f["subcortical_pvs"] for k in sorted(g.keys()): p._roi_pvs[k] = g[k][()] return p
def test_surf_edges(): td = get_testdir() ins = Surface(op.join(td, 'in.surf.gii')) e = ins.edges()