Exemplo n.º 1
0
def savemgz(arr, filename, mgzobj=None, affine=None, header=None):
    '''
    savemgz(arr, filename, mgzobj=None, affine=None, header=None):

    save 3D into a full mgz filename using <filename>. We used
    the affine and header information from <mgzobj>.

    If <affine> or <header> is supplied, they will overwrite information from the <mgzobj>

    20180720 <filename> can accept a path-like object

    '''
    from nibabel import save, MGZImage
    from numpy import ndarray
    from RZutilpy.system import makedirs, rzpath

    # check input, do we need this? I think mgz also accept 1d surface number??
    assert isinstance(arr, ndarray) and (3<=arr.ndim<=4), 'Please input an ndarray!'

    # make the dir if it does not exist
    filename = rzpath(filename) if ~isinstance(filename,rzpath) else filename

    makedirs(filename)

    assert isinstance(mgzobj, Nifti1Image) if affine is None or header is None, \
    'affine or header is none, you must supply mgzobj'

    # get affine and header information
    affine = mgzobj.affine if affine is None
    header = mgzobj.header if header is None

    # save the file
    save(MGZImage(arr, affine, header), filename.str)
Exemplo n.º 2
0
def savegifti(arr, filename, giftiobj, intent=2005):
    '''
    savegifti(arr, filename, giftiobj, intent=2005)::

    save 1D or 2D <array> into a full gifti filename using <filename>. We used
    the affine and header information from <giftiobj>.

    Note that the file name should have '.gii'. nibabel cannot read '.gii.gz' file

    This is different from readgifti, we must supply an reference <giftiobj> to save the data


    <intent> is a list of integers with intent numbers for each column of arr. Typically
    we can treat it as a normal morph data, so default is 2005. For multiple columns
    in <arr>, you should input a list such as [2005, 2005, 2005]. For full list intent code.
    check the website below:

    https://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/group__NIFTI1__INTENT__CODES.html/document_view


    20190409 RZ created it

    '''

    from nibabel import save
    from nibabel.gifti import GiftiDataArray
    from numpy import ndarray
    from RZutilpy.system import makedirs, Path
    from nibabel.gifti.giftiio import write as giftiwrite

    # check input
    assert isinstance(
        arr,
        ndarray) and (1 <= arr.ndim <= 2), 'Please input 1d or 2d an ndarray!'

    # make the dir if it does not exist
    filename = Path(filename) if ~isinstance(filename, Path) else filename
    # add .gii suffix if not supplied
    filename = Path(filename.str +
                    '.gii') if filename.suffix != '.gii' else filename

    makedirs(filename)  # note here we add a os.sep

    # make darrays for each column
    if arr.ndim == 1:
        giftiobj.darrays[0].data = arr
        giftiobj.darrays[0].intent = intent
    else:
        assert len(intent) == arr.shape[
            1], 'data column number not equal to intent number!'
        darray_list = [giftiobj.darrays[0] for _ in range(arr.shape[1])]
        for i in range(arr.shape[1]):
            darray_list[i].data = arr[:, i]
            darray_list[i].intent = intent[i]
        giftiobj.darrays = darray_list

    save(giftiobj, filename.str)
Exemplo n.º 3
0
def savejson(filename, varsdict):
    '''
    def savejson(filename, varsdict):

    <filename>: a string 'test.json', or 'test', can have full path
    <varsdict>: a dict to save


    '''

    from json import dump
    from RZutilpy.system import Path, makedirs

    filename = Path(filename)
    if filename.suffix != 'json':
        filename = Path(filename.str + '.json')
    makedirs(filename)

    # open a file
    f = open('{}.json'.format(filename.strnosuffix), 'w')
    dump(varsdict, f, indent=4)
    f.close()
Exemplo n.º 4
0
def savenifti(arr, filename, niftiobj=None, affine=None, header=None):
    '''
    savenifti(arr, filename, niftiobj=None, affine=None, header=None):

    save 3D or 4D <array> into a full nifti filename using <filename>. We used
    the affine and header information from <niftiobj>.

    Note that the file name can have either the ext as '.nii' or '.nii.gz'. nibabel
    can take care of it.

    If <affine> or <header> is supplied, they will overwrite information from the <niftiobj>

    20180622 RZ add support for affine and header

    '''
    from nibabel import save, Nifti1Image
    from numpy import ndarray
    from RZutilpy.system import makedirs, Path

    # check input
    assert isinstance(
        arr,
        ndarray) and (3 <= arr.ndim <= 4), 'Please input 3d or 4d an ndarray!'

    # make the dir if it does not exist
    filename = Path(filename) if ~isinstance(filename, Path) else filename
    makedirs(filename)  # note here we add a os.sep

    if affine is None or header is None:
        assert isinstance(
            niftiobj,
            Nifti1Image), 'affine or header is none, you must supply niftiobj'

    # get affine and header information
    affine = niftiobj.affine.copy() if affine is None else affine.copy()
    header = niftiobj.header.copy() if header is None else header.copy()

    save(Nifti1Image(arr, affine, header), filename.str)
Exemplo n.º 5
0
def jupyter2pdf(jupyterFile, outputDir=None):
    '''
    convert jupyter2pdf using pdfkit.

    <jupyterFile>: a string or a Path-lib object

    <outputDir>: outputDir, default:cwd

    '''
    from RZutilpy.system import Path, makedirs
    import pdfkit

    jupyterFile = Path(jupyterFile)
    outputDir = Path.cwd() if outputDir is None else Path(outputDir)
    makedirs(outputDir) # create the dir if no exists

    name = jupyterFile.pstem

    !jupyter nbconvert --to html {name}

    htmlFile = Path(jupyterFile.strnosuffix+'.html')
    pdfkit.from_file(htmlFile.str, (outputDir/(name+'.pdf')).str)
    !rm -f {name}.html
