예제 #1
0
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)
예제 #2
0
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')
예제 #3
0
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'
예제 #4
0
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
예제 #5
0
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)
예제 #6
0
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()
예제 #7
0
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)
예제 #8
0
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)
예제 #9
0
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
예제 #10
0
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
예제 #11
0
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
예제 #12
0
    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 
예제 #13
0
def test_surf_edges():
    td = get_testdir()
    ins = Surface(op.join(td, 'in.surf.gii'))
    e = ins.edges()