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)
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)
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()
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)
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
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)
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
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}')
# 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)
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)))
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)
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)
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})
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