Exemplo n.º 6
0
def writemgz(subjectid, name, vals, hemi, outputdir=None, surfsuffix=None):
    '''
    function cvnwritemgz(subjectid,name,vals,hemi,outputdir,surfsuffix)

    <subjectid> is like 'C0041' (can be 'fsaverage')
    <name> is a string, filename
    <vals> is a vector of values for the surface
    <hemi> is 'lh' or 'rh'
    <outputdir> (optional) is the directory to write the file to.
        Default is cvnpath('freesurfer')/<subjectid>/surf/
    <surfsuffix> (optional) is a suffix to tack onto <hemi>, e.g., 'DENSETRUNCpt'.
        Special case is 'orig' which is equivalent to ''.
        Default: 'orig'.

    Write a file like <hemi><surfsuffix>.<name>.mgz.


    # ======================== RZ notes =======================================
    Note that, unlike cvnwritemgz.m, we do not mangle the headerinformation (see code).
    This is due to inherent difference between nibabel and matlab MRIread.m
    history:
        20180714 RZ start to use pathlib object, <outputdir> now accept path-like object

    '''
    from RZutilpy.cvnpy import cvnpath
    from RZutilpy.system import makedirs
    import os
    import nibabel as nib

    # calc
    fsdir = (Path(cvnpath('freesurfer')) / subjectid).str

    # input
    outputdir = (Path(fsdir) / 'surf').str if outputdir is None else outputdir

    if surfsuffix is None:
        surfsuffix = 'orig'

    # prep
    makedirs(outputdir)

    # load template
    file0 = (Path(fsdir) / 'surf' / '{}.w-g.pct.mgh'.format(hemi)).str

    if not Path(file0).exists(
    ):  # fsaverage doesn't have the above file, so let's use this one:
        file0 = (Path(fsdir) / 'surf' /
                 '{}.orig.avg.area.mgh'.format(hemi)).str

    fsmgh = nib.load(file0)

    # calc
    suffstr = '' if surfsuffix == 'orig' else surfsuffix

    file = (Path(outputdir) / f'{hemi}{suffstr}.{name}.mgz').str

    ## mangle the field, did this in cvnwritemgz but no need to here.
    # n = numel(vals);
    # # mangle
    # fsmgh.fspec = file;
    # fsmgh.vol = flatten(vals);
    # fsmgh.volsize = [1 n 1];
    # fsmgh.width = n;
    # fsmgh.nvoxels = n;

    # write
    nib.save(vals.flatten(), file, fsmgh)
Exemplo n.º 7
0
def collectT1s(subjectid, dataloc, gradfile=None, str0='T1w', wantskip=True):
    '''
    def collectT1s(subjectid,dataloc,gradfile,str0,wantskip):

    <subjectid> a str, like 'C0001'
    <dataloc> is a scan directory like '/home/stone-ext1/fmridata/20151014-ST001-wynn,subject1'
        or a list of scan directories
    <gradfile>  is gradunwarp's scanner or coeff file (e.g. 'prisma').
        Default is None which means do not perform gradunwarp.
    <str0> a str is the filename match thing. Default: 'T1w'.
    <wantskip> boolean, is whether to treat as pairs and use the second of each pair.
        (The idea is that the second of each pair might be homogeneity-corrected.)
        Default: True.

    Within the specified scan directories (in the order as given),
    find all of the T1 (or whatever) DICOM directories, and if <wantskip>,
    ignoring the 1st of each pair and keeping the 2nd of each pair.
    Then convert these DICOM directories to NIFTI files.
    If <gradfile> is specified, we additionally run fslreorient2std
    and gradunwarp.

    We return a cell vector of the final NIFTI paths,
    preserving the order. Note that filenames will be different
    depending on whether <gradfile> is used.

    See code for specific assumptions.
    we use muliprocessin.Pool for speed-ups!

    To do:
        - consider using nipype wrapper of dcm2nii??
        -

    history:
        - 20180720 <dataloc> now can accept an Path object
        - 20180620 RZ created it based on cvncollectT1s.m
    '''
    from RZutilpy.cvnpy import cvnpath
    from RZutilpy.system import makedirs, unix_wrapper, Path
    from RZutilpy.rzio import matchfiles
    import re
    from pathos.multiprocessing import Pool  # note 1st letter uppercase
    from os.path import join

    dir0 = (Path(cvnpath('anatomicals')) /
            subjectid).str  # cvnpath output pathlib objects

    # make subject anatomical directory
    makedirs(dir0)

    # massage, a single string input
    if ~isinstance(dataloc, list):
        dataloc = [dataloc]

    # figure out T1 DICOM directories [ASSUME THAT THERE ARE AN EVEN NUMBER OF DIRECTORIES IN EACH SCAN SESSION]
    t1files = []
    for p in dataloc:
        t1files0 = matchfiles((Path(p) / 'dicom' / '*%s*'.format(str0)).str)
        if wantskip:
            assert len(t1files0) % 2 == 0
            t1files0 = t1files0[1::2]
        # collect them up
        t1files = t1files + t1files0

    # convert dicoms to NIFTIS and get the filenames
    files = []  # a list of path-like object
    for p in t1files:
        result = unix_wrapper('dcm2nii -o %s -r N -x N %s'.format(dir0, p))
        temp = re.findall(r'GZip[.]*(\w+.nii.gz)', result)
        files.append((Path(dir0) / temp[0]).str)

    # perform gradunwarp
    if gradfile:
        # run Keith's fslreorient2std on each
        [unix_wrapper('fslreorient2std_inplace %s'.format(p)) for p in files]

        # then do grandunwarp
        # extract filename and give new files
        assert all([Path(p).suffixesstr == '.nii.gz' for p in files])
        file0 = [
            Path(p).strnosuffix for p in files
        ]  # a list of string, remove multiple suffixes in pathlib objects
        newfiles = [
            (Path(p).parent / (Path(p).pstem + '_gradunwarped.nii.gz')).str
            for p in file0
        ]  # a list of path-like obj

        # do the grandunwarp
        def gradunwarp(filename):
            # filename is a string
            unix_wrapper('gradunwarp -w %s_warp.nii.gz -m %s_mask.nii.gz %s.nii.gz %s_gradunwarped.nii.gz %s' \
                % (str(filename),str(filename),str(filename),str(filename), gradfile))

        # if __name__ == '__main__':
        #     with Pool(12) as p:
        #         p.imap_unordered(gradunwarp, file0)
        with Pool(12) as p:
            p.imap_unordered(gradunwarp, file0)
        #files = list(newfiles)

    return files
