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