Exemplo n.º 8
0
def findminoutlier(funcfiles, outputDir=None):
    '''
    def findminoutlier(func, outputDir=None):

    Find the minimal outlier from a set of runs of functional data. This function is
    derived direct from the script generate from afni_proc. We want to isolate this part
    so we can use the minimal outlier to perform epi2anat alignment separately

    Input:
        <funcfiles>: can be
            (1): a string, a wildercard to matchfile multiple functional files
            (2): a list of strings of fullfile paths
        <outputDir>: output directory, can be a string or a path object
    Output:

    '''
    from RZutilpy.rzio import matchfiles
    from RZutilpy.system import unix_wrapper, Path, makedirs
    from os import getcwd
    from numpy import loadtxt

    funcfiles = matchfiles(funcfiles) if isinstance(funcfiles,
                                                    str) else funcfiles
    assert len(funcfiles) >= 0, 'can not find data files!'

    nRuns = len(funcfiles)
    nVols_list = [
        int(unix_wrapper(f'3dinfo -nt {i}', verbose=0, wantreturn=True))
        for i in funcfiles
    ]

    # deal with Path
    outputDir = Path(getcwd()) if outputDir is None else outputDir
    outputDir = Path(outputDir)
    makedirs(outputDir)  # create outputDir if not exist

    # remove existing files
    unix_wrapper(f'rm {outputDir}/out.pre_ss_warn.txt', verbose=0)
    unix_wrapper(f'rm {outputDir}/outcount.r**.1D', verbose=0)
    unix_wrapper(f'rm {outputDir}/out.min_outlier.txt', verbose=0)
    unix_wrapper(f'rm {outputDir}/vr_base_min_outlier*', verbose=0)

    # make outcount files
    pressfile = open(f'{outputDir}/out.pre_ss_warn.txt', 'w')
    for i in range(nRuns):
        unix_wrapper(
            f'3dToutcount -automask -fraction -polort 2 -legendre                     \
                {funcfiles[i]} > {outputDir}/outcount.r{i+1:02d}.1D')

        outcount = loadtxt(f'{outputDir}/outcount.r{i+1:02d}.1D')
        if outcount[0] >= 0.4:
            print(
                f"** TR #0 outliers: possible pre-steady state TRs in run {i+1:02d}",
                file=pressfile)
    pressfile.close()
    # combine all files
    unix_wrapper(
        f'cat {outputDir}/outcount.r*.1D > {outputDir}/outcount_rall.1D')

    # find the which run and which volume
    outcountfile = loadtxt(f'{outputDir}/outcount_rall.1D')
    miniindex = outcountfile.argmin()
    i = 0
    while i <= nRuns:
        if miniindex < sum(nVols_list[:i + 1]):
            minoutrun = i + 1
            minouttr = miniindex - sum(nVols_list[:i])
            break
        i = i + 1

    min_outlier_vol_file = open(f'{outputDir}/out.min_outlier.txt', 'w')
    print(f'min outlier: run {minoutrun}, TR {minouttr}',
          file=min_outlier_vol_file)
    min_outlier_vol_file.close()

    # extract this vr_min_outlier
    unix_wrapper(
        f'3dbucket -prefix {outputDir}/vr_base_min_outlier "{funcfiles[minoutrun-1]}[{minouttr}]" '
    )
    print(f'min outlier: run {minoutrun}, TR {minouttr}')
Exemplo n.º 9
0
# write video to different frames
from moviepy.editor import VideoFileClip
from RZutilpy.system import rzpath, makedirs

# the file name you want to change
video_file = 'Interstellar - Ending Scene 1080p HD.mp4'

video_folder = '/Users/ruyuan/Documents/Code_git/samplevideo/'
video_file_full = rzpath(video_folder + video_file)
makedirs(video_file_full.strnosuffix)
imagesequence_folder = rzpath(video_file_full.strnosuffix)

# create the videoclip
clip = VideoFileClip(video_file_full.str)

# write image sequences
clip.write_images_sequence((imagesequence_folder / 'frame%04d.png').str,
                           verbose=True,
                           progress_bar=True)
Exemplo n.º 10
0
def cvnrunfreesurfer(subjectid, dataloc, extraflags='', scanstouse=None,t2nifti=None):
    '''
    function cvnrunfreesurfer(subjectid,dataloc,extraflags,scanstouse,t2nifti)

    <subjectid> is like 'C0001'
    <dataloc> is:
        (1) the scan directory like '/home/stone-ext1/fmridata/20151014-ST001-wynn,subject1'
        (2) a NIFTI T1 .nii.gz file like '/home/stone-ext1/fmridata/AurelieData/Austin_3D.nii.gz'
        (3) or a pathlib object of (1) or (2)
    <extraflags> (optional) is a string with extra flags to pass to recon-all.
        Default: ''
    <scanstouse> (optional) is a vector of indices of T1 scans to use.
        For example, if there are 5 scans, [1 3 5] means to use the 1st, 3rd, and 5th.
        Default is to use all available.
    <t2nifti> (optional) is a NIFTI T2 .nii.gz file (str or path obj). If you specify this case,
      <dataloc> must be case (2).

    push anatomical data through FreeSurfer.
    see code for assumptions.

    history:
    - 2016/11/28 - major update for the new scheme with manual FS edits.
    '''
    from RZutilpy.cvnpy import cvnpath
    from RZutilpy.system import makedirs, unix_wrapper, Path
    from RZutilpy.rzio import matchfiles


    # calc
    dir0 = (Path(cvnpath('anatomicals')) / subjectid).str
    fsdir = (Path(cvnpath('freesurfer')) / subjectid).str

    # make subject anatomical directory
    makedirs(dir0)

    # case 1
    if Path(dataloc).is_dir():

      # figure out T1 files [ASSUME THAT THERE ARE AN EVEN NUMBER OF DIRECTORIES]
      t1file = matchfiles((Path(dataloc) / 'dicom' / '*T1w*').str)
      assert len(t1file) % 2==0
      t1file = t1file[1::2]   # [hint: 2nd of each pair is the one that is homogenity-corrected]

            #           # figure out T2 file [ASSUME THAT WE WILL MATCH TWO DIRECTORIES]
            #           t2file = matchfiles(sprintf('%s/*T2w*',dataloc))
            #           assert(mod(length(t2file),2)==0)
            #           t2file = t2file(2:2:end)   # [hint: 2nd of the two is the one to use, as it is homogenity-corrected]

      # convert dicoms to NIFTIs
      for p in t1file:
        unix_wrapper('dcm2nii -o {} -r N -x N {}'.format(dir0, p))

            #   assert(0==unix(sprintf('dcm2nii -o %s -r N -x N %s',dir0,t2file)))

      # find the NIFTIs
      t1nifti = matchfiles('{}/dicom/*T1w*nii.gz'.format(dir0))
            #   t2nifti = matchfiles(sprintf('%s/*T2w*nii.gz',dir0))
            #assert(length(t1nifti)==1)
            #   assert(length(t2nifti)==1)
            #t1nifti = t1nifti{1}
            #   t2nifti = t2nifti{1}
      assert t2nifti is None

    # case 2, dataloc is a nifti file
    else:
      assert Path(dataloc).is_file() and Path(dataloc).exists()

      # find the NIFTI
      t1nifti = matchfiles(dataloc)
          #assert(length(t1nifti)==1)
          #t1nifti = t1nifti{1}
      if not t2nifti:
        t2nifti = matchfiles(t2nifti)
        assert len(t2nifti)==1
        t2nifti = t2nifti[0]


    # deal with scanstouse
    if not scanstouse:
      scanstouse = range(len(t1nifti))

    # call recon-all
    str0 = ['-i %s '.format(str(x)) for x in t1nifti[scanstouse]]

    if not t2nifti:
      extrat2stuff = ''
    else:
      extrat2stuff = '-T2 %s -T2pial'.format(t2nifti)

    unix_wrapper('recon-all -s {} {} {} -all {} > {}/reconlog.txt'.format(subjectid,str0,extrat2stuff,extraflags,str(dir0)))

    # convert T1 to NIFTI for external use
    unix_wrapper('mri_convert {}/mri/T1.mgz {}/mri/T1.nii.gz'.format(str(fsdir),str(fsdir)))
Exemplo n.º 11
0
def transferatlastosurface(subjectid, fsmap, hemi, outpre, fstruncate=None,fun=None,outputdir=None):
    '''
    def transferatlastosurface(subjectid, fsmap, hemi, outpre, fstruncate=None,fun=None,outputdir=None):

    <subjectid> is like 'C0001'
    <fsmap> is the fsaverage surface file like '/software/freesurfer/fsaveragemaps/KayDataFFA1-RH.mgz'
    <hemi> is 'lh' or 'rh' indicating whether the surface file is left or right hemisphere
    <outpre> is the prefix of the destination .mgz files to write, like 'KayDataFFA1'
    <fstruncate> is the name of the truncation surface in fsaverage.
     if None, this indicates the non-dense processing case.
    <fun> (optional) is a function to apply to <fsmap> after loading it.
     Default is to do nothing (use values as-is).
    <outputdir> (optional) is the directory to write the .mgz files to.
     Default is cvnpath('freesurfer')/<subjectid>/label/

    Take the <fsmap> file, apply <fun>, and then transfer to single-subject surface space
    using nearest-neighbor interpolation.  Values in the other hemisphere are just set to 0.

    We write three versions:
    (1) <hemi>.<outpre>.mgz - standard (non-dense) surface
    (2) <hemi>DENSE.<outpre>.mgz - dense surface
    (3) <hemi>DENSETRUNC<fstruncate>.<outpre>.mgz - dense, truncated surface

    Note that (2) and (3) are not written if <fstruncate> is [].

    NOTES FROM KEITH:
    cvnroimask looks for label/rh[DENSE|DENSETRUNCpt].roiname.(mgz|mgh|label|annot)
    For multi-label files the label names are contained in a file called
      label/rh[DENSE|DENSETRUNCpt].roiname.(mgz|mgh|label|annot).ctab
    Or possibly without the rh|lh



    # ============== RZ notes ===============================
    Notes:
        * check cvntransferatlastosurface.m
        * <outputdir> now accept both str and a path-like object
        * default <fstruncate>,<fun>,<outputdir> to None

    History:
        20180714 RZ created it based on cvntransferatlastosurface.m
    '''

    from RZutilpy.cvnpy import cvnpath, writemgz
    from RZutilpy.rzio import loadpkl
    from RZutilpy.system import Path, makedirs
    from numpy import zeros

    # internal constants
    fsnumv = 163842  # vertices

    # calc
    fsdir = cvnpath('freesurfer') / subjectid

    # input
    fun = (lambda x: x) if fun is None else fun
    outputdir = fsdir / label if outputdir is None else outputdir
    outputdir = Path(outputdir) if ~isinstance(outputdir, Path) else outputdir

    # load transfer functions
    a1 = loadpkl((cvnpath('anatomicals') / subjectid / 'tfun.pkl').str)

    # load more
    if fstruncate is None:
        a2 = loadpkl((cvnpath('anatomicals')/subjectid/'tfunDENSE.pkl').str)
        # load truncation indices
        a3 = loadpkl((fsdir/'surf'/f'{hemi}.DENSETRUNC{fstruncate}.pkl').str)  # contains 'validix'

    ## !! ## fix here, make sure what load_mgh actually dod
    # load fsaverage map
    vals = flatten(load_mgh(fsmap))  # 1 x 163842
    assert len(vals)==fsnumv
    ## !! ## fix here, make sure what load_mgh actually dod

    # apply fun and expand map into full format (1 x 2*163842)
    vals = hstack((zeros(fsnumv), fun(vals))) if hemi=='rh' else hstack((fun(vals), zeros(1,fsnumv)))

    # make destination directory if necessary
    makedirs(outputdir)

    ##### STANDARD CASE (NON-DENSE)

    # transfer to single subject space (using nearest neighbor interpolation)
    vals0 = a1.tfunFSSSrh(vals) if hemi='rh' else a1.tfunFSSSlh(vals)

    # write mgz
    writemgz(subjectid,outpre,vals0,hemi,outputdir.str)

    ##### DENSE CASES (DENSE ON THE SPHERE ALSO TRUNCATED VERSION)

    if fstruncate is not None:

        # transfer to single subject space (using nearest neighbor interpolation)
        vals0 = a2.tfunFSSSrh(vals) if hemi=='rh' else a2.tfunFSSSlh(vals)

        # write mgz
        writemgz(subjectid,outpre,vals0,hemi+'DENSE',outputdir.str)

        # write mgz (truncated)
        writemgz(subjectid,outpre,vals0[a3.validix], hemi+'DENSETRUNC'+fstruncate, outputdir.str)
Exemplo n.º 12
0
def t1warp(t1,
           outputDir=None,
           template=None,
           maskvol=None,
           skullstrip_kw=[],
           affine_kw=[],
           affonly=False):
    '''
    Nonlinear (or linear warping) warping t1 file to a template. In afni_proc, they use
    @SSwarper by default. For a standard human T1 file, @SSwarper produce similar warping files
    as this script. But this script might be useful for like unusual cases, for example, Monkey data

    Input:
        <t1>: t1 file, a string or a pathlib object
        <outputDir>: output directory, we first copy t1 file into this outputDir. if None,
            use cwd.
        <template>: the template t1 file,a string or a pathlib object. if None, we use
            afni MNI152_2009_template_SSW.nii.gz file
        <maskvol>: mask volume, a str or a pathlib object. You can supply an maskvol file
            here so the skullstrip step will be skipped. This is useful when you want to
            manually edit the mask volume and supply it to skullstrip
        <skullstrip_kw>, <affine_kw>: lists, extra options supply to '3dSkullstrip' and 'align_epi_anat.py'
            commands. This is useful for like monkey data. Default: [] (empty list)
        <affonly>: boolean (default: False), only need affine transform, no nonlinear warping.

    Output: this will produce several files in <outputDir>, they are generated in order
        **.nii.gz: copy of t1 file
        **_ss.nii.gz: skullstriped(ss) T1
        **_ssmask.nii.gz: skullstriped(ss) brain mask
        **_ssiu.nii.gz: intenstivity unifize(iu) t1 after ss
        **_ssiu_shft: shift to center of mass of the template after iu
        **_ssiu_shft_aff: affine tranformed T1 in the template space
        **_ssiu_shft_aff_mat.aff12.1D: affine transformation matrix
        **_ssui_shft_aff_matINV.aff12.1D: inverse affine transformation
        **_ssiu_shft_aff_nl.nii.gz: nonlinear warp to the template in the template space
        WARP.nii.gz: nonlinear warp file

    Note:
        1. Here we do three transform, shift-affine-nlwarp. The later two can be concatenated but
            it is not recommended to concatenate all three because combining shift into affine and nlwarp
            will make the "3dNwarpapply" take a lot of memory and very long time (and might fail).
            If you want to inversely warp an atlas from template space into native T1 space. You can do like

            # first do inverse (affine+nlwarp)
            cmd = ['3dNwarpApply', '-prefix', f'{template.strnosuffix}_nl2t1.nii.gz', \
               '-nwarp', f'inv({(outputDir/"WARP.nii.gz").str} {t1.strnosuffix}_ssiu_shft_aff2NMT_mat.aff12.1D)', \
               '-source', template.str, '-master', f'{t1.strnosuffix}_ssiu_shft.nii.gz', '-overwrite']
            unix_wrapper(cmd)
            # then inverse the shift
            # Note that here do not use 3dAllineate as it will resample the data one more time
            # Use 3drefit, it will not resample data
            cmd = f'3drefit -duporigin {t1.strnosuffix}_ssiu.nii.gz {template.strnosuffix}_nl2t1.nii.gz}'
            unix_wrapper(cmd)

        2. Be very CAREFUL about the order when concatenating affine and nlwarp!

    To do:
        1. automatically perform inversed warp atlas??

    History:
        20190413 RZ create
    '''

    from RZutilpy.system import Path, unix_wrapper, makedirs

    t1 = Path(t1)
    if template:
        template = Path(template) if ~isinstance(template, Path) else template
    else:  # default template is mni template
        afnipath = unix_wrapper(f'which afni')  # get the afni install Path
        template = Path(afnipath).parent / 'MNI152_2009_template_SSW.nii.gz'

    outputDir = Path.cwd() if outputDir is None else Path(outputDir)
    makedirs(outputDir)  # create outputdir if not exist
    unix_wrapper(f'cp {t1} {outputDir}')  # copy t1 file to outputdir
    t1 = outputDir / f'{t1.name}'  # switch to new t1

    # ======= step 1, skull strip =====================
    if maskvol is None:
        # make brain mask, this step typically is good for a human brain, but tricky for a monkey brain
        cmd = ['3dSkullStrip', \
            '-input', t1.str, \
            '-prefix', f'{t1.strnosuffix}_ssmask.nii.gz', \
            '-mask_vol']
        cmd = cmd + skullstrip_kw
        unix_wrapper(cmd)
        maskvol = Path(f'{t1.strnosuffix}_ssmask.nii.gz')
    # skullstrip-based on mask
    cmd=['3dcalc', '-a', t1.str, '-b', maskvol.str, \
        '-expr', "a*step(b)", \
        '-prefix', f'{t1.strnosuffix}_ss.nii.gz']
    unix_wrapper(cmd)

    # ===== step 2, # intensity unifize ===========
    cmd = f'3dUnifize -prefix {t1.strnosuffix}_ssiu.nii.gz {t1.strnosuffix}_ss.nii.gz'
    unix_wrapper(cmd)

    # ====== step 3, affine transform to NMT template ==================

    # first we can align center of mass
    cmd = f'@Align_Centers -base {template.str} -dset {t1.strnosuffix}_ssiu.nii.gz -cm'
    unix_wrapper(
        cmd)  # this step will generate {t1.strnosuffix}_ssiu_shft.nii.gz
    shftfile = Path.cwd() / f'{t1.pstem}_ssiu_shft.1D'
    unix_wrapper(f'mv {shftfile.str} {outputDir.str}')
    # This step will generate {t1.strnosuffix}_ssiu_shft.nii.gz
    # This step will also generate a 1D transformation file under CWD not OUTPUTDIR
    # we have to copy this 1D file into <outputDir>

    # do affine alignment, note that data will be resampled on NMT grid
    cmd = ['align_epi_anat.py', '-dset1', f'{t1.strnosuffix}_ssiu_shft.nii.gz', \
                  '-dset2', template.str, \
                  '-master_dset1', template.str,\
                  '-suffix', '_aff.nii.gz',\
                  '-dset1to2', '-dset1_strip','None','-dset2_strip','None','-overwrite',\
                  '-output_dir', outputDir.str]
    cmd = cmd + affine_kw
    unix_wrapper(cmd)

    # change transformation file name
    unix_wrapper(
        f'mv {t1.strnosuffix}_ssiu_shft_aff.nii.gz_mat.aff12.1D {t1.strnosuffix}_ssiu_shft_aff_mat.aff12.1D'
    )

    # calc the inverse affine transformation
    cmd = f'cat_matvec -ONELINE {t1.strnosuffix}_ssiu_shft_aff_mat.aff12.1D -I > {t1.strnosuffix}_ssiu_shft_aff_matINV.aff12.1D'
    unix_wrapper(cmd)

    # ================= step 4, nonlinear registration ==============
    # note that this step should be run after completing affine transformation
    # Also here, input dataset is the anat dataset but put on the base grid,
    unix_wrapper(f'rm -rf {(outputDir/"awpy*").str}')
    # use superhard, to get the best alignment, but it take a long time
    cmd = ['auto_warp.py', '-base', template.str, '-skip_affine', 'yes', \
      '-input', f'{t1.strnosuffix}_ssiu_shft_aff.nii.gz', '-overwrite', \
      '-output_dir', (outputDir/'awpy').str, '-qw_opts','-iwarp','-superhard']
    unix_wrapper(cmd)
    # copy and delete redundant files
    unix_wrapper(
        f'cp {(outputDir/"awpy"/"anat.un.qw_WARP.nii").str} {(outputDir/"WARP.nii").str}'
    )
    unix_wrapper(f'gzip -f {(outputDir/"WARP.nii").str}'
                 )  # compress nonlinear warp file
    unix_wrapper(f'rm -rf {(outputDir/"awpy")}')

    # apply transform warp t1 to the template space
    cmd = ['3dNwarpApply', '-prefix', f'{t1.strnosuffix}_ssiu_shft_aff_nl.nii.gz', \
       '-nwarp', f'{(outputDir/"WARP.nii.gz").str} {t1.strnosuffix}_ssiu_shft_aff_mat.aff12.1D', \
       '-source', f'{t1.strnosuffix}_ssiu_shft.nii.gz', '-master', template.str, '-overwrite']
    unix_wrapper(cmd)
Exemplo n.º 13
0
def runfreesurfer2(subjectid,extraflags=''):
    '''
    def runfreesurfer2(subjectid,extraflags):

    <subjectid> is like 'C0001'
    <extraflags> (optional) is a string with extra flags to pass to recon-all.
     Default: ''

    This is part 2/2 for pushing anatomical data through FreeSurfer.
    see code for assumptions.
    '''
    from RZutilpy.cvnpy import cvnpath, writemgz, fstoint
    from RZutilpy.system import makedirs,Path
    from RZutilpy.rzio import savepkl, loadpkl
    import nibabel.freesurfer.io as fsio
    import nibabel as nib
    import re
    from numpy import stack
    from sklearn.neighbors import NearestNeighbors


    # calc
    dir0 = (Path(cvnpath('anatomicals')) / subjectid).str
    fsdir = (Path(cvnpath('freesurfer')) / subjectid).str

    # make subject anatomical directory
    makedirs(dir0)

    # convert some miscellaneous files

    # convert .thickness files to ASCII
    # no need for python since nibabel can directly read the file
    # unix_wrapper('mris_convert -c {0}/surf/lh.thickness {0}/surf/lh.white {0}/surf/lh.thickness.asc'.format(str(fsdir)))
    # unix_wrapper('mris_convert -c {0}/surf/rh.thickness {0}/surf/rh.white {0}/surf/rh.thickness.asc'.format(str(fsdir)))

    # # convert .curv files to ASCII
    # unix_wrapper('mris_convert -c {0}/surf/lh.curv {0}/surf/lh.white {0}/surf/lh.curv.asc'.format(str(fsdir)))
    # unix_wrapper('mris_convert -c {0}/surf/rh.curv {0}/surf/rh.white {0}/surf/rh.curv.asc'.format(str(fsdir)))

    #### make mid-gray surface

    unix_wrapper('mris_expand -thickness {0}/surf/lh.white 0.5 {0}/surf/lh.graymid'.format(fsdir))
    unix_wrapper('mris_expand -thickness {0}/surf/rh.white 0.5 {0}/surf/rh.graymid'.format(fsdir))

    #### consolidate mid-gray surface stuff into a .mat file
    for hemi in ['lh' 'rh']:
        # read .graymid surface
        vertices,faces = fsio.read_geometry((Path(fsdir) / 'surf'/ f'{hemi}.graymid').str)

        # construct vertices (4 x V), becareful here, numpy and matlab index might be different!!!
        vertices = vertices.T + np.array([128, 129, 128]).reshape(-1,1)
        vertices = np.vstack((vertices, np.ones(vertices.shape[1]).reshape(1,-1)))

        # construct faces (F x 3)
        faces = faces[:,[0, 2, 1]]  # necessary to convert freesurfer to matlab

        # load auxiliary info (V x 1)
        thickness = fsio.read_morph_data((Path(fsdir) / 'surf' / f'{hemi}.thickness').str)
        curvature = fsio.read_morph_data((Path(fsdir) / 'surf' / f'{hemi}.curv').str)

        # get freesurfer labels (fslabels is V x 1)
        fslabels, _, _ = fsio.read_annot((Path(fsdir) / 'label' / f'{hemi}.aparc.annot').str)

        # save
        savepkl((Path(cvnpath('anatomicals')) / subjectid / f'{hemi}midgray.pkl'.format(hemi)).str,
          {'vertices':vertices, 'faces':faces, 'thickness':thickness, \
          'curvature':curvature, 'fslabels': fslabels})

    #### calculate gray-matter information

    if isempty(regexp(extraflags,'hires')):
        # load ribbon
        ribmgz = nib.load((Path(fsdir)/ 'mri' / 'ribbon.mgz').str)
        rib = fstoint(ribmgz.get_data())

        # load coordinates of surface vertices
        coord0 = stack(\
          (loadpkl(Path((cvnpath('anatomicals')) / subjectid / 'lhmidgray.mat').str)['vertices'],\
            loadpkl((Path(cvnpath('anatomicals')) / subjectid / 'rhmidgray.mat').str)['vertices']),\
          axis=1)

        #### use nearestNeighour, need to double check this
        nbrs = NearestNeighbors(1, metric='l2')
        nbrs.fit(coord0.T)
        dist, mnix = nbrs.kneighbors(rib, 1) # do I need to reshape dist and mnix?


        # compute distances to vertices [i.e. create a volume where gray matter voxels have certain informative values]
        #[dist,mnix] = surfaceslice2(ismember(rib,[3 42]),coord0, 3, 4)  # NOTICE HARD-CODED VALUES HERE
        ####

        # save
          # 1-mm volume with, for each gray matter voxel, distance to closest vertex (of mid-gray surface)
        nib.save(inttofs(dist), (Path(fsdir) / 'mri'/ 'ribbonsurfdist.mgz').str, ribmgz)
          # 1-mm volume with, for each gray matter voxel, index of closest vertex (of mid-gray surface)
        nib.save(inttofs(mnix),(Path(fsdir) / 'mri'/ 'ribbonsurfindex.mgz').str, ribmgz)


    #### calculate transfer functions

    # calc
    [tfunFSSSlh,tfunFSSSrh,tfunSSFSlh,tfunSSFSrh] = \
      calctransferfunctions((Path(cvnpath('freesurfer')).joinpath('fsaverage', 'surf','lh.sphere.reg')).str, \
                            (Path(cvnpath('freesurfer')).joinpath('fsaverage', 'surf','rh.sphere.reg')).str, \
                               (Path(cvnpath('freesurfer')).joinpath(subjectid, 'surf','lh.sphere.reg')).str, \
                               (Path(cvnpath('freesurfer')).joinpath(subjectid, 'surf','rh.sphere.reg')).str)

    # save
    savepkl((Path(cvnpath('anatomicals')) / subjectid /'tfun.mat').str,\
      {'tfunFSSSlh': tfunFSSSlh,\
      'tfunFSSSrh': tfunFSSSrh,\
      'tfunSSFSlh': tfunSSFSlh\
      'tfunSSFSrh': tfunSSFSrh})
Exemplo n.º 14
0
def makeimagestack3dfiles(m, outputprefix=None, skips=(5, 5, 5), k=(0, 0, 0), \
    cmap='gray', returnstack=False, **stack_kw):
    '''
    makeimagestack3dfiles(m, outputprefix=None, skips=[5, 5, 5], k=[0, 0, 0], \
        cmap='gray', **kwargs):

    Input:
        <m>: is a 3D matrix or a nibabel image object
        <outputprefix>: is a output prefix,if it is NONE, then do not write images
        <skips> (optional) is a sequence of numbers of slices to skip in each of the 3 dimensions.
          Default: [5, 5, 5].
        <k> (optional) is a sequence with numbers containing the times to CCW rotate matrix
            rotation info. See np.rot90. Default: [0, 0, 0]. <k[i]> indicates
            rotate the image when writing image stack along ith dimension
        <cmap> is the colormap to use. Default: gray(256), any matplotlib colormap input is fine
        <returnstack>: boolean, whether to return the 3 imagestack. Useful to visualize
            and can help adjust <skips> and <k>. Default:False
        <stack_kw>: kwargs for makeimagestack, include <wantnorm>, <addborder>
            <csize>,<bordersize>
    Output:
        <imglist>: a 1x3 list containing the image matrix for 2,1,0 dimensions.
            note here the order is not from 0-2 dimensions. Each image is within
            range 0~1 float 64.

    We take <m> and write out three .png files, one for each slicing:
      <outputprefix>_view0.png
      <outputprefix>_view1.png
      <outputprefix>_view2.png

    The first slicing is through the first dimension with ordering [0 1 2].
    The second slicing is through the second dimension with ordering [1 3 2].
    The third slicing is through the third dimension with ordering [2 3 1].
    Note that this is different from KK's makeimagestack3dfiles.m

    After slicing, rotation (if supplied) is applied within the first
    two dimensions using np.rot90

    Example:
        vol = makegaussian3d([100 100 100],[.7 .3 .5],[.1 .4 .1]);
        makeimagestack3dfiles(vol,'test',(10, 10, 10),k=(5,5,5),wantnorm=(0, 1))

    To do:
        1. Fix the filepath problem

    History
        20180621 RZ added <returnstack> input
        20180502 RZ fixed the k rotdaion bug, now should be more clear.
        20180412 RZ changed the default cmap to 'gray'
        20180419 RZ changed the functionality of outputprefix, not saving images if None..

    '''
    from matplotlib.pyplot import imsave
    from nibabel.nifti1 import Nifti1Image as nifti
    from RZutilpy.imageprocess import makeimagestack
    from RZutilpy.system import Path, makedirs
    import numpy as np
    import os


    if isinstance(m, nifti):
        m = m.get_data()

    _is_writeimage = True
    if outputprefix is None:
        outputprefix = Path.cwd()
        _is_writeimage = False

    # create the folder if not exist.
    assert makedirs(Path(outputprefix).parent)

    # define permutes
    imglist = []
    # we make the dimension to slice to the last one
    permutes = np.array([[1, 2, 0], [0, 2, 1], [0, 1, 2]])
    for dim in range(3):
        temp = m
        if dim == 0:
            # note that the first element in the output image is along 1st dimension
            temp = temp[::skips[dim], :, :].transpose(permutes[dim, :])
        elif dim == 1:
            temp = temp[:, ::skips[dim], :].transpose(permutes[dim, :])
        elif dim == 2:
            temp = temp[:, :, ::skips[dim]].transpose(permutes[dim, :])
        # rotate image
        if k[dim]:  # note
            temp = np.rot90(temp, k=k[dim], axes=(0, 1))  # CCW rotate

        f = makeimagestack(temp, **stack_kw)
        imglist.append(f)
        # write the image
        fname = Path(outputprefix).parent / f'{Path(outputprefix).pstem}_view{dim}.png'
        # note that plt.imsave can automatically recognize the vmin and vmax, no
        # need to convert it to uint8 like in matlab
        if _is_writeimage:  # if not, only return the images of three views
            imsave(fname.str, f, cmap=cmap)

    # return the imagestacks for visualization and debugging
    if returnstack:
        return imglist  # reverse list to keep compatible the original axis