def cruise_cortex_extraction(init_image, wm_image, gm_image, csf_image, vd_image=None, data_weight=0.4, regularization_weight=0.1, max_iterations=500, normalize_probabilities=False, correct_wm_pv=True, wm_dropoff_dist=1.0, topology='wcs', topology_lut_dir=None, save_data=False, overwrite=False, output_dir=None, file_name=None, log_file="timelog.json"): """ CRUISE cortex extraction Segments the cortex from a whole brain segmented data set with the CRUISE method (includes customized partial voluming corrections and the Anatomically-Consistent Enhancement (ACE) of sulcal fundi). Note that the main input images are generated by the nighres module :func:`nighres.brain.extract_brain_region`. Parameters ---------- init_image: niimg Initial white matter (WM) segmentation mask (binary mask>0 inside WM) wm_image: niimg Filled WM probability map (values in [0,1], including subcortical GM and ventricles) gm_image: niimg Cortical gray matter (GM) probability map (values in [0,1], highest inside the cortex) csf_image: niimg Sulcal cerebro-spinal fluid (CSf) and background probability map (values in [0,1], highest in CSf and masked regions) vd_image: niimg, optional Additional probability map of vessels and dura mater to be excluded data_weight: float Weighting of probability-based balloon forces in CRUISE (default 0.4, sum of {data_weight,regularization_weight} should be below or equal to 1) regularization_weight: float Weighting of curvature regularization forces in CRUISE (default 0.1, sum of {data_weight,regularization_weight} should be below or equal to 1) max_iterations: int Maximum number of iterations in CRUISE (default is 500) normalize_probabilities: bool Whether to normalize the wm, gm, and csf probabilities (default is False) correct_wm_pv: bool Whether to correct for WM partial voluming in gyral crowns (default is True) wm_dropoff_dist: float Distance parameter to lower WM probabilities away from current segmentation (default is 1.0 voxel) topology: {'wcs', 'no'} Topology setting, choose 'wcs' (well-composed surfaces) for strongest topology constraint, 'no' for no topology constraint (default is 'wcs') topology_lut_dir: str Path to directory in which topology files are stored (default is stored in TOPOLOGY_LUT_DIR) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * cortex (niimg): Hard segmentation of the cortex with labels background=0, gm=1, and wm=2 (_cruise_cortex) * gwb (niimg): Gray-White matter Boundary (GWB) level set function (_cruise_gwb) * cgb (niimg): CSF-Gray matter Boundary (CGB) level set function (_cruise_cgb) * avg (niimg): Central level set function, obtained as geometric average of GWB and CGB (*not* the middle depth of the cortex, use volumetric_layering if you want accurate depth measures) (_cruise-avg) * thickness (niimg): Simple cortical thickness estimate: distance to the GWB and CGB surfaces, in mm (_cruise-thick) * pwm (niimg): Optimized WM probability, including partial volume and distant values correction (_cruise-pwm) * pgm (niimg): Optimized GM probability, including CSF sulcal ridges correction (_cruise_pgm) * pcsf (niimg): Optimized CSF probability, including sulcal ridges and vessel/dura correction (_cruise-pwm) Notes ---------- Original algorithm by Xiao Han. Java module by Pierre-Louis Bazin. Algorithm details can be found in [1]_ References ---------- .. [1] X. Han, D.L. Pham, D. Tosun, M.E. Rettmann, C. Xu, and J. L. Prince, CRUISE: Cortical Reconstruction Using Implicit Surface Evolution, NeuroImage, vol. 23, pp. 997--1012, 2004 """ print('\nCRUISE Cortical Extraction') start = time.time() # check topology_lut_dir and set default if not given topology_lut_dir = _check_topology_lut_dir(topology_lut_dir) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, gm_image) cortex_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=gm_image, suffix='cruise-cortex', )) gwb_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=gm_image, suffix='cruise-gwb', )) cgb_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=gm_image, suffix='cruise-cgb', )) avg_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=gm_image, suffix='cruise-avg', )) thick_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=gm_image, suffix='cruise-thick', )) pwm_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=gm_image, suffix='cruise-pwm', )) pgm_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=gm_image, suffix='cruise-pgm', )) pcsf_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=gm_image, suffix='cruise-pcsf', )) if overwrite is False \ and os.path.isfile(cortex_file) \ and os.path.isfile(gwb_file) \ and os.path.isfile(cgb_file) \ and os.path.isfile(avg_file) \ and os.path.isfile(thick_file) \ and os.path.isfile(pwm_file) \ and os.path.isfile(pgm_file) \ and os.path.isfile(pcsf_file) : print("skip computation (use existing results)") output = { 'cortex': cortex_file, 'gwb': gwb_file, 'cgb': cgb_file, 'avg': avg_file, 'thickness': thick_file, 'pwm': pwm_file, 'pgm': pgm_file, 'pcsf': pcsf_file } return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create instance cruise = nighresjava.CortexOptimCRUISE() # set parameters cruise.setDataWeight(data_weight) cruise.setRegularizationWeight(regularization_weight) cruise.setMaxIterations(max_iterations) cruise.setNormalizeProbabilities(normalize_probabilities) cruise.setCorrectForWMGMpartialVoluming(correct_wm_pv) cruise.setWMdropoffDistance(wm_dropoff_dist) cruise.setTopology(topology) cruise.setTopologyLUTdirectory(topology_lut_dir) # load images init = load_volume(init_image, log_file=log_file) init_data = init.get_data() affine = init.affine header = init.header resolution = [x.item() for x in header.get_zooms()] dimensions = init_data.shape cruise.setDimensions(dimensions[0], dimensions[1], dimensions[2]) cruise.setResolutions(resolution[0], resolution[1], resolution[2]) cruise.importInitialWMSegmentationImage( nighresjava.JArray('int')( (init_data.flatten('F')).astype(int).tolist())) wm_data = load_volume(wm_image, log_file=log_file).get_data() cruise.setFilledWMProbabilityImage( nighresjava.JArray('float')((wm_data.flatten('F')).astype(float))) gm_data = load_volume(gm_image, log_file=log_file).get_data() cruise.setGMProbabilityImage( nighresjava.JArray('float')((gm_data.flatten('F')).astype(float))) csf_data = load_volume(csf_image, log_file=log_file).get_data() cruise.setCSFandBGProbabilityImage( nighresjava.JArray('float')((csf_data.flatten('F')).astype(float))) if vd_image is not None: vd_data = load_volume(vd_image, log_file=log_file).get_data() cruise.setVeinsAndDuraProbabilityImage( nighresjava.JArray('float')((vd_data.flatten('F')).astype(float))) # execute try: cruise.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes cortex_data = np.reshape(np.array(cruise.getCortexMask(), dtype=np.int32), dimensions, 'F') gwb_data = np.reshape(np.array(cruise.getWMGMLevelset(), dtype=np.float32), dimensions, 'F') cgb_data = np.reshape( np.array(cruise.getGMCSFLevelset(), dtype=np.float32), dimensions, 'F') avg_data = np.reshape( np.array(cruise.getCentralLevelset(), dtype=np.float32), dimensions, 'F') thick_data = np.reshape( np.array(cruise.getCorticalThickness(), dtype=np.float32), dimensions, 'F') pwm_data = np.reshape( np.array(cruise.getCerebralWMprobability(), dtype=np.float32), dimensions, 'F') pgm_data = np.reshape( np.array(cruise.getCorticalGMprobability(), dtype=np.float32), dimensions, 'F') pcsf_data = np.reshape( np.array(cruise.getSulcalCSFprobability(), dtype=np.float32), dimensions, 'F') # adapt header min, max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmax(cortex_data) header['cal_max'] = np.nanmax(cortex_data) cortex = nb.Nifti1Image(cortex_data, affine, header) header['cal_min'] = np.nanmax(gwb_data) header['cal_max'] = np.nanmax(gwb_data) gwb = nb.Nifti1Image(gwb_data, affine, header) header['cal_min'] = np.nanmax(cgb_data) header['cal_max'] = np.nanmax(cgb_data) cgb = nb.Nifti1Image(cgb_data, affine, header) header['cal_min'] = np.nanmax(avg_data) header['cal_max'] = np.nanmax(avg_data) avg = nb.Nifti1Image(avg_data, affine, header) header['cal_min'] = np.nanmax(thick_data) header['cal_max'] = np.nanmax(thick_data) thickness = nb.Nifti1Image(thick_data, affine, header) header['cal_min'] = np.nanmax(pwm_data) header['cal_max'] = np.nanmax(pwm_data) pwm = nb.Nifti1Image(pwm_data, affine, header) header['cal_min'] = np.nanmax(pgm_data) header['cal_max'] = np.nanmax(pgm_data) pgm = nb.Nifti1Image(pgm_data, affine, header) header['cal_min'] = np.nanmax(pcsf_data) header['cal_max'] = np.nanmax(pcsf_data) pcsf = nb.Nifti1Image(pcsf_data, affine, header) if save_data: save_volume(cortex_file, cortex, log_file=log_file) save_volume(gwb_file, gwb, log_file=log_file) save_volume(cgb_file, cgb, log_file=log_file) save_volume(avg_file, avg, log_file=log_file) save_volume(thick_file, thickness, log_file=log_file) save_volume(pwm_file, pwm, log_file=log_file) save_volume(pgm_file, pgm, log_file=log_file) save_volume(pcsf_file, pcsf, log_file=log_file) result = { 'cortex': cortex_file, 'gwb': gwb_file, 'cgb': cgb_file, 'avg': avg_file, 'thickness': thick_file, 'pwm': pwm_file, 'pgm': pgm_file, 'pcsf': pcsf_file } else: result = { 'cortex': cortex, 'gwb': gwb, 'cgb': cgb, 'avg': avg, 'thickness': thickness, 'pwm': pwm, 'pgm': pgm, 'pcsf': pcsf } end = time.time() time_log(log_file, "cruise_cortex_extraction", "makespan", None, start, end) return result
def phase_unwrapping(image, mask=None, nquadrants=3, tv_flattening=False, tv_scale=0.5, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Fast marching phase unwrapping Fast marching method for unwrapping phase images, based on _[1] Parameters ---------- image: niimg Input phase image to unwrap mask: niimg, optional Data mask to specify acceptable seeding regions nquadrants: int, optional Number of image quadrants to use (default is 3) tv_flattening: bool, optional Whether or not to run a post-processing step to remove background phase variations with a total variation filter (default is False) tv_scale: float, optional Relative intensity scale for the TV filter (default is 0.5) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (niimg): The unwrapped image rescaled in radians Notes ---------- Original Java module by Pierre-Louis Bazin. Algorithm adapted from [1]_ with additional seeding in multiple image quadrants to reduce the effects of possible phase singularities References ---------- .. [1] Abdul-Rahman, Gdeisat, Burton and Lalor. Fast three-dimensional phase-unwrapping algorithm based on sorting by reliability following a non-continuous path. doi: 10.1117/12.611415 """ print('\nFast marching phase unwrapping') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image) out_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image, suffix='unwrap-img')) if overwrite is False \ and os.path.isfile(out_file) : print("skip computation (use existing results)") output = {'result': out_file} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create instance unwrap = nighresjava.FastMarchingPhaseUnwrapping() # set parameters # load image and use it to set dimensions and resolution img = load_volume(image) data = img.get_data() affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape dimensions3D = (dimensions[0], dimensions[1], dimensions[2]) unwrap.setDimensions(dimensions[0], dimensions[1], dimensions[2]) unwrap.setResolutions(resolution[0], resolution[1], resolution[2]) unwrap.setPhaseImage( nighresjava.JArray('float')( (data.flatten('F')).astype(float)[0:dimensions[0] * dimensions[1] * dimensions[2]])) if mask is not None: unwrap.setMaskImage( idx, nighresjava.JArray('int')( (load_volume(mask).get_data().flatten('F') ).astype(int).tolist())) # set algorithm parameters unwrap.setQuadrantNumber(nquadrants) if tv_flattening: unwrap.setTVPostProcessing("TV-residuals") unwrap.setTVScale(tv_scale) # execute the algorithm try: unwrap.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes unwrap_data = np.reshape( np.array(unwrap.getCorrectedImage(), dtype=np.float32), dimensions3D, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(unwrap_data) header['cal_max'] = np.nanmax(unwrap_data) out = nb.Nifti1Image(unwrap_data, affine, header) if save_data: save_volume(out_file, out) return {'result': out_file} else: return {'result': out}
def filter_ridge_structures(input_image, structure_intensity='bright', output_type='probability', use_strict_min_max_filter=True, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Filter Ridge Structures Uses an image filter to make a probabilistic image of ridge structures. Parameters ---------- input_image: niimg Image containing structure-of-interest structure_intensity: {'bright', 'dark', 'both} Image intensity of structure-of-interest' output_type: {'probability','intensity'} Whether the image should be normalized to reflect probabilities use_strict_min_max_filter: bool, optional Choose between the more specific recursive ridge filter or a more sensitive bidirectional filter (default is True) save_data: bool, optional Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * ridge_structure_image: Image that reflects the presensence of ridges in the image (_rdg-img) Notes ---------- Original Java module by Pierre-Louis Bazin. """ if save_data: output_dir = _output_dir_4saving(output_dir, input_image) ridge_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=input_image, suffix='rdg-img', )) if overwrite is False \ and os.path.isfile(ridge_file) : print("skip computation (use existing results)") output = {'result': ridge_file} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create algorithm instance filter_ridge = nighresjava.FilterRidgeStructures() # set parameters filter_ridge.setStructureIntensity(structure_intensity) filter_ridge.setOutputType(output_type) filter_ridge.setUseStrictMinMaxFilter(use_strict_min_max_filter) # load images and set dimensions and resolution input_image = load_volume(input_image) data = input_image.get_fdata() affine = input_image.affine header = input_image.header resolution = [x.item() for x in header.get_zooms()] dimensions = input_image.shape filter_ridge.setDimensions(dimensions[0], dimensions[1], dimensions[2]) filter_ridge.setResolutions(resolution[0], resolution[1], resolution[2]) data = load_volume(input_image).get_fdata() filter_ridge.setInputImage(nighresjava.JArray('float')( (data.flatten('F')).astype(float))) # execute try: filter_ridge.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # Collect output ridge_structure_image_data = np.reshape(np.array( filter_ridge.getRidgeStructureImage(), dtype=np.float32), dimensions, 'F') if output_type == 'probability': header['cal_min'] = 0.0 header['cal_max'] = 1.0 else: header['cal_min'] = np.nanmin(ridge_structure_image_data) header['cal_max'] = np.nanmax(ridge_structure_image_data) ridge_structure_image = nb.Nifti1Image(ridge_structure_image_data, affine, header) if save_data: save_volume(ridge_file, ridge_structure_image) outputs = {'result': ridge_file} else: outputs = {'result': ridge_structure_image} return outputs
def intensity_propagation(image, mask=None, combine='mean', distance_mm=5.0, target='zero', scaling=1.0, domain=None, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Intensity Propogation Propagates the values inside the mask (or non-zero) into the neighboring voxels Parameters ---------- image: niimg Input image mask: niimg, optional Data mask to specify acceptable seeding regions combine: {'min','mean','max'}, optional Propagate using the mean (default), max or min data from neighboring voxels distance_mm: float, optional Distance for the propagation (note: this algorithm will be slow for large distances) target: {'zero','mask','lower','higher'}, optional Propagate into zero (default), masked out, lower or higher neighboring voxels scaling: float, optional Multiply the propagated values by a factor <=1 (default is 1) domain: niimg, optional Domain mask to specify acceptable regions to grow into save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (niimg): The propagated intensity image Notes ---------- Original Java module by Pierre-Louis Bazin. """ print('\nIntensity Propagation') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image) out_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=image, suffix='ppag-img')) if overwrite is False \ and os.path.isfile(out_file) : print("skip computation (use existing results)") output = {'result': out_file} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create instance propag = nighresjava.IntensityPropagate() # set parameters # load image and use it to set dimensions and resolution img = load_volume(image) data = img.get_fdata() affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape if len(dimensions)>2: propag.setDimensions(dimensions[0], dimensions[1], dimensions[2]) propag.setResolutions(resolution[0], resolution[1], resolution[2]) else: propag.setDimensions(dimensions[0], dimensions[1]) propag.setResolutions(resolution[0], resolution[1]) propag.setInputImage(nighresjava.JArray('float')( (data.flatten('F')).astype(float))) if mask is not None: propag.setMaskImage(nighresjava.JArray('int')( (load_volume(mask).get_fdata().flatten('F')).astype(int).tolist())) if domain is not None: propag.setDomainImage(nighresjava.JArray('int')( (load_volume(domain).get_fdata().flatten('F')).astype(int).tolist())) # set algorithm parameters propag.setCombinationMethod(combine) propag.setPropagationDistance(distance_mm) propag.setTargetVoxels(target) propag.setPropogationScalingFactor(scaling) # execute the algorithm try: propag.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes propag_data = np.reshape(np.array(propag.getResultImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(propag_data) header['cal_max'] = np.nanmax(propag_data) out = nb.Nifti1Image(propag_data, affine, header) if save_data: save_volume(out_file, out) return {'result': out_file} else: return {'result': out}
def mp2rage_skullstripping(second_inversion, t1_weighted=None, t1_map=None, skip_zero_values=True, topology_lut_dir=None, save_data=False, overwrite=False, output_dir=None, file_name=None): """ MP2RAGE skull stripping Estimates a brain mask from MRI data acquired with the MP2RAGE sequence. At least a T1-weighted or a T1 map image is required Parameters ---------- second_inversion: niimg Second inversion image derived from MP2RAGE sequence t1_weighted: niimg T1-weighted image derived from MP2RAGE sequence (also referred to as "uniform" image) At least one of t1_weighted and t1_map is required t1_map: niimg Quantitative T1 map image derived from MP2RAGE sequence At least one of t1_weighted and t1_map is required skip_zero_values: bool Ignores voxels with zero value (default is True) topology_lut_dir: str, optional Path to directory in which topology files are stored (default is stored in TOPOLOGY_LUT_DIR) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * brain_mask (niimg): Binary brain mask (_strip-mask) * inv2_masked (niimg): Masked second inversion imamge (_strip-inv2) * t1w_masked (niimg): Masked T1-weighted image (_strip-t1w) * t1map_masked (niimg): Masked T1 map (_strip-t1map) Notes ---------- Original Java module by Pierre-Louis Bazin. Details on the algorithm can be found in [1]_ and a presentation of the MP2RAGE sequence in [2]_ References ---------- .. [1] Bazin et al. (2014). A computational framework for ultra-high resolution cortical segmentation at 7 Tesla. DOI: 10.1016/j.neuroimage.2013.03.077 .. [2] Marques et al. (2010). MP2RAGE, a self bias-field corrected sequence for improved segmentation and T1-mapping at high field. DOI: 10.1016/j.neuroimage.2009.10.002 """ print('\nMP2RAGE Skull Stripping') # check topology lut dir and set default if not given topology_lut_dir = _check_topology_lut_dir(topology_lut_dir) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, second_inversion) inv2_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=second_inversion, suffix='strip-inv2')) mask_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=second_inversion, suffix='strip-mask')) if t1_weighted is not None: t1w_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=t1_weighted, suffix='strip-t1w')) else: t1w_file = None if t1_map is not None: t1map_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=t1_map, suffix='strip-t1map')) else: t1map_file = None if overwrite is False \ and os.path.isfile(mask_file) \ and os.path.isfile(inv2_file) : print("skip computation (use existing results)") output = {'brain_mask': mask_file, 'inv2_masked': inv2_file} if t1w_file is not None: if os.path.isfile(t1w_file): output['t1w_masked'] = t1w_file if t1map_file is not None: if os.path.isfile(t1map_file): output['t1map_masked'] = t1map_file return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create skulltripping instance algo = nighresjava.BrainMp2rageSkullStripping() # get dimensions and resolution from second inversion image inv2_img = load_volume(second_inversion) inv2_data = inv2_img.get_fdata() inv2_affine = inv2_img.affine inv2_hdr = inv2_img.header resolution = [x.item() for x in inv2_hdr.get_zooms()] dimensions = inv2_data.shape algo.setDimensions(dimensions[0], dimensions[1], dimensions[2]) algo.setResolutions(resolution[0], resolution[1], resolution[2]) algo.setSecondInversionImage( nighresjava.JArray('float')((inv2_data.flatten('F')).astype(float))) # pass other inputs if (t1_weighted is None and t1_map is None): raise ValueError('You must specify at least one of ' 't1_weighted and t1_map') if t1_weighted is not None: t1w_img = load_volume(t1_weighted) t1w_data = t1w_img.get_fdata() t1w_affine = t1w_img.affine t1w_hdr = t1w_img.header algo.setT1weightedImage( nighresjava.JArray('float')((t1w_data.flatten('F')).astype(float))) if t1_map is not None: t1map_img = load_volume(t1_map) t1map_data = t1map_img.get_fdata() t1map_affine = t1map_img.affine t1map_hdr = t1map_img.header algo.setT1MapImage( nighresjava.JArray('float')( (t1map_data.flatten('F')).astype(float))) algo.setSkipZeroValues(skip_zero_values) algo.setTopologyLUTdirectory(topology_lut_dir) # execute skull stripping try: algo.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs and potentially save inv2_masked_data = np.reshape( np.array(algo.getMaskedSecondInversionImage(), dtype=np.float32), dimensions, 'F') inv2_hdr['cal_max'] = np.nanmax(inv2_masked_data) inv2_masked = nb.Nifti1Image(inv2_masked_data, inv2_affine, inv2_hdr) mask_data = np.reshape(np.array(algo.getBrainMaskImage(), dtype=np.uint32), dimensions, 'F') inv2_hdr['cal_max'] = np.nanmax(mask_data) mask = nb.Nifti1Image(mask_data, inv2_affine, inv2_hdr) if save_data: save_volume(inv2_file, inv2_masked) save_volume(mask_file, mask) outputs = {'brain_mask': mask_file, 'inv2_masked': inv2_file} else: outputs = {'brain_mask': mask, 'inv2_masked': inv2_masked} if t1_weighted is not None: t1w_masked_data = np.reshape( np.array(algo.getMaskedT1weightedImage(), dtype=np.float32), dimensions, 'F') t1w_hdr['cal_max'] = np.nanmax(t1w_masked_data) t1w_masked = nb.Nifti1Image(t1w_masked_data, t1w_affine, t1w_hdr) if save_data: save_volume(t1w_file, t1w_masked) outputs['t1w_masked'] = t1w_file else: outputs['t1w_masked'] = t1w_masked if t1_map is not None: t1map_masked_data = np.reshape( np.array(algo.getMaskedT1MapImage(), dtype=np.float32), dimensions, 'F') t1map_hdr['cal_max'] = np.nanmax(t1map_masked_data) t1map_masked = nb.Nifti1Image(t1map_masked_data, t1map_affine, t1map_hdr) if save_data: save_volume(t1map_file, t1map_masked) outputs['t1map_masked'] = t1map_file else: outputs['t1map_masked'] = t1map_masked return outputs
def topology_correction(image, shape_type, connectivity='wcs', propagation='object->background', minimum_distance=0.00001, topology_lut_dir=None, save_data=False, overwrite=False, output_dir=None, file_name=None): """Topology correction Corrects the topology of a binary image, a probability map or a levelset surface to spherical with a fast marching technique [1]_. Parameters ---------- image: niimg Image representing the shape of interest shape_type: {'binary_object','probability_map','signed_distance_function'} Which type of image is used as input connectivity: {'wcs','6/18','6/26','18/6','26/6'} What connectivity type to use (default is wcs: well-composed surfaces) propagation: {'object->background','background->object'} Which direction to use to enforce topology changes (default is 'object->background' ) minimum_distance: float, optional Minimum distance to impose between successive voxels (default is 1e-5) topology_lut_dir: str, optional Path to directory in which topology files are stored (default is stored in TOPOLOGY_LUT_DIR) save_data: bool, optional Save output data to file (default is False) overwrite: bool, optional Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * corrected (niimg): Corrected image (output file suffix _tpc-img) * object (niimg): Corrected binary object (output file suffix _tpc-obj) Notes ---------- Original Java module by Pierre-Louis Bazin References ---------- .. [1] Bazin and Pham (2007). Topology correction of segmented medical images using a fast marching algorithm doi:10.1016/j.cmpb.2007.08.006 """ print("\nTopology Correction") # check topology_lut_dir and set default if not given topology_lut_dir = _check_topology_lut_dir(topology_lut_dir) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image) corrected_file = os.path.join( output_dir, _fname_4saving(file_name=file_name, rootfile=image, suffix='tpc-img')) corrected_obj_file = os.path.join( output_dir, _fname_4saving(file_name=file_name, rootfile=image, suffix='tpc-obj')) if overwrite is False \ and os.path.isfile(corrected_file) \ and os.path.isfile(corrected_obj_file) : print("skip computation (use existing results)") output = { 'corrected': load_volume(corrected_file), 'object': load_volume(corrected_obj_file) } return output # start virtual machine if not running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # initiate class algorithm = nighresjava.ShapeTopologyCorrection2() # load the data img = load_volume(image) hdr = img.header aff = img.affine data = img.get_data() resolution = [x.item() for x in hdr.get_zooms()] dimensions = data.shape algorithm.setResolutions(resolution[0], resolution[1], resolution[2]) algorithm.setDimensions(dimensions[0], dimensions[1], dimensions[2]) algorithm.setShapeImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) algorithm.setShapeImageType(shape_type) algorithm.setTopology(connectivity) algorithm.setTopologyLUTdirectory(topology_lut_dir) algorithm.setPropagationDirection(propagation) algorithm.setMinimumDistance(minimum_distance) # execute class try: algorithm.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs corrected_data = np.reshape( np.array(algorithm.getCorrectedImage(), dtype=np.float32), dimensions, 'F') hdr['cal_min'] = np.nanmin(corrected_data) hdr['cal_max'] = np.nanmax(corrected_data) corrected = nb.Nifti1Image(corrected_data, aff, hdr) corrected_obj_data = np.reshape( np.array(algorithm.getCorrectedObjectImage(), dtype=np.int32), dimensions, 'F') hdr['cal_min'] = np.nanmin(corrected_obj_data) hdr['cal_max'] = np.nanmax(corrected_obj_data) corrected_obj = nb.Nifti1Image(corrected_obj_data, aff, hdr) if save_data: save_volume(corrected_file, corrected) save_volume(corrected_obj_file, corrected_obj) return {'corrected': corrected, 'object': corrected_obj}
def levelset_thickness(input_image, shape_image_type='signed_distance', save_data=False, overwrite=False, output_dir=None, file_name=None): """ Levelset Thickness Using a medial axis representation, derive a thickness map for a levelset surface Parameters ---------- input_image: niimg Image containing structure-of-interest shape_image_type: str shape of the input image: either 'signed_distance', 'probability_map', or 'parcellation'. save_data: bool, optional Save output data to file (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * thickness (niimg): Estimated thickness map (_lth-map) * axis (niimg): Medial axis extracted (_lth-ax) * dist (niimg): Medial axis distance (_lth-dist) Notes ---------- Original Java module by Pierre-Louis Bazin. """ print("\nLevelset Thickness") if save_data: output_dir = _output_dir_4saving(output_dir, input_image) thickness_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='lth-map')) axis_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='lth-ax')) dist_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='lth-dist')) if overwrite is False \ and os.path.isfile(thickness_file) \ and os.path.isfile(axis_file) \ and os.path.isfile(dist_file) : output = { 'thickness': thickness_file, 'axis': axis_file, 'dist': dist_file } return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create algorithm instance algorithm = nighresjava.LevelsetThickness() # set parameters algorithm.setShapeImageType(shape_image_type) # load images and set dimensions and resolution input_image = load_volume(input_image) data = input_image.get_data() affine = input_image.get_affine() header = input_image.get_header() resolution = [x.item() for x in header.get_zooms()] dimensions = input_image.shape algorithm.setDimensions(dimensions[0], dimensions[1], dimensions[2]) algorithm.setResolutions(resolution[0], resolution[1], resolution[2]) data = load_volume(input_image).get_data() if (shape_image_type == 'parcellation'): algorithm.setLabelImage( nighresjava.JArray('int')( (data.flatten('F')).astype(int).tolist())) else: algorithm.setShapeImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) # execute try: algorithm.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # Collect output axis_data = np.reshape( np.array(algorithm.getMedialAxisImage(), dtype=np.float32), dimensions, 'F') dist_data = np.reshape( np.array(algorithm.getMedialDistanceImage(), dtype=np.float32), dimensions, 'F') thickness_data = np.reshape( np.array(algorithm.geThicknessImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(axis_data) header['cal_max'] = np.nanmax(axis_data) axis_img = nb.Nifti1Image(axis_data, affine, header) header['cal_min'] = np.nanmin(dist_data) header['cal_max'] = np.nanmax(dist_data) dist_img = nb.Nifti1Image(dist_data, affine, header) header['cal_min'] = np.nanmin(thickness_data) header['cal_max'] = np.nanmax(thickness_data) thickness_img = nb.Nifti1Image(thickness_data, affine, header) if save_data: save_volume(axis_file, axis_img) save_volume(dist_file, dist_img) save_volume(thickness_file, thickness_img) return { 'thickness': thickness_file, 'axis': axis_file, 'dist': dist_file } else: return {'thickness': thickness_img, 'axis': axis_img, 'dist': dist_img}
def laminar_iterative_smoothing(profile_surface_image, intensity_image, fwhm_mm, roi_mask_image=None, save_data=False, overwrite=False, output_dir=None, file_name=None): '''Smoothing data on multiple intracortical layers Parameters ----------- data_image: niimg Image from which data should be sampled profile_surface_image: niimg 4D image containing levelset representations of different intracortical surfaces on which data should be sampled fwhm_mm: float Full width half maximum distance to use in smoothing (in mm) roi_mask_image: niimg, optional Mask image defining a region of interest to restrict the smoothing save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ----------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (niimg): smoothed intensity image (_lis-smooth) Notes ---------- Original Java module by Pierre-Louis Bazin Important: this method assumes isotropic voxels ''' print('\nLaminar iterative smoothing') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, intensity_image) smoothed_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=intensity_image, suffix='lis-smooth')) if overwrite is False \ and os.path.isfile(smoothed_file) : print("skip computation (use existing results)") output = {"result": smoothed_file} return output # start VM if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # initate class smoother = nighresjava.LaminarIterativeSmoothing() # load the data surface_img = load_volume(profile_surface_image) surface_data = surface_img.get_fdata() layers = surface_data.shape[3] - 1 intensity_img = load_volume(intensity_image) intensity_data = intensity_img.get_fdata() hdr = intensity_img.header aff = intensity_img.affine resolution = [x.item() for x in hdr.get_zooms()] dimensions = intensity_data.shape if (roi_mask_image != None): roi_mask_data = load_volume(data_image).get_fdata() else: roi_mask_data = None # pass inputs smoother.setIntensityImage( nighresjava.JArray('float')( (intensity_data.flatten('F')).astype(float))) smoother.setProfileSurfaceImage( nighresjava.JArray('float')((surface_data.flatten('F')).astype(float))) smoother.setResolutions(resolution[0], resolution[1], resolution[2]) smoother.setDimensions(dimensions[0], dimensions[1], dimensions[2]) smoother.setLayers(layers) if (len(dimensions) > 3): smoother.set4thDimension(dimensions[3]) else: smoother.set4thDimension(1) if (roi_mask_data != None): smoother.setROIMask( nighresjava.JArray('int')( (roi_mask_data.flatten('F')).astype(int).tolist())) smoother.setFWHMmm(float(fwhm_mm)) # execute class try: smoother.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collecting outputs smoothed_data = np.reshape( np.array(smoother.getSmoothedIntensityImage(), dtype=np.float32), dimensions, 'F') hdr['cal_max'] = np.nanmax(smoothed_data) smoothed = nb.Nifti1Image(smoothed_data, aff, hdr) if save_data: save_volume(smoothed_file, smoothed) return {"result": smoothed_file} else: return {"result": smoothed}
def apply_coordinate_mappings_2d(image, mapping1, mapping2=None, mapping3=None, mapping4=None, interpolation="nearest", padding="closest", save_data=False, overwrite=False, output_dir=None, file_name=None): '''Apply a 2D coordinate mapping (or a succession of coordinate mappings) to a 2D or 3D image. Parameters ---------- image: niimg Image to deform mapping1 : niimg First coordinate mapping to apply mapping2 : niimg, optional Second coordinate mapping to apply mapping3 : niimg, optional Third coordinate mapping to apply mapping4 : niimg, optional Fourth coordinate mapping to apply interpolation: {'nearest', 'linear'} Interpolation method (default is 'nearest') padding: {'closest', 'zero', 'max'} Image padding method (default is 'closest') save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (niimg): Result image (_def-img) Notes ---------- Original Java module by Pierre-Louis Bazin ''' print('\nApply coordinate mappings (2D)') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image) deformed_file = os.path.join( output_dir, _fname_4saving(file_name=file_name, rootfile=image, suffix='def-img')) if overwrite is False \ and os.path.isfile(deformed_file) : print("skip computation (use existing results)") output = {'result': load_volume(deformed_file)} return output # start virutal machine if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # initate class applydef = nighresjava.RegistrationApplyDeformations2D() # load the data img = load_volume(image) data = img.get_data() hdr = img.header aff = img.affine imgres = [x.item() for x in hdr.get_zooms()] imgdim = data.shape # set parameters from input images if len(imgdim) is 3: applydef.setImageDimensions(imgdim[0], imgdim[1], imgdim[2]) else: applydef.setImageDimensions(imgdim[0], imgdim[1]) applydef.setImageResolutions(imgres[0], imgres[1]) applydef.setImageToDeform( nighresjava.JArray('float')((data.flatten('F')).astype(float))) def1 = load_volume(mapping1) def1data = def1.get_data() aff = def1.affine hdr = def1.header trgdim = def1data.shape applydef.setDeformationMapping1( nighresjava.JArray('float')((def1data.flatten('F')).astype(float))) applydef.setDeformation1Dimensions(def1data.shape[0], def1data.shape[1]) applydef.setDeformationType1("mapping(voxels)") if not (mapping2 == None): def2 = load_volume(mapping2) def2data = def2.get_data() aff = def2.affine hdr = def2.header trgdim = def2data.shape applydef.setDeformationMapping2( nighresjava.JArray('float')((def2data.flatten('F')).astype(float))) applydef.setDeformation2Dimensions(def2data.shape[0], def2data.shape[1]) applydef.setDeformationType2("mapping(voxels)") if not (mapping3 == None): def3 = load_volume(mapping3) def3data = def3.get_data() aff = def3.affine hdr = def3.header trgdim = def3data.shape applydef.setDeformationMapping3( nighresjava.JArray('float')( (def3data.flatten('F')).astype(float))) applydef.setDeformation3Dimensions(def3data.shape[0], def3data.shape[1]) applydef.setDeformationType3("mapping(voxels)") if not (mapping4 == None): def4 = load_volume(mapping4) def4data = def4.get_data() aff = def4.affine hdr = def4.header trgdim = def4data.shape applydef.setDeformationMapping4( nighresjava.JArray('float')( (def4data.flatten('F')).astype(float))) applydef.setDeformation4Dimensions(def4data.shape[0], def4data.shape[1]) applydef.setDeformationType4("mapping(voxels)") applydef.setInterpolationType(interpolation) applydef.setImagePadding(padding) # execute class try: applydef.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect data if len(imgdim) is 3: trgdim = [trgdim[0], trgdim[1], imgdim[2]] else: trgdim = [trgdim[0], trgdim[1]] deformed_data = np.reshape( np.array(applydef.getDeformedImage(), dtype=np.float32), trgdim, 'F') hdr['cal_min'] = np.nanmin(deformed_data) hdr['cal_max'] = np.nanmax(deformed_data) deformed = nb.Nifti1Image(deformed_data, aff, hdr) if save_data: save_volume(deformed_file, deformed) return {'result': deformed}
def mgdm_segmentation(contrast_image1, contrast_type1, contrast_image2=None, contrast_type2=None, contrast_image3=None, contrast_type3=None, contrast_image4=None, contrast_type4=None, n_steps=5, max_iterations=800, topology='wcs', atlas_file=None, topology_lut_dir=None, adjust_intensity_priors=False, normalize_qmaps=True, compute_posterior=False, posterior_scale=5.0, diffuse_probabilities=False, save_data=False, overwrite=False, output_dir=None, file_name=None): """ MGDM segmentation Estimates brain structures from an atlas for MRI data using a Multiple Object Geometric Deformable Model (MGDM) Parameters ---------- contrast_image1: niimg First input image to perform segmentation on contrast_type1: str Contrast type of first input image, must be listed as a prior in used atlas(specified in atlas_file). Possible inputs by default are DWIFA3T, DWIMD3T, T1map9T, Mp2rage9T, T1map7T, Mp2rage7T, PV, Filters, T1pv, Mprage3T, T1map3T, Mp2rage3T, HCPT1w, HCPT2w, NormMPRAGE. contrast_image2: niimg, optional Additional input image to inform segmentation, must be in the same space as constrast_image1, requires contrast_type2 contrast_type2: str, optional Contrast type of second input image, must be listed as a prior in used atlas (specified in atlas_file). Possible inputs by default are the same as with parameter contrast_type1 (see above). contrast_image3: niimg, optional Additional input image to inform segmentation, must be in the same space as constrast_image1, requires contrast_type3 contrast_type3: str, optional Contrast type of third input image, must be listed as a prior in used atlas (specified in atlas_file). Possible inputs by default are the same as with parameter contrast_type1 (see above). contrast_image4: niimg, optional Additional input image to inform segmentation, must be in the same space as constrast_image1, requires contrast_type4 contrast_type4: str, optional Contrast type of fourth input image, must be listed as a prior in used atlas (specified in atlas_file). Possible inputs by default are the same as with parameter contrast_type1 (see above). n_steps: int, optional Number of steps for MGDM (default is 5, set to 0 for quick testing of registration of priors, which does not perform true segmentation) max_iterations: int, optional Maximum number of iterations per step for MGDM (default is 800, set to 1 for quick testing of registration of priors, which does not perform true segmentation) topology: {'wcs', 'no'}, optional Topology setting, choose 'wcs' (well-composed surfaces) for strongest topology constraint, 'no' for no topology constraint (default is 'wcs') atlas_file: str, optional Path to plain text atlas file (default is stored in DEFAULT_ATLAS) or atlas name to be searched in ATLAS_DIR topology_lut_dir: str, optional Path to directory in which topology files are stored (default is stored in TOPOLOGY_LUT_DIR) normalize_qmaps: bool Normalize quantitative maps into [0,1] (default is False) adjust_intensity_priors: bool Adjust intensity priors based on dataset (default is False) normalize_qmaps: bool Normalize quantitative maps in [0,1] (default in True, change this if using one of the -quant atlas text files in ATLAS_DIR) compute_posterior: bool Compute posterior probabilities for segmented structures (default is False) posterior_scale: float Posterior distance scale from segmented structures to compute posteriors (default is 5.0 mm) diffuse_probabilities: bool Regularize probability distribution with a non-linear diffusion scheme (default is False) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * segmentation (niimg): Hard brain segmentation with topological constraints (if chosen) (_mgdm_seg) * labels (niimg): Maximum tissue probability labels (_mgdm_lbls) * memberships (niimg): Maximum tissue probability values, 4D image where the first dimension shows each voxel's highest probability to belong to a specific tissue, the second dimension shows the second highest probability to belong to another tissue etc. (_mgdm_mems) * distance (niimg): Minimum distance to a segmentation boundary (_mgdm_dist) Notes ---------- Original Java module by Pierre-Louis Bazin. Algorithm details can be found in [1]_ and [2]_ References ---------- .. [1] Bazin et al. (2014). A computational framework for ultra-high resolution cortical segmentation at 7 Tesla. doi: 10.1016/j.neuroimage.2013.03.077 .. [2] Bogovic et al. (2013). A multiple object geometric deformable model for image segmentation. doi:10.1016/j.cviu.2012.10.006.A """ print('\nMGDM Segmentation') # Check data file parameters if not save_data and return_filename: raise ValueError('save_data must be True if return_filename is True ') # check atlas_file and set default if not given atlas_file = _check_atlas_file(atlas_file) # check topology_lut_dir and set default if not given topology_lut_dir = _check_topology_lut_dir(topology_lut_dir) # find available intensity priors in selected MGDM atlas mgdm_intensity_priors = _get_mgdm_intensity_priors(atlas_file) # sanity check contrast types contrasts = [ contrast_image1, contrast_image2, contrast_image3, contrast_image4 ] ctypes = [contrast_type1, contrast_type2, contrast_type3, contrast_type4] for idx, ctype in enumerate(ctypes): if ctype is None and contrasts[idx] is not None: raise ValueError( ("If specifying contrast_image{0}, please also " "specify contrast_type{0}".format(idx + 1, idx + 1))) elif ctype is not None and ctype not in mgdm_intensity_priors: raise ValueError(("{0} is not a valid contrast type for " "contrast_type{1} please choose from the " "following contrasts provided by the chosen " "atlas: ").format(ctype, idx + 1), ", ".join(mgdm_intensity_priors)) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, contrast_image1) seg_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=contrast_image1, suffix='mgdm-seg', )) lbl_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=contrast_image1, suffix='mgdm-lbls')) mems_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=contrast_image1, suffix='mgdm-mems')) dist_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=contrast_image1, suffix='mgdm-dist')) if overwrite is False \ and os.path.isfile(seg_file) \ and os.path.isfile(lbl_file) \ and os.path.isfile(mems_file) \ and os.path.isfile(dist_file) : print("skip computation (use existing results)") output = { 'segmentation': seg_file, 'labels': lbl_file, 'memberships': mems_file, 'distance': dist_file } return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create mgdm instance mgdm = nighresjava.BrainMgdmMultiSegmentation2() # set mgdm parameters mgdm.setAtlasFile(atlas_file) mgdm.setTopologyLUTdirectory(topology_lut_dir) mgdm.setOutputImages('label_memberships') mgdm.setAdjustIntensityPriors(adjust_intensity_priors) mgdm.setComputePosterior(compute_posterior) mgdm.setPosteriorScale_mm(posterior_scale) mgdm.setDiffuseProbabilities(diffuse_probabilities) mgdm.setSteps(n_steps) mgdm.setMaxIterations(max_iterations) mgdm.setTopology(topology) mgdm.setNormalizeQuantitativeMaps(normalize_qmaps) # set to False for "quantitative" brain prior atlases # (version quant-3.0.5 and above) # load contrast image 1 and use it to set dimensions and resolution img = load_volume(contrast_image1) data = img.get_data() affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape mgdm.setDimensions(dimensions[0], dimensions[1], dimensions[2]) mgdm.setResolutions(resolution[0], resolution[1], resolution[2]) # convert orientation information to mgdm slice and orientation info sliceorder, LR, AP, IS = _get_mgdm_orientation(affine, mgdm) mgdm.setOrientations(sliceorder, LR, AP, IS) # input image 1 mgdm.setContrastImage1( nighresjava.JArray('float')((data.flatten('F')).astype(float))) mgdm.setContrastType1(contrast_type1) # if further contrast are specified, input them if contrast_image2 is not None: data = load_volume(contrast_image2).get_data() mgdm.setContrastImage2( nighresjava.JArray('float')((data.flatten('F')).astype(float))) mgdm.setContrastType2(contrast_type2) if contrast_image3 is not None: data = load_volume(contrast_image3).get_data() mgdm.setContrastImage3( nighresjava.JArray('float')((data.flatten('F')).astype(float))) mgdm.setContrastType3(contrast_type3) if contrast_image4 is not None: data = load_volume(contrast_image4).get_data() mgdm.setContrastImage4( nighresjava.JArray('float')( (data.flatten('F')).astype(float))) mgdm.setContrastType4(contrast_type4) # execute MGDM try: mgdm.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes seg_data = np.reshape( np.array(mgdm.getSegmentedBrainImage(), dtype=np.int32), dimensions, 'F') dist_data = np.reshape( np.array(mgdm.getLevelsetBoundaryImage(), dtype=np.float32), dimensions, 'F') # membership and labels output has a 4th dimension, set to 6 dimensions4d = [dimensions[0], dimensions[1], dimensions[2], 6] lbl_data = np.reshape( np.array(mgdm.getPosteriorMaximumLabels4D(), dtype=np.int32), dimensions4d, 'F') mems_data = np.reshape( np.array(mgdm.getPosteriorMaximumMemberships4D(), dtype=np.float32), dimensions4d, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_max'] = np.nanmax(seg_data) seg = nb.Nifti1Image(seg_data, affine, header) header['cal_max'] = np.nanmax(dist_data) dist = nb.Nifti1Image(dist_data, affine, header) header['cal_max'] = np.nanmax(lbl_data) lbls = nb.Nifti1Image(lbl_data, affine, header) header['cal_max'] = np.nanmax(mems_data) mems = nb.Nifti1Image(mems_data, affine, header) if save_data: save_volume(seg_file, seg) save_volume(dist_file, dist) save_volume(lbl_file, lbls) save_volume(mems_file, mems) output = { 'segmentation': seg_file, 'labels': lbl_file, 'memberships': mems_file, 'distance': dist_file } else: output = { 'segmentation': seg, 'labels': lbls, 'memberships': mems, 'distance': dist } return output
def multiscale_vessel_filter(input_image, structure_intensity='bright', filterType='RRF', propagationtype='diffusion', threshold=0.5, factor=0.5, max_diff=0.001, max_itr=100, scale_step=1.0, scales=4, prior_image=None, invert_prior=False, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Vessel filter with prior Uses an image filter to make a probabilistic image of ridge structures. Parameters ---------- input_image: niimg Image containing structure-of-interest structure_intensity: str Image intensity of structure-of-interest 'bright', 'dark', or 'both' (default is 'bright'). filterType: str Decide for a filter type: either RRF or Hessian (default is 'RRF') propagationtype: str Set the diffusion model of the filter: either 'diffusion' or 'belief' propagation model (default is 'diffusion') threshold: float Set the propability treshold to decide at what probability the detected structure should be seen as a vessel (default is 0.5) factor: float Diffusion factor between 0 and 100 (default is 0.5) max_diff: float maximal difference for stopping (default is 0.001) max_itr: int maximale iteration number (default is 100) scale_step: float Scaling step between diameters (default is 1) scales: int Number of scales to use (default is 4) prior_image: niimg (opt) Image prior for the region to include (positive) or exclude (negative) invert_prior: boolean, optional (default is False) In case there is a prior, the prior can be considered as negative prior (False) or as positive prior (True) save_data: bool, optional Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * segmentation: segmented vessel centerlines (_mvf-seg) * filtered: result of the vessel filtering step (_mvf-filter) * probability: probability score of segmented centerlines (_mvf-proba) * scale: discrete scale at which the centerlines are detected (_mvf-scale) * diameter: estimated vessel diameter (_mvf-dia) * length: lenght of continuous vessel segments (_mvf-length) * pv: partial volume estimate of vessels (_mvf-pv) * label: labeling of individual vessel segments (_mvf-label) * direction: estimated vessel direction (_mvf-dir) Notes ---------- Original Java module by Pierre-Louis Bazin and Julia Huck. """ print('\n Multiscale Vessel Filter') if save_data: output_dir = _output_dir_4saving(output_dir, input_image) vesselImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-seg')) filterImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-filter')) probaImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-proba')) scaleImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-scale')) diameterImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-dia')) lengthImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-length')) pvImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-pv')) labelImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-label')) directionImage_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=input_image, suffix='mvf-dir')) if overwrite is False \ and os.path.isfile(vesselImage_file) \ and os.path.isfile(filterImage_file) \ and os.path.isfile(probaImage_file) \ and os.path.isfile(scaleImage_file) \ and os.path.isfile(diameterImage_file) \ and os.path.isfile(pvImage_file) \ and os.path.isfile(lengthImage_file) \ and os.path.isfile(labelImage_file) \ and os.path.isfile(directionImage_file) : output = { 'segmentation': vesselImage_file, 'filtered': filterImage_file, 'probability': probaImage_file, 'scale': scaleImage_file, 'diameter': diameterImage_file, 'pv': pvImage_file, 'length': lengthImage_file, 'label': labelImage_file, 'direction': directionImage_file } return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create algorithm instance vessel_filter = nighresjava.MultiscaleVesselFilter() # set parameters vessel_filter.setStructureIntensity(structure_intensity) vessel_filter.setFilterShape(filterType) vessel_filter.setThreshold(threshold) vessel_filter.setScaleStep(scale_step) vessel_filter.setScaleNumber(scales) vessel_filter.setPropagationModel(propagationtype) vessel_filter.setDiffusionFactor(factor) vessel_filter.setMaxDiff(max_diff) vessel_filter.setMaxItr(max_itr) vessel_filter.setInvertPrior(invert_prior) # load images and set dimensions and resolution input_image = load_volume(input_image) data = input_image.get_data() affine = input_image.get_affine() header = input_image.get_header() resolution = [x.item() for x in header.get_zooms()] dimensions = input_image.shape # direction output has a 4th dimension, set to 3 dimensions4d = [dimensions[0], dimensions[1], dimensions[2], 3] vessel_filter.setDimensions(dimensions[0], dimensions[1], dimensions[2]) vessel_filter.setResolutions(resolution[0], resolution[1], resolution[2]) data = load_volume(input_image).get_data() vessel_filter.setInputImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) if not (prior_image == None): prior = load_volume(prior_image) data_prior = prior.get_data() vessel_filter.setPriorImage( nighresjava.JArray('float')( (data_prior.flatten('F')).astype(float))) # execute try: vessel_filter.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # Collect output vesselImage_data = np.reshape( np.array(vessel_filter.getSegmentedVesselImage(), dtype=np.float32), dimensions, 'F') filterImage_data = np.reshape( np.array(vessel_filter.getFilteredImage(), dtype=np.float32), dimensions, 'F') probaImage_data = np.reshape( np.array(vessel_filter.getProbabilityImage(), dtype=np.float32), dimensions, 'F') scaleImage_data = np.reshape( np.array(vessel_filter.getScaleImage(), dtype=np.float32), dimensions, 'F') diameterImage_data = np.reshape( np.array(vessel_filter.getDiameterImage(), dtype=np.float32), dimensions, 'F') pvImage_data = np.reshape( np.array(vessel_filter.getPVimage(), dtype=np.float32), dimensions, 'F') lengthImage_data = np.reshape( np.array(vessel_filter.getLengthImage(), dtype=np.float32), dimensions, 'F') labelImage_data = np.reshape( np.array(vessel_filter.getLabelImage(), dtype=np.float32), dimensions, 'F') directionImage_data = np.reshape( np.array(vessel_filter.getDirectionImage(), dtype=np.float32), dimensions4d, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_max'] = np.nanmax(vesselImage_data) vesselImage = nb.Nifti1Image(vesselImage_data, affine, header) header['cal_max'] = np.nanmax(filterImage_data) filterImage = nb.Nifti1Image(filterImage_data, affine, header) header['cal_max'] = np.nanmax(probaImage_data) probaImage = nb.Nifti1Image(probaImage_data, affine, header) header['cal_max'] = np.nanmax(scaleImage_data) scaleImage = nb.Nifti1Image(scaleImage_data, affine, header) header['cal_max'] = np.nanmax(diameterImage_data) diameterImage = nb.Nifti1Image(diameterImage_data, affine, header) header['cal_max'] = np.nanmax(pvImage_data) pvImage = nb.Nifti1Image(pvImage_data, affine, header) header['cal_max'] = np.nanmax(lengthImage_data) lengthImage = nb.Nifti1Image(lengthImage_data, affine, header) header['cal_max'] = np.nanmax(labelImage_data) labelImage = nb.Nifti1Image(labelImage_data, affine, header) header['cal_max'] = np.nanmax(directionImage_data) directionImage = nb.Nifti1Image(directionImage_data, affine, header) if save_data: save_volume(vesselImage_file, vesselImage) save_volume(filterImage_file, filterImage) save_volume(probaImage_file, probaImage) save_volume(scaleImage_file, scaleImage) save_volume(diameterImage_file, diameterImage) save_volume(pvImage_file, pvImage) save_volume(lengthImage_file, lengthImage) save_volume(labelImage_file, labelImage) save_volume(directionImage_file, directionImage) return { 'segmentation': vesselImage_file, 'filtered': filterImage_file, 'probability': probaImage_file, 'scale': scaleImage_file, 'diameter': diameterImage_file, 'pv': pvImage_file, 'length': lengthImage_file, 'label': labelImage_file, 'direction': directionImage_file } else: return { 'segmentation': vesselImage, 'filtered': filterImage, 'probability': probaImage, 'scale': scaleImage, 'diameter': diameterImage, 'pv': pvImage, 'length': lengthImage, 'label': labelImage, 'direction': directionImage }
def massp_atlasing(subjects, structures, contrasts, levelset_images=None, skeleton_images=None, contrast_images=None, save_data=False, overwrite=False, output_dir=None, file_name=None): """ MASSP Atlasing Builds a multi-atlas prior for MASSP Parameters ---------- subjects: int Number of atlas subjects structures: int Number of structures to parcellate contrasts: int Number of image intensity contrasts levelset_images: [niimg] Atlas shape levelsets indexed by (subjects,structures) skeleton_images: [niimg] Atlas shape skeletons indexed by (subjects,structures) contrast_images: [niimg] Atlas images to use in the parcellation, indexed by (subjects, contrasts) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * max_spatial_proba (niimg): Maximum spatial probability map (_massp-sproba) * max_spatial_label (niimg): Maximum spatial probability labels (_massp-slabel) * cond_hist (niimg): Conditional intensity histograms (_massp-chist) * max_skeleton_proba (niimg): Maximum skeleton probability map (_massp-kproba) * max_skeleton_label (niimg): Maximum skeleton probability labels (_massp-klabel) Notes ---------- Original Java module by Pierre-Louis Bazin. """ print('\nMASSP Atlasing') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, contrast_images[0][0]) spatial_proba_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=contrast_images[0][0], suffix='massp-sproba', )) spatial_label_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=contrast_images[0][0], suffix='massp-slabel')) condhist_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=contrast_images[0][0], suffix='massp-chist')) skeleton_proba_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=contrast_images[0][0], suffix='massp-kproba', )) skeleton_label_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=contrast_images[0][0], suffix='massp-klabel')) if overwrite is False \ and os.path.isfile(spatial_proba_file) \ and os.path.isfile(spatial_label_file) \ and os.path.isfile(condhist_file) \ and os.path.isfile(skeleton_proba_file) \ and os.path.isfile(skeleton_label_file): print("skip computation (use existing results)") output = { 'max_spatial_proba': spatial_proba_file, 'max_spatial_label': spatial_label_file, 'cond_hist': condhist_file, 'max_skeleton_proba': skeleton_proba_file, 'max_skeleton_label': skeleton_label_file } return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create instance massp = nighresjava.ConditionalShapeSegmentation() # set parameters massp.setNumberOfSubjectsObjectsBgAndContrasts(subjects, structures, 1, contrasts) massp.setOptions(True, False, False, False, True) # load target image for parameters # load a first image for dim, res img = load_volume(contrast_images[0][0]) data = img.get_data() header = img.get_header() affine = img.get_affine() trg_resolution = [x.item() for x in header.get_zooms()] trg_dimensions = data.shape massp.setTargetDimensions(trg_dimensions[0], trg_dimensions[1], trg_dimensions[2]) massp.setTargetResolutions(trg_resolution[0], trg_resolution[1], trg_resolution[2]) resolution = trg_resolution dimensions = trg_dimensions massp.setAtlasDimensions(dimensions[0], dimensions[1], dimensions[2]) massp.setAtlasResolutions(resolution[0], resolution[1], resolution[2]) # load the atlas structures and contrasts, if needed for sub in range(subjects): for struct in range(structures): print("load: " + str(levelset_images[sub][struct])) data = load_volume(levelset_images[sub][struct]).get_data() massp.setLevelsetImageAt( sub, struct, nighresjava.JArray('float')((data.flatten('F')).astype(float))) for contrast in range(contrasts): print("load: " + str(contrast_images[sub][contrast])) data = load_volume(contrast_images[sub][contrast]).get_data() massp.setContrastImageAt( sub, contrast, nighresjava.JArray('float')((data.flatten('F')).astype(float))) # execute first step scale = 1.0 try: scale = massp.computeAtlasPriors() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # clean up and go to second step levelset_images = None contrast_images = None for sub in range(subjects): for struct in range(structures): print("load: " + str(skeleton_images[sub][struct])) data = load_volume(skeleton_images[sub][struct]).get_data() massp.setSkeletonImageAt( sub, struct, nighresjava.JArray('float')((data.flatten('F')).astype(float))) try: massp.computeSkeletonPriors(scale) except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return skeleton_images = None # reshape output to what nibabel likes dimensions = (dimensions[0], dimensions[1], dimensions[2], massp.getBestDimension()) dimskel = (dimensions[0], dimensions[1], dimensions[2], int(massp.getBestDimension() / 4)) dims3Dtrg = (trg_dimensions[0], trg_dimensions[1], trg_dimensions[2]) intens_dims = (structures + 1, structures + 1, contrasts) intens_hist_dims = ((structures + 1) * (structures + 1), massp.getNumberOfBins() + 6, contrasts) spatial_proba_data = np.reshape( np.array(massp.getBestSpatialProbabilityMaps(dimensions[3]), dtype=np.float32), dimensions, 'F') spatial_label_data = np.reshape( np.array(massp.getBestSpatialProbabilityLabels(dimensions[3]), dtype=np.int32), dimensions, 'F') intens_hist_data = np.reshape( np.array(massp.getConditionalHistogram(), dtype=np.float32), intens_hist_dims, 'F') skeleton_proba_data = np.reshape( np.array(massp.getBestSkeletonProbabilityMaps(dimskel[3]), dtype=np.float32), dimskel, 'F') skeleton_label_data = np.reshape( np.array(massp.getBestSkeletonProbabilityLabels(dimskel[3]), dtype=np.int32), dimskel, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_max'] = np.nanmax(spatial_proba_data) spatial_proba = nb.Nifti1Image(spatial_proba_data, affine, header) header['cal_max'] = np.nanmax(spatial_label_data) spatial_label = nb.Nifti1Image(spatial_label_data, affine, header) chist = nb.Nifti1Image(intens_hist_data, None, None) header['cal_max'] = np.nanmax(skeleton_proba_data) skeleton_proba = nb.Nifti1Image(skeleton_proba_data, affine, header) header['cal_max'] = np.nanmax(skeleton_label_data) skeleton_label = nb.Nifti1Image(skeleton_label_data, affine, header) if save_data: save_volume(spatial_proba_file, spatial_proba) save_volume(spatial_label_file, spatial_label) save_volume(condhist_file, chist) save_volume(skeleton_proba_file, skeleton_proba) save_volume(skeleton_label_file, skeleton_label) output = { 'max_spatial_proba': spatial_proba_file, 'max_spatial_label': spatial_label_file, 'cond_hist': condhist_file, 'max_skeleton_proba': skeleton_proba_file, 'max_skeleton_label': skeleton_label_file } return output else: output = { 'max_spatial_proba': spatial_proba, 'max_spatial_label': spatial_label, 'cond_hist': chist, 'max_skeleton_proba': skeleton_proba, 'max_skeleton_label': skeleton_label } return output
def massp(target_images, structures=31, shape_atlas_probas=None, shape_atlas_labels=None, intensity_atlas_hist=None, skeleton_atlas_probas=None, skeleton_atlas_labels=None, map_to_target=None, max_iterations=80, max_difference=0.1, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Multi-contrast Anatomical Subcortical Structure parcellation (MASSP) Estimates subcortical structures based on a multi-atlas approach on shape Parameters ---------- target_images: [niimg] Input images to perform the parcellation from structures: int Number of structures to parcellate shape_atlas_probas: niimg (opt) Pre-computed shape atlas probabilities (default is loaded from nighres atlas) shape_atlas_labels: niimg (opt) Pre-computed shape atlas labels (default is loaded from nighres atlas) intensity_atlas_hist: niimg (opt) Pre-computed intensity atlas from the contrast images (default is loaded from nighres atlas) skeleton_atlas_probas: niimg (opt) Pre-computed skeleton atlas probabilities (default is loaded from nighres atlas) skeleton_atlas_labels: niimg (opt) Pre-computed skeleton atlas labels (default is loaded from nighres atlas) map_to_target: niimg Coordinate mapping from the atlas to the target (opt) max_iterations: int Maximum number of diffusion iterations to perform max_difference: float Maximum difference between diffusion steps save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * max_proba (niimg): Maximum probability map (_massp-proba) * max_label (niimg): Maximum probability labels (_massp-label) Notes ---------- Original Java module by Pierre-Louis Bazin. """ print('\nMASSP') # check topology_lut_dir and set default if not given topology_lut_dir = _check_topology_lut_dir(None) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, target_images[0]) proba_file = os.path.join( output_dir, _fname_4saving( module=__name__, file_name=file_name, rootfile=target_images[0], suffix='massp-proba', )) label_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=target_images[0], suffix='massp-label')) if overwrite is False \ and os.path.isfile(proba_file) \ and os.path.isfile(label_file): print("skip computation (use existing results)") output = {'max_proba': proba_file, 'max_label': label_file} return output contrasts = len(target_images) # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create instance massp = nighresjava.ConditionalShapeSegmentation() # set parameters massp.setNumberOfSubjectsObjectsBgAndContrasts(1, structures, 1, contrasts) massp.setOptions(True, False, False, False, True) massp.setDiffusionParameters(max_iterations, max_difference) # load target image for parameters print("load: " + str(target_images[0])) img = load_volume(target_images[0]) data = img.get_data() trg_affine = img.get_affine() trg_header = img.get_header() trg_resolution = [x.item() for x in trg_header.get_zooms()] trg_dimensions = data.shape massp.setTargetDimensions(trg_dimensions[0], trg_dimensions[1], trg_dimensions[2]) massp.setTargetResolutions(trg_resolution[0], trg_resolution[1], trg_resolution[2]) # target image 1 massp.setTargetImageAt( 0, nighresjava.JArray('float')((data.flatten('F')).astype(float))) # if further contrast are specified, input them for contrast in range(1, contrasts): print("load: " + str(target_images[contrast])) data = load_volume(target_images[contrast]).get_data() massp.setTargetImageAt( contrast, nighresjava.JArray('float')((data.flatten('F')).astype(float))) # if not specified, check if standard atlases are available or download them if ((intensity_atlas_hist is None) or (shape_atlas_probas is None) or (shape_atlas_labels is None) or (skeleton_atlas_probas is None) or (skeleton_atlas_labels is None)): if (not os.path.exists(DEFAULT_MASSP_ATLAS)): download_MASSP_atlas(overwrite=False) intensity_atlas_hist = DEFAULT_MASSP_HIST shape_atlas_probas = DEFAULT_MASSP_SPATIAL_PROBA shape_atlas_labels = DEFAULT_MASSP_SPATIAL_LABEL skeleton_atlas_probas = DEFAULT_MASSP_SKEL_PROBA skeleton_atlas_labels = DEFAULT_MASSP_SKEL_LABEL # load the shape and intensity atlases print("load: " + str(intensity_atlas_hist)) hist = load_volume(intensity_atlas_hist).get_data() massp.setConditionalHistogram( nighresjava.JArray('float')((hist.flatten('F')).astype(float))) print("load: " + str(shape_atlas_probas)) # load a first image for dim, res img = load_volume(shape_atlas_probas) pdata = img.get_data() header = img.get_header() affine = img.get_affine() resolution = [x.item() for x in header.get_zooms()] dimensions = pdata.shape massp.setAtlasDimensions(dimensions[0], dimensions[1], dimensions[2]) massp.setAtlasResolutions(resolution[0], resolution[1], resolution[2]) print("load: " + str(shape_atlas_labels)) ldata = load_volume(shape_atlas_labels).get_data() if map_to_target is not None: print("map atlas to subject") print("load: " + str(map_to_target)) mdata = load_volume(map_to_target).get_data() massp.setMappingToTarget( nighresjava.JArray('float')((mdata.flatten('F')).astype(float))) massp.setShapeAtlasProbasAndLabels( nighresjava.JArray('float')((pdata.flatten('F')).astype(float)), nighresjava.JArray('int')((ldata.flatten('F')).astype(int).tolist())) print("load: " + str(skeleton_atlas_probas)) pdata = load_volume(skeleton_atlas_probas).get_data() print("load: " + str(skeleton_atlas_labels)) ldata = load_volume(skeleton_atlas_labels).get_data() massp.setSkeletonAtlasProbasAndLabels( nighresjava.JArray('float')((pdata.flatten('F')).astype(float)), nighresjava.JArray('int')((ldata.flatten('F')).astype(int).tolist())) # execute try: massp.estimateTarget() massp.fastSimilarityDiffusion(4) massp.collapseToJointMaps() massp.precomputeStoppingStatistics(3.0) massp.topologyBoundaryDefinition("wcs", topology_lut_dir) massp.conditionalPrecomputedDirectVolumeGrowth(3.0) massp.collapseSpatialPriorMaps() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes dims3Dtrg = (trg_dimensions[0], trg_dimensions[1], trg_dimensions[2]) proba_data = np.reshape(np.array(massp.getFinalProba(), dtype=np.float32), dims3Dtrg, 'F') label_data = np.reshape(np.array(massp.getFinalLabel(), dtype=np.int32), dims3Dtrg, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects trg_header['cal_max'] = np.nanmax(proba_data) proba = nb.Nifti1Image(proba_data, trg_affine, trg_header) trg_header['cal_max'] = np.nanmax(label_data) label = nb.Nifti1Image(label_data, trg_affine, trg_header) if save_data: save_volume(proba_file, proba) save_volume(label_file, label) output = {'max_proba': proba_file, 'max_label': label_file} return output else: output = {'max_proba': proba, 'max_label': label} return output
def intensity_based_skullstripping(main_image, extra_image=None, noise_model='exponential', skip_zero_values=True, iterate=False, dilate_mask=0, topology_lut_dir=None, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Intensity-based skull stripping Estimate a brain mask for a dataset with good brain/background intensity separation (e.g. PD-weighted). An extra image can be used to ensure high intensities are preserved (e.g. T1 map, T2-weighted data or a probability map for a ROI). Parameters ---------- main_image: niimg Main Intensity Image extra_image: niimg, optional Extra image with high intensity at brain boundary noise_model: {'exponential','half-normal','exp+log-normal','half+log-normal'} Background noise model (default is 'exponential') skip_zero_values: bool Ignores voxels with zero value (default is True) iterate: bool Whether to iterate the estimation (may be unstable in some cases, default is False) dilate_mask: int Additional dilation (or erosion, if negative) of the brain mask (default is 0) topology_lut_dir: str, optional Path to directory in which topology files are stored (default is stored in TOPOLOGY_LUT_DIR) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * brain_mask (niimg): Binary brain mask (_istrip-mask) * brain_proba (niimg): Probability brain map (_istrip-proba) * main_masked (niimg): Masked main image (_istrip-main) * extra_masked (niimg): Masked extra map (_istrip-extra) Notes ---------- Original Java module by Pierre-Louis Bazin. Details on the algorithm can be found in [1]_ References ---------- .. [1] Bazin et al. (2014). A computational framework for ultra-high resolution cortical segmentation at 7 Tesla. DOI: 10.1016/j.neuroimage.2013.03.077 """ print('\nIntensity-based Skull Stripping') # check topology lut dir and set default if not given topology_lut_dir = _check_topology_lut_dir(topology_lut_dir) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, main_image) mask_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=main_image, suffix='istrip-mask')) proba_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=main_image, suffix='istrip-proba')) main_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=main_image, suffix='istrip-main')) if extra_image is not None: extra_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=extra_image, suffix='istrip-extra')) else: extra_file = None if overwrite is False \ and os.path.isfile(mask_file) \ and os.path.isfile(proba_file) \ and os.path.isfile(main_file) : print("skip computation (use existing results)") output = {'brain_mask': mask_file, 'brain_proba': proba_file, 'main_masked': main_file} if extra_file is not None: if os.path.isfile(extra_file) : output['extra_masked'] = extra_file return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create skulltripping instance algo = nighresjava.BrainIntensityBasedSkullStripping() # get dimensions and resolution from second inversion image main_img = load_volume(main_image) main_data = main_img.get_data() main_affine = main_img.affine main_hdr = main_img.header resolution = [x.item() for x in main_hdr.get_zooms()] dimensions = main_data.shape algo.setDimensions(dimensions[0], dimensions[1], dimensions[2]) algo.setResolutions(resolution[0], resolution[1], resolution[2]) algo.setMainIntensityImage(nighresjava.JArray('float')( (main_data.flatten('F')).astype(float))) # pass other inputs if extra_image is not None: extra_img = load_volume(extra_image) extra_data = extra_img.get_data() extra_affine = extra_img.affine extra_hdr = extra_img.header algo.setExtraIntensityImage(nighresjava.JArray('float')( (extra_data.flatten('F')).astype(float))) algo.setBackgroundNoiseModel(noise_model) algo.setIterativeEstimation(iterate) algo.setSkipZeroValues(skip_zero_values) algo.setAdditionalMaskDilation(dilate_mask) algo.setTopologyLUTdirectory(topology_lut_dir) # execute skull stripping try: algo.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs and potentially save main_masked_data = np.reshape(np.array( algo.getMaskedMainImage(), dtype=np.float32), dimensions, 'F') main_hdr['cal_max'] = np.nanmax(main_masked_data) main_masked = nb.Nifti1Image(main_masked_data, main_affine, main_hdr) mask_data = np.reshape(np.array(algo.getBrainMaskImage(), dtype=np.uint32), dimensions, 'F') main_hdr['cal_max'] = np.nanmax(mask_data) mask = nb.Nifti1Image(mask_data, main_affine, main_hdr) proba_data = np.reshape(np.array( algo.getForegroundProbabilityImage(), dtype=np.float32), dimensions, 'F') main_hdr['cal_max'] = np.nanmax(proba_data) proba = nb.Nifti1Image(proba_data, main_affine, main_hdr) if extra_image is not None: extra_data = np.reshape(np.array( algo.getMaskedExtraImage(), dtype=np.float32), dimensions, 'F') extra_hdr['cal_max'] = np.nanmax(extra_data) extra_masked = nb.Nifti1Image(extra_data, extra_affine, extra_hdr) if save_data: save_volume(main_file, main_masked) save_volume(mask_file, mask) save_volume(proba_file, proba) outputs = {'brain_mask': mask_file, 'brain_proba': proba_file, 'main_masked': main_file} if extra_image is not None: save_volume(extra_file, extra_masked) outputs['extra_masked'] = extra_file else: outputs = {'brain_mask': mask, 'brain_proba': proba, 'main_masked': main_masked} if extra_image is not None: outputs['extra_masked'] = extra_masked return outputs
def mp2rage_dura_estimation(second_inversion, skullstrip_mask, background_distance=5.0, output_type='dura_region', save_data=False, overwrite=False, output_dir=None, file_name=None): """ MP2RAGE dura estimation Filters a MP2RAGE brain image to obtain a probability map of dura matter. Parameters ---------- second_inversion: niimg Second inversion image derived from MP2RAGE sequence skullstrip_mask: niimg Skullstripping mask defining the approximate region including the brain background_distance: float Maximum distance within the mask for dura (default is 5.0 mm) output_type: {'dura_region','boundary','dura_prior','bg_prior', 'intens_prior'} Type of output result (default is 'dura_region') save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) return_filename: bool, optional Return filename instead of object Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (niimg): Dura probability image (_dura-proba) Notes ---------- Original Java module by Pierre-Louis Bazin. Details on the algorithm can be found in [1]_ and a presentation of the MP2RAGE sequence in [2]_ References ---------- .. [1] Bazin et al. (2014). A computational framework for ultra-high resolution cortical segmentation at 7 Tesla. DOI: 10.1016/j.neuroimage.2013.03.077 .. [2] Marques et al. (2010). MP2RAGE, a self bias-field corrected sequence for improved segmentation and T1-mapping at high field. DOI: 10.1016/j.neuroimage.2009.10.002 """ print('\nMP2RAGE Dura Estimation') # Check data file parameters if not save_data and return_filename: raise ValueError('save_data must be True if return_filename is True ') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, second_inversion) result_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=second_inversion, suffix='dura-proba')) if overwrite is False \ and os.path.isfile(result_file) : print("skip computation (use existing results)") output = {'result': result_file} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create skulltripping instance algo = nighresjava.BrainMp2rageDuraEstimation() # get dimensions and resolution from second inversion image inv2_img = load_volume(second_inversion) inv2_data = inv2_img.get_data() inv2_affine = inv2_img.affine inv2_hdr = inv2_img.header resolution = [x.item() for x in inv2_hdr.get_zooms()] dimensions = inv2_data.shape algo.setDimensions(dimensions[0], dimensions[1], dimensions[2]) algo.setResolutions(resolution[0], resolution[1], resolution[2]) algo.setSecondInversionImage( nighresjava.JArray('float')((inv2_data.flatten('F')).astype(float))) # pass other inputs mask_data = load_volume(skullstrip_mask).get_data() algo.setSkullStrippingMask( nighresjava.JArray('int')( (mask_data.flatten('F')).astype(int).tolist())) algo.setDistanceToBackground_mm(background_distance) algo.setOutputType(output_type) # execute skull stripping try: algo.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs and potentially save result_data = np.reshape(np.array(algo.getDuraImage(), dtype=np.float32), dimensions, 'F') inv2_hdr['cal_max'] = np.nanmax(result_data) result_img = nb.Nifti1Image(result_data, inv2_affine, inv2_hdr) if save_data: save_volume(result_file, result_img) outputs = {'result': result_file} else: outputs = {'result': result_img} return outputs
def surface_inflation(surface_mesh, step_size=0.75, max_iter=2000, max_curv=10.0, save_data=False, overwrite=False, output_dir=None, file_name=None): """Surface inflation Inflate a surface with the method of Tosun et al _[1]. Parameters ---------- surface_mesh: mesh Mesh model of the surface step_size: float Relaxation rate in [0, 1]: values closer to 1 are more stable but slower (default is 0.75) max_iter: int Maximum number of iterations (default is 2000) max_curv: float Desired maximum curvature (default is 10.0) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (mesh): Surface mesh dictionary of "points", "faces" and "data" showing the SOM coordinates on the mesh Notes ---------- Original Java module by Pierre-Louis Bazin """ print("\nSurface inflation") # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, surface_mesh) infl_file = os.path.join( output_dir, _fname_4saving(file_name=file_name, rootfile=surface_mesh, suffix='infl-mesh', ext='vtk')) if overwrite is False \ and os.path.isfile(infl_file) : print("skip computation (use existing results)") output = {'result': load_mesh(infl_file)} return output # start virtual machine if not running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # initiate class algorithm = nighresjava.SurfaceInflation() # load the data orig_mesh = load_mesh(surface_mesh) algorithm.setSurfacePoints( nighresjava.JArray('float')( (orig_mesh['points'].flatten('C')).astype(float))) algorithm.setSurfaceTriangles( nighresjava.JArray('int')( (orig_mesh['faces'].flatten('C')).astype(int).tolist())) algorithm.setStepSize(step_size) algorithm.setMaxIter(max_iter) algorithm.setMaxCurv(max_curv) # execute class try: algorithm.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs print("collect outputs") npt = int( np.array(algorithm.getInflatedSurfacePoints(), dtype=np.float32).shape[0] / 3) nfc = int( np.array(algorithm.getInflatedSurfaceTriangles(), dtype=np.int32).shape[0] / 3) print("surface...") orig_points = np.reshape( np.array(algorithm.getInflatedSurfacePoints(), dtype=np.float32), (npt, 3), 'C') orig_faces = np.reshape( np.array(algorithm.getInflatedSurfaceTriangles(), dtype=np.int32), (nfc, 3), 'C') orig_data = np.reshape( np.array(algorithm.getInflatedSurfaceValues(), dtype=np.float32), (npt), 'F') # create the mesh dictionary inflated_orig_mesh = { "points": orig_points, "faces": orig_faces, "data": orig_data } if save_data: print("saving...") save_mesh(infl_file, inflated_orig_mesh) return {'result': inflated_orig_mesh}
def parcellation_to_meshes(parcellation_image, connectivity="18/6", spacing = 0.0, smoothing=1.0, save_data=False, overwrite=False, output_dir=None, file_name=None): """Parcellation to meshes Creates a collection of triangulated meshes from a parcellation using a connectivity-consistent marching cube algorithm. Parameters ---------- parcellation_image: niimg Parcellation image to be turned into meshes connectivity: {"6/18","6/26","18/6","26/6"}, optional Choice of digital connectivity to build the mesh (default is 18/6) spacing: float, optional Added spacing between meshes for better visualization (default is 0.0) smoothing: float, optional Smoothing of the boundary for prettier meshes, high values may bring small distortions (default is 1.0) save_data: bool, optional Save output data to file (default is False) overwrite: bool, optional Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result ([mesh]): A list of surface mesh dictionaries of "points" and "faces" (_p2m-mesh) Notes ---------- Ported from original Java module by Pierre-Louis Bazin. Original algorithm from [1]_ and adapted from [2]_. References ---------- .. [1] Han et al (2003). A Topology Preserving Level Set Method for Geometric Deformable Models doi: .. [2] Lucas et al (2010). The Java Image Science Toolkit (JIST) for Rapid Prototyping and Publishing of Neuroimaging Software doi: """ print("\nParcellation to Meshes") # first we need to know how many meshes to build # load the data p_img = load_volume(parcellation_image) p_data = p_img.get_fdata() hdr = p_img.header aff = p_img.affine resolution = [x.item() for x in hdr.get_zooms()] dimensions = p_data.shape # count the labels (incl. background) labels = numpy.unique(p_data) print("found labels: "+str(labels)) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, parcellation_image) mesh_files = [] for num,label in enumerate(labels): # exclude background as first label if num>0: mesh_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=parcellation_image, suffix='p2m-mesh'+str(num),ext="vtk")) mesh_files.append(mesh_file) if overwrite is False : missing = False for num,label in enumerate(labels): if num>0: if not os.path.isfile(mesh_files[num-1]) : missing = True if not missing: print("skip computation (use existing results)") output = {'result': mesh_files} return output # start virtual machine if not running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # build a simplified levelset for each structure meshes = [] for num,label in enumerate(labels): if num>0: lvl_data = -1.0*(p_data==label) +1.0*(p_data!=label) # initiate class inside the loop, to avoid garbage collection issues with many labels (??) algorithm = nighresjava.SurfaceLevelsetToMesh() algorithm.setResolutions(resolution[0], resolution[1], resolution[2]) algorithm.setDimensions(dimensions[0], dimensions[1], dimensions[2]) algorithm.setLevelsetImage(nighresjava.JArray('float')( (lvl_data.flatten('F')).astype(float))) algorithm.setConnectivity(connectivity) algorithm.setZeroLevel(0.0) algorithm.setInclusive(True) algorithm.setSmoothing(smoothing) # execute class try: algorithm.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs npt = int(numpy.array(algorithm.getPointList(), dtype=numpy.float32).shape[0]/3) mesh_points = numpy.reshape(numpy.array(algorithm.getPointList(), dtype=numpy.float32), (npt,3), 'C') nfc = int(numpy.array(algorithm.getTriangleList(), dtype=numpy.int32).shape[0]/3) mesh_faces = numpy.reshape(numpy.array(algorithm.getTriangleList(), dtype=numpy.int32), (nfc,3), 'C') mesh_label = label*numpy.ones((npt,1)) # create the mesh dictionary mesh = {"points": mesh_points, "faces": mesh_faces, "data": mesh_label} meshes.append(mesh) # if needed, spread values away from center if spacing>0: center = numpy.zeros((1,3)) for num,label in enumerate(labels): if num>0: center = center + numpy.mean(meshes[num-1]['points'], axis=0) center = center/(len(labels)-1) for num,label in enumerate(labels): if num>0: mesh0 = numpy.mean(meshes[num-1]['points'], axis=0) meshes[num-1]['points'] = meshes[num-1]['points']-mesh0 + spacing*(mesh0-center) + center if save_data: for num,label in enumerate(labels): if num>0: save_mesh(mesh_files[num-1], meshes[num-1]) return {'result': mesh_files} else: return {'result': meshes}
def levelset_to_mesh(levelset_image, connectivity="18/6", level=0.0, inclusive=True, save_data=False, overwrite=False, output_dir=None, file_name=None): """Levelset to mesh Creates a triangulated mesh from the distance to a levelset surface representation using a connectivity-consistent marching cube algorithm. Parameters ---------- levelset_image: niimg Levelset image to be turned into a mesh connectivity: {"6/18","6/26","18/6","26/6"}, optional Choice of digital connectivity to build the mesh (default is 18/6) level: float, optional Value of the levelset function to use as isosurface (default is 0) inclusive: bool, optional Whether voxels at the exact 'level' value are inside the isosurface (default is True) save_data: bool, optional Save output data to file (default is False) overwrite: bool, optional Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (mesh): Surface mesh dictionary of "points" and "faces" (_l2m-mesh) Notes ---------- Ported from original Java module by Pierre-Louis Bazin. Original algorithm from [1]_ and adapted from [2]_. References ---------- .. [1] Han et al (2003). A Topology Preserving Level Set Method for Geometric Deformable Models doi: .. [2] Lucas et al (2010). The Java Image Science Toolkit (JIST) for Rapid Prototyping and Publishing of Neuroimaging Software doi: """ print("\nLevelset to Mesh") # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, levelset_image) mesh_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=levelset_image, suffix='l2m-mesh', ext="vtk")) if overwrite is False \ and os.path.isfile(mesh_file) : print("skip computation (use existing results)") output = {'result': mesh_file} return output # start virtual machine if not running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # initiate class algorithm = nighresjava.SurfaceLevelsetToMesh() # load the data lvl_img = load_volume(levelset_image) lvl_data = lvl_img.get_data() hdr = lvl_img.header aff = lvl_img.affine resolution = [x.item() for x in hdr.get_zooms()] dimensions = lvl_data.shape algorithm.setResolutions(resolution[0], resolution[1], resolution[2]) algorithm.setDimensions(dimensions[0], dimensions[1], dimensions[2]) algorithm.setLevelsetImage( nighresjava.JArray('float')((lvl_data.flatten('F')).astype(float))) algorithm.setConnectivity(connectivity) algorithm.setZeroLevel(level) algorithm.setInclusive(inclusive) # execute class try: algorithm.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs npt = int( np.array(algorithm.getPointList(), dtype=np.float32).shape[0] / 3) mesh_points = np.reshape( np.array(algorithm.getPointList(), dtype=np.float32), (npt, 3), 'C') nfc = int( np.array(algorithm.getTriangleList(), dtype=np.int32).shape[0] / 3) mesh_faces = np.reshape( np.array(algorithm.getTriangleList(), dtype=np.int32), (nfc, 3), 'C') # create the mesh dictionary mesh = {"points": mesh_points, "faces": mesh_faces} if save_data: save_mesh_geometry(mesh_file, mesh) return {'result': mesh_file} else: return {'result': mesh}
def lcpca_denoising(image_list, phase_list=None, ngb_size=4, stdev_cutoff=1.05, min_dimension=0, max_dimension=-1, unwrap=True, rescale_phs=True, process_2d=False, use_rmt=False, save_data=False, overwrite=False, output_dir=None, file_names=None): """ LCPCA denoising Denoise multi-contrast data with a local complex-valued PCA-based method Parameters ---------- image_list: [niimg] List of input images to denoise phase_list: [niimg], optional List of input phase to denoise (order must match that of image_list) ngb_size: int, optional Size of the local PCA neighborhood, to be increased with number of inputs (default is 4) stdev_cutoff: float, optional Factor of local noise level to remove PCA components. Higher values remove more components (default is 1.05) min_dimension: int, optional Minimum number of kept PCA components (default is 0) max_dimension: int, optional Maximum number of kept PCA components (default is -1 for all components) unwrap: bool, optional Whether to unwrap the phase data of keep it as is (default is True) rescale_phs: bool, optional Whether to rescale the phase data of keep it as is, assuming radians (default is True) process_2d: bool, optional Whether to denoise in 2D, for instance when acquiring a thin slab of data (default is False) use_rmt: bool, optional Whether to use random matrix theory rather than noise fitting to estimate the noise threshold (default is False) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_names: [str], optional Desired base names for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * denoised ([niimg]): The list of denoised input images (_lcpca_den) * dimensions (niimg): Map of the estimated local dimensions (_lcpca_dim) * residuals (niimg): Estimated residuals between input and denoised images (_lcpca_err) Notes ---------- Original Java module by Pierre-Louis Bazin. Algorithm adapted from [1]_ with a different approach to set the adaptive noise threshold and additional processing to handle the phase data. References ---------- .. [1] Manjon, Coupe, Concha, Buades, Collins, Robles (2013). Diffusion Weighted Image Denoising Using Overcomplete Local PCA doi:10.1371/journal.pone.0073021 """ print('\nLCPCA denoising') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image_list[0]) den_files = [] for idx, image in enumerate(image_list): if file_names is None: name = None else: name = file_names[idx] den_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=name, rootfile=image, suffix='lcpca-den')) den_files.append(den_file) if (phase_list != None): for idx, image in enumerate(phase_list): if file_names is None: name = None else: name = file_names[len(image_list) + idx] den_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=name, rootfile=image, suffix='lcpca-den')) den_files.append(den_file) if file_names is None: name = None else: name = file_names[0] dim_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=name, rootfile=image_list[0], suffix='lcpca-dim')) err_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=name, rootfile=image_list[0], suffix='lcpca-res')) if overwrite is False \ and os.path.isfile(dim_file) \ and os.path.isfile(err_file) : # check that the denoised data is the same too missing = False for den_file in den_files: if not os.path.isfile(den_file): missing = True if not missing: print("skip computation (use existing results)") denoised = [] for den_file in den_files: denoised.append(den_file) output = { 'denoised': denoised, 'dimensions': dim_file, 'residuals': err_file } return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create lcpca instance lcpca = nighresjava.LocalComplexPCADenoising() # load first image and use it to set dimensions and resolution img = load_volume(image_list[0]) data = img.get_fdata() #data = data[0:10,0:10,0:10] affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape dim3D = (dimensions[0], dimensions[1], dimensions[2]) # set lcpca parameters lcpca.setImageNumber(len(image_list)) eigdim = len(image_list) if (phase_list != None): if len(dimensions) > 3: eigdim = 2 * eigdim * dimensions[3] else: eigdim = 2 * eigdim if (len(phase_list) != len(image_list)): print('\nmismatch of magnitude and phase images: abort') return if len(dimensions) > 3: lcpca.setDimensions(dimensions[0], dimensions[1], dimensions[2], dimensions[3]) else: lcpca.setDimensions(dimensions[0], dimensions[1], dimensions[2]) lcpca.setResolutions(resolution[0], resolution[1], resolution[2]) # input images # important: set image number before adding images for idx, image in enumerate(image_list): #print('\nloading ('+str(idx)+'): '+image) data = load_volume(image).get_fdata() #data = data[0:10,0:10,0:10] lcpca.setMagnitudeImageAt( idx, nighresjava.JArray('float')((data.flatten('F')).astype(float))) # input phase, if specified if (phase_list != None): for idx, image in enumerate(phase_list): #print('\nloading '+image) data = load_volume(image).get_fdata() #data = data[0:10,0:10,0:10] lcpca.setPhaseImageAt( idx, nighresjava.JArray('float')((data.flatten('F')).astype(float))) # set algorithm parameters lcpca.setPatchSize(ngb_size) lcpca.setStdevCutoff(stdev_cutoff) lcpca.setMinimumDimension(min_dimension) lcpca.setMaximumDimension(max_dimension) lcpca.setUnwrapPhase(unwrap) lcpca.setRescalePhase(rescale_phs) lcpca.setProcessSlabIn2D(process_2d) lcpca.setRandomMatrixTheory(use_rmt) # execute the algorithm try: lcpca.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes denoised_list = [] for idx, image in enumerate(image_list): den_data = np.reshape( np.array(lcpca.getDenoisedMagnitudeImageAt(idx), dtype=np.float32), dimensions, 'F') header['cal_min'] = np.nanmin(den_data) header['cal_max'] = np.nanmax(den_data) denoised = nb.Nifti1Image(den_data, affine, header) denoised_list.append(denoised) if save_data: save_volume(den_files[idx], denoised) if (phase_list != None): for idx, image in enumerate(phase_list): den_data = np.reshape( np.array(lcpca.getDenoisedPhaseImageAt(idx), dtype=np.float32), dimensions, 'F') header['cal_min'] = np.nanmin(den_data) header['cal_max'] = np.nanmax(den_data) denoised = nb.Nifti1Image(den_data, affine, header) denoised_list.append(denoised) if save_data: save_volume(den_files[idx + len(image_list)], denoised) dim_data = np.reshape( np.array(lcpca.getLocalDimensionImage(), dtype=np.float32), dim3D, 'F') err_data = np.reshape(np.array(lcpca.getNoiseFitImage(), dtype=np.float32), dim3D, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(dim_data) header['cal_max'] = np.nanmax(dim_data) dim = nb.Nifti1Image(dim_data, affine, header) header['cal_min'] = np.nanmin(err_data) header['cal_max'] = np.nanmax(err_data) err = nb.Nifti1Image(err_data, affine, header) if save_data: save_volume(dim_file, dim) save_volume(err_file, err) output = { 'denoised': den_files, 'dimensions': dim_file, 'residuals': err_file } else: output = { 'denoised': denoised_list, 'dimensions': dim, 'residuals': err } return output
def levelset_fusion(levelset_images, correct_topology=True, topology_lut_dir=None, max_distance=10.0, smooth_curvature=0.1, follow_stdev=False, sharpen=0.0, save_data=False, overwrite=False, output_dir=None, file_name=None): """Levelset fusion Creates an average levelset surface representations from a collection of levelset surfaces, with same avearage volume and (optionally) spherical topology Parameters ---------- levelset_images: niimg List of levelset images to combine. correct_topology: bool, optional Corrects the average shape to ensure correct topology (default is True) topology_lut_dir: str, optional Path to directory in which topology files are stored (default is stored in TOPOLOGY_LUT_DIR) max_distance: float, optional Maximum distance for levelset combination (default is 10.0 voxels) smooth_curvature: float, optional Curvature smoothing of the final average in [0,1] (default is 0) follow_stdev: bool, optional Grows preferrentially in regions of higher variance (default is False) sharpen: float, optional Sharpening of average by weighted average with a Laplacian filtered version [0,1] (default is 0) save_data: bool, optional Save output data to file (default is False) overwrite: bool, optional Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (niimg): Levelset representation of combined surface (_lsf-avg) Notes ---------- Original Java module by Pierre-Louis Bazin """ print("\nLevelset Shape Fusion") # check topology_lut_dir and set default if not given topology_lut_dir = _check_topology_lut_dir(topology_lut_dir) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, levelset_images[0]) levelset_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=levelset_images[0], suffix='lsf-avg')) print('output file: ' + levelset_file) if overwrite is False \ and os.path.isfile(levelset_file) : print("skip computation (use existing results)") output = {'result': levelset_file} return output # start virtual machine if not running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # initiate class algorithm = nighresjava.LevelsetShapeFusion() #algorithm = nighresjava.ShapeLevelsetFusion() # load the data nsubjects = len(levelset_images) img = load_volume(levelset_images[0]) hdr = img.header aff = img.affine resolution = [x.item() for x in hdr.get_zooms()] dimensions = img.get_fdata().shape algorithm.setNumberOfImages(nsubjects) algorithm.setResolutions(resolution[0], resolution[1], resolution[2]) algorithm.setDimensions(dimensions[0], dimensions[1], dimensions[2]) levelset_data = [] for idx in range(len(levelset_images)): img = load_volume(levelset_images[idx]) data = img.get_fdata() algorithm.setLevelsetImageAt( idx, nighresjava.JArray('float')((data.flatten('F')).astype(float))) algorithm.setCorrectSkeletonTopology(correct_topology) algorithm.setTopologyLUTdirectory(topology_lut_dir) algorithm.setLevelsetDistance(max_distance) algorithm.setCurvatureSmoothing(smooth_curvature) algorithm.setSlopeSharpening(sharpen) algorithm.setIncludeVariance(follow_stdev) # execute class try: algorithm.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs levelset_data = np.reshape( np.array(algorithm.getLevelsetAverage(), dtype=np.float32), dimensions, 'F') hdr['cal_min'] = np.nanmin(levelset_data) hdr['cal_max'] = np.nanmax(levelset_data) levelset = nb.Nifti1Image(levelset_data, aff, hdr) if save_data: save_volume(levelset_file, levelset) return {'result': levelset_file} else: return {'result': levelset}
def recursive_ridge_diffusion(input_image, ridge_intensities, ridge_filter, surface_levelset=None, orientation='undefined', loc_prior=None, min_scale=0, max_scale=3, diffusion_factor=1.0, similarity_scale=0.1, max_iter=100, max_diff=1e-3, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Recursive Ridge Diffusion Extracts planar of tubular structures across multiple scales, with an optional directional bias. Parameters ---------- input_image: niimg Input image ridge_intensities: {'bright','dark','both'} Which intensities to consider for the filtering ridge_filter: {'2D','1D','0D'} Whether to filter for 2D ridges, 1D vessels, or 0D holes surface_levelset: niimg, optional Level set surface to restrict the orientation of the detected features orientation: {'undefined','parallel','orthogonal'} The orientation of features to keep with regard to the surface or its normal loc_prior: niimg, optional Location prior image to restrict the search for features min_scale: int Minimum scale (in voxels) to look for features (default is 0) max_scale: int Maximum scale (in voxels) to look for features (default is 3) diffusion_factor: float Scaling factor for the diffusion weighting in [0,1] (default is 1.0) similarity_scale: float Scaling of the similarity function as a factor of intensity range max_iter: int Maximum number of diffusion iterations max_diff: int Maximum difference to stop the diffusion save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * filter (niimg): raw filter response (_rrd-filter) * propagation (niimg): propagated probabilistic response after diffusion (_rrd-propag) * scale (niimg): scale of the detection filter (_rrd-scale) * ridge_dir (niimg): estimated local ridge direction (_rrd-dir) * ridge_pv (niimg): ridge partial volume map, taking size into account (_rrd-pv) * ridge_size (niimg): estimated size of each detected component (rrd-size) Notes ---------- Original Java module by Pierre-Louis Bazin. Extension of the recursive ridge filter in [1]_. References ---------- .. [1] Bazin et al (2016), Vessel segmentation from quantitative susceptibility maps for local oxygenation venography, Proc ISBI. """ print('\n Recursive Ridge Diffusion') # check atlas_file and set default if not given #atlas_file = _check_atlas_file(atlas_file) # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, input_image) filter_file = _fname_4saving(file_name=file_name, rootfile=input_image, suffix='rrd-filter') propagation_file = _fname_4saving(file_name=file_name, rootfile=input_image, suffix='rrd-propag') scale_file = _fname_4saving(file_name=file_name, rootfile=input_image, suffix='rrd-scale') ridge_direction_file = _fname_4saving(file_name=file_name, rootfile=input_image, suffix='rrd-dir') ridge_pv_file = _fname_4saving(file_name=file_name, rootfile=input_image, suffix='rrd-pv') ridge_size_file = _fname_4saving(file_name=file_name, rootfile=input_image, suffix='rrd-size') if overwrite is False \ and os.path.isfile(filter_file) \ and os.path.isfile(propagation_file) \ and os.path.isfile(scale_file) \ and os.path.isfile(ridge_direction_file) \ and os.path.isfile(ridge_pv_file) \ and os.path.isfile(ridge_size_file) : print("skip computation (use existing results)") output = { 'filter': load_volume(filter_file), 'propagation': load_volume(propagation_file), 'scale': load_volume(scale_file), 'ridge_dir': load_volume(ridge_direction_file), 'ridge_pv': load_volume(ridge_pv_file), 'ridge_size': load_volume(ridge_size_file) } return output # load input image and use it to set dimensions and resolution img = load_volume(input_image) data = img.get_data() affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape if (len(dimensions) < 3): dimensions = (dimensions[0], dimensions[1], 1) if (len(resolution) < 3): resolution = [resolution[0], resolution[1], 1.0] # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create extraction instance if dimensions[2] is 1: rrd = nighresjava.FilterRecursiveRidgeDiffusion2D() else: rrd = nighresjava.FilterRecursiveRidgeDiffusion() # set parameters rrd.setRidgeIntensities(ridge_intensities) rrd.setRidgeFilter(ridge_filter) rrd.setOrientationToSurface(orientation) rrd.setMinimumScale(min_scale) rrd.setMaximumScale(max_scale) rrd.setDiffusionFactor(diffusion_factor) rrd.setSimilarityScale(similarity_scale) rrd.setPropagationModel("none") if max_iter > 0: rrd.setPropagationModel("diffusion") rrd.setMaxIterations(max_iter) rrd.setMaxDifference(max_diff) rrd.setDimensions(dimensions[0], dimensions[1], dimensions[2]) rrd.setResolutions(resolution[0], resolution[1], resolution[2]) # input input_image rrd.setInputImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) # input surface_levelset : dirty fix for the case where surface image not input try: data = load_volume(surface_levelset).get_data() rrd.setSurfaceLevelSet( nighresjava.JArray('float')((data.flatten('F')).astype(float))) except: print("no surface image") # input location prior image : loc_prior is optional try: data = load_volume(loc_prior).get_data() rrd.setLocationPrior( nighresjava.JArray('float')((data.flatten('F')).astype(float))) except: print("no location prior image") # execute Extraction try: rrd.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes filter_data = np.reshape( np.array(rrd.getFilterResponseImage(), dtype=np.float32), dimensions, 'F') propagation_data = np.reshape( np.array(rrd.getPropagatedResponseImage(), dtype=np.float32), dimensions, 'F') scale_data = np.reshape( np.array(rrd.getDetectionScaleImage(), dtype=np.int32), dimensions, 'F') ridge_direction_data = np.reshape( np.array(rrd.getRidgeDirectionImage(), dtype=np.float32), (dimensions[0], dimensions[1], dimensions[2], 3), 'F') ridge_pv_data = np.reshape( np.array(rrd.getRidgePartialVolumeImage(), dtype=np.float32), dimensions, 'F') ridge_size_data = np.reshape( np.array(rrd.getRidgeSizeImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_max'] = np.nanmax(filter_data) filter_img = nb.Nifti1Image(filter_data, affine, header) header['cal_max'] = np.nanmax(propagation_data) propag_img = nb.Nifti1Image(propagation_data, affine, header) header['cal_max'] = np.nanmax(scale_data) scale_img = nb.Nifti1Image(scale_data, affine, header) header['cal_max'] = np.nanmax(ridge_direction_data) ridge_dir_img = nb.Nifti1Image(ridge_direction_data, affine, header) header['cal_max'] = np.nanmax(ridge_pv_data) ridge_pv_img = nb.Nifti1Image(ridge_pv_data, affine, header) header['cal_max'] = np.nanmax(ridge_size_data) ridge_size_img = nb.Nifti1Image(ridge_size_data, affine, header) if save_data: save_volume(os.path.join(output_dir, filter_file), filter_img) save_volume(os.path.join(output_dir, propagation_file), propag_img) save_volume(os.path.join(output_dir, scale_file), scale_img) save_volume(os.path.join(output_dir, ridge_direction_file), ridge_dir_img) save_volume(os.path.join(output_dir, ridge_pv_file), ridge_pv_img) save_volume(os.path.join(output_dir, ridge_size_file), ridge_size_img) return { 'filter': filter_img, 'propagation': propag_img, 'scale': scale_img, 'ridge_dir': ridge_dir_img, 'ridge_pv': ridge_pv_img, 'ridge_size': ridge_size_img }
def linear_fiber_interpolation(image, references, mapped_proba, mapped_theta, mapped_lambda, mapped_dim=1, weights=None, patch=2, search=3, median=True, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Linear fiber interpolation Uses a simple non-local means approach adapted from [1]_ to interpolate extracted line information across slices Parameters ---------- image: niimg Input 2D image references: [niimg] Reference 2D images to use for intensity mapping mapped_proba: [niimg] Corresponding mapped 3D images to use for line probabilites mapped_theta: [niimg] Corresponding mapped 3D images to use for line directions mapped_lambda: [niimg] Corresponding mapped 3D images to use for line lengths mapped_dim: int Thrid dimension of the mapped 3D images (default is 1) weights: [float], optional Weight factors for the 2D images (default is 1 for all) patch: int, optional Maximum distance to define patch size (default is 2) search: int, optional Maximum distance to define search window size (default is 3) median: bool Whether to use median instead of mean of the patches (default is True) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * proba (niimg): The probability mapped input * theta (niimg): The direction mapped input * lambda (niimg): The length mapped input Notes ---------- Original Java module by Pierre-Louis Bazin. References ---------- .. [1] P. Coupé, J.V. Manjón, V. Fonov, J. Pruessner, M. Robles, D.L. Collins, Patch-based segmentation using expert priors: Application to hippocampus and ventricle msegmentation, NeuroImage, vol. 54, pp. 940--954, 2011. """ print('\nLinear fiber interpolation') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image) proba_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image, suffix='lfi-proba')) theta_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image, suffix='lfi-theta')) lambda_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image, suffix='lfi-lambda')) if overwrite is False \ and os.path.isfile(proba_file) \ and os.path.isfile(theta_file) \ and os.path.isfile(lambda_file) : print("skip computation (use existing results)") output = { 'proba': proba_file, 'theta': theta_file, 'lambda': lambda_file } return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create instance lfi = nighresjava.NonlocalLinearFiberInterpolation() # set parameters # load image and use it to set dimensions and resolution img = load_volume(image) data = img.get_fdata() affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape lfi.setDimensions(dimensions[0], dimensions[1], 1) lfi.setLineNumber(mapped_dim) lfi.setInputImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) lfi.setReferenceNumber(len(references)) for idx, ref in enumerate(references): data = load_volume(ref).get_fdata() lfi.setReferenceImageAt( idx, nighresjava.JArray('float')((data.flatten('F')).astype(float))) data = load_volume(mapped_proba[idx]).get_fdata() lfi.setMappedProbaAt( idx, nighresjava.JArray('float')((data.flatten('F')).astype(float))) data = load_volume(mapped_theta[idx]).get_fdata() lfi.setMappedThetaAt( idx, nighresjava.JArray('float')((data.flatten('F')).astype(float))) data = load_volume(mapped_lambda[idx]).get_fdata() lfi.setMappedLambdaAt( idx, nighresjava.JArray('float')((data.flatten('F')).astype(float))) if weights is not None: lfi.setWeightAt(idx, weights[idx]) else: lfi.setWeightAt(idx, 1.0) # set algorithm parameters lfi.setPatchDistance(patch) lfi.setSearchDistance(search) lfi.setUseMedian(median) # execute the algorithm try: lfi.execute2D() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes dimensions = (dimensions[0], dimensions[1], mapped_dim) data = np.reshape(np.array(lfi.getMappedProba(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(data) header['cal_max'] = np.nanmax(data) result_proba = nb.Nifti1Image(data, affine, header) # reshape output to what nibabel likes dimensions = (dimensions[0], dimensions[1], mapped_dim) data = np.reshape(np.array(lfi.getMappedTheta(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(data) header['cal_max'] = np.nanmax(data) result_theta = nb.Nifti1Image(data, affine, header) # reshape output to what nibabel likes dimensions = (dimensions[0], dimensions[1], mapped_dim) data = np.reshape(np.array(lfi.getMappedLambda(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(data) header['cal_max'] = np.nanmax(data) result_lambda = nb.Nifti1Image(data, affine, header) if save_data: save_volume(proba_file, result_proba) save_volume(theta_file, result_theta) save_volume(lambda_file, result_lambda) return { 'proba': proba_file, 'theta': theta_file, 'lambda': lambda_file } else: return { 'proba': result_proba, 'theta': result_theta, 'lambda': result_lambda }
def segmentation_statistics(segmentation, intensity=None, template=None, statistics=None, output_csv=None, atlas=None, skip_first=True, ignore_zero=True, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Segmentation Statistics Compute various statistics of image segmentations Parameters ---------- segmentation: niimg Input segmentation image intensity: niimg, optional Input intensity image for intensity-based statistics template: niimg, optional Input template segmentation for comparisons statistics: [str] Statistics to compute. Available options include: "Voxels", "Volume", "Center_of_mass", "Mean_intensity", "Std_intensity", "Sum_intensity", "10_intensity","25_intensity", "50_intensity", "75_intensity","90_intensity", "Median_intensity", "IQR_intensity", "SNR_intensity","rSNR_intensity", "Volumes", "Dice_overlap", "Jaccard_overlap", "Volume_difference", "False_positives" "False_negatives", "Dilated_Dice_overlap","Dilated_false_positive", "Dilated_false_negative", "Dilated_false_negative_volume", "Dilated_false_positive_volume", "Center_distance", "Detected_clusters", "False_detections", "Cluster_numbers", "Mean_cluster_sizes", "Cluster_maps", "Average_surface_distance", "Average_surface_difference", "Average_squared_surface_distance", "Hausdorff_distance" output_csv: str File name of the statistics file to generate or expand atlas: str, optional File name of an atlas file defining the segmentation labels skip_first: bool, optional Whether to skip the first segmentation label (usually representing the background, default is True) ignore_zero: bool, optional Whether to ignore zero intensity values in the intensity image (default is True) save_data: bool, optional Save output data to file (default is False) overwrite: bool, optional Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * csv (str): The csv statistics file * map (niimg): Map of the estimated statistic, if relevant (stat-map) Notes ---------- Original Java module by Pierre-Louis Bazin. """ print('\nSegmentation statistics') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, segmentation) map_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=segmentation, suffix='stat-map')) csv_file = os.path.join(output_dir, output_csv) if overwrite is False \ and os.path.isfile(csv_file) : # check that the denoised data is the same too print("append results to existing csv file") if overwrite is True: # delete current stats file to start from the beginning os.remove(csv_file) else: csv_file = output_csv # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create algorithm instance stats = nighresjava.StatisticsSegmentation() # load first image and use it to set dimensions and resolution img = load_volume(segmentation) data = img.get_fdata() affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape if len(dimensions) > 2: stats.setDimensions(dimensions[0], dimensions[1], dimensions[2]) stats.setResolutions(resolution[0], resolution[1], resolution[2]) else: stats.setDimensions(dimensions[0], dimensions[1], 1) stats.setResolutions(resolution[0], resolution[1], 1.0) stats.setSegmentationImage( nighresjava.JArray('int')((data.flatten('F')).astype(int).tolist())) stats.setSegmentationName( _fname_4saving(module=__name__, rootfile=segmentation)) # other input images, if any if intensity is not None: data = load_volume(intensity).get_fdata() stats.setIntensityImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) stats.setIntensityName( _fname_4saving(module=__name__, rootfile=intensity)) if template is not None: data = load_volume(template).get_fdata() stats.setTemplateImage( nighresjava.JArray('int')( (data.flatten('F')).astype(int).tolist())) stats.setTemplateName( _fname_4saving(module=__name__, rootfile=template)) # set algorithm parameters if atlas is not None: stats.setAtlasFile(atlas) stats.setSkipFirstLabel(skip_first) stats.setIgnoreZeroIntensities(ignore_zero) stats.setStatisticNumber(len(statistics)) for idx, stat in enumerate(statistics): stats.setStatisticAt(idx, stat) stats.setSpreadsheetFile(csv_file) # execute the algorithm try: stats.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes output = False for st in statistics: if st == "Cluster_maps": output = True if (output): data = np.reshape(np.array(stats.getOutputImage(), dtype=np.int32), dimensions, 'F') header['cal_min'] = np.nanmin(data) header['cal_max'] = np.nanmax(data) output = nb.Nifti1Image(data, affine, header) if save_data: save_volume(map_file, output) csv_file = stats.getOutputFile() if output: if save_data: return {'csv': csv_file, 'map': map_file} else: return {'csv': csv_file, 'map': output} else: return {'csv': csv_file}
def stack_intensity_regularisation(image, cutoff=50, mask=None, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Stack intensity regularisation Estimates an image-to-image linear intensity scaling for a stack of 2D images Parameters ---------- image: niimg Input 2D images, stacked in the Z dimension cutoff: float, optional Range of image differences to keep (default is middle 50%) mask: niimg Input mask or probability image of the data to use (optional) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (niimg): The intensity regularised input Notes ---------- Original Java module by Pierre-Louis Bazin. """ print('\nStack Intensity Regularisation') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image) regularised_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image, suffix='sir-img')) if overwrite is False \ and os.path.isfile(regularised_file) : print("skip computation (use existing results)") output = {'result': regularised_file} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create instance sir = nighresjava.StackIntensityRegularisation() # set parameters # load image and use it to set dimensions and resolution img = load_volume(image) data = img.get_fdata() affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape sir.setDimensions(dimensions[0], dimensions[1], dimensions[2]) sir.setInputImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) if mask is not None: sir.setForegroundImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) # set algorithm parameters sir.setVariationRatio(float(cutoff)) # execute the algorithm try: sir.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes regularised_data = np.reshape( np.array(sir.getRegularisedImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(regularised_data) header['cal_max'] = np.nanmax(regularised_data) regularised = nb.Nifti1Image(regularised_data, affine, header) if save_data: save_volume(regularised_file, regularised) return {'result': regularised_file} else: return {'result': regularised}
def mp2rageme_pd_mapping(first_inversion, second_inversion, t1map, r2smap, echo_times, inversion_times, flip_angles, inversion_TR, excitation_TR, N_excitations, efficiency=0.96, b1map=None, s0img=None, save_data=False, overwrite=False, output_dir=None, file_name=None): """ MP2RAGEME PD mapping Estimate PD maps from MP2RAGEME data, combining T1 and R2* estimates with the MPRAGE model of _[1]. Parameters ---------- first_inversion: [niimg] List of {magnitude, phase} images for the first inversion second_inversion: [niimg] List of {magnitude, phase} images for the second inversion t1map: niimg Quantitative T1 map image, in milliseconds r2smap: niimg Quantitative R2* map image, in kHz echo_times: [float] List of {te1, te2, te3, te4, te5} echo times, in seconds inversion_times: [float] List of {first, second} inversion times, in seconds flip_angles: [float] List of {first, second} flip angles, in degrees inversion_TR: float Inversion repetition time, in seconds excitation_TR: [float] List of {first,second} repetition times,in seconds N_excitations: int Number of excitations efficiency: float Inversion efficiency (default is 0.96) correct_B1: bool Whether to correct for B1 inhomogeneities (default is False) b1map: niimg Computed B1 map (optional) s0map: niimg Computed S0 map (optional) scale_phase: bool Whether to rescale the phase image in [0,2PI] or to assume it is already in radians save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * pd1 (niimg): Map of estimated proton density from inv1 (_qpd-inv1) * pd2 (niimg): Map of estimated proton density from inv2 (_qpd-inv2) * pd (niimg): Map of estimated proton density average (_qpd-avg) Notes ---------- Original Java module by Pierre-Louis Bazin. References ---------- .. [1] Marques, Kober, Krueger, van der Zwaag, Van de Moortele, Gruetter (2010) MP2RAGE, a self bias-field corrected sequence for improved segmentation and T1-mapping at high field. doi: 10.1016/j.neuroimage.2009.10.002. """ print('\nPD Mapping') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, first_inversion[0]) pd1_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=first_inversion[0], suffix='qpd-inv1')) pd2_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=first_inversion[0], suffix='qpd-inv2')) pd_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=first_inversion[0], suffix='qpd-avg')) if overwrite is False \ and os.path.isfile(pd1_file) \ and os.path.isfile(pd2_file) \ and os.path.isfile(pd_file) : output = {'pd1': pd1_file, 'pd2': pd2_file, 'pd': pd_file} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create algorithm instance qpdmap = nighresjava.IntensityMp2ragemePDmapping() # set algorithm parameters qpdmap.setFirstEchoTime(echo_times[0]) qpdmap.setFirstInversionTime(inversion_times[0]) qpdmap.setSecondInversionTime(inversion_times[1]) qpdmap.setFirstFlipAngle(flip_angles[0]) qpdmap.setSecondFlipAngle(flip_angles[1]) qpdmap.setInversionRepetitionTime(inversion_TR) qpdmap.setFirstExcitationRepetitionTime(excitation_TR[0]) qpdmap.setSecondExcitationRepetitionTime(excitation_TR[1]) qpdmap.setNumberExcitations(N_excitations) qpdmap.setInversionEfficiency(efficiency) qpdmap.setCorrectB1inhomogeneities(b1map != None) # load first image and use it to set dimensions and resolution img = load_volume(first_inversion[0]) data = img.get_data() #data = data[0:10,0:10,0:10] affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape qpdmap.setDimensions(dimensions[0], dimensions[1], dimensions[2]) qpdmap.setResolutions(resolution[0], resolution[1], resolution[2]) # input images qpdmap.setFirstInversionMagnitude( nighresjava.JArray('float')((data.flatten('F')).astype(float))) data = load_volume(first_inversion[1]).get_data() qpdmap.setFirstInversionPhase( nighresjava.JArray('float')((data.flatten('F')).astype(float))) data = load_volume(second_inversion[0]).get_data() qpdmap.setSecondInversionMagnitude( nighresjava.JArray('float')((data.flatten('F')).astype(float))) data = load_volume(second_inversion[1]).get_data() qpdmap.setSecondInversionPhase( nighresjava.JArray('float')((data.flatten('F')).astype(float))) data = load_volume(t1map).get_data() qpdmap.setT1mapImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) data = load_volume(r2smap).get_data() qpdmap.setR2smapImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) if (s0img != None): data = load_volume(s0img).get_data() qpdmap.setS0Image( nighresjava.JArray('float')((data.flatten('F')).astype(float))) if (b1map != None): data = load_volume(b1map).get_data() qpdmap.setB1mapImage( nighresjava.JArray('float')((data.flatten('F')).astype(float))) # execute the algorithm try: qpdmap.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes pd1_data = np.reshape( np.array(qpdmap.getProtonDensityImage1(), dtype=np.float32), dimensions, 'F') pd2_data = np.reshape( np.array(qpdmap.getProtonDensityImage2(), dtype=np.float32), dimensions, 'F') pd_data = np.reshape( np.array(qpdmap.getProtonDensityImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(pd1_data) header['cal_max'] = np.nanmax(pd1_data) pd1 = nb.Nifti1Image(pd1_data, affine, header) header['cal_min'] = np.nanmin(pd2_data) header['cal_max'] = np.nanmax(pd2_data) pd2 = nb.Nifti1Image(pd2_data, affine, header) header['cal_min'] = np.nanmin(pd_data) header['cal_max'] = np.nanmax(pd_data) pd = nb.Nifti1Image(pd_data, affine, header) if save_data: save_volume(pd1_file, pd1) save_volume(pd2_file, pd2) save_volume(pd_file, pd) return {'pd1': pd1_file, 'pd2': pd2_file, 'pd': pd_file} else: return {'pd1': pd1, 'pd2': pd2, 'pd': pd}
def total_variation_filtering(image, mask=None, lambda_scale=0.05, tau_step=0.125,max_dist=1e-4,max_iter=500, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Total Variation Filtering Total variation filtering. Parameters ---------- image: niimg Input image to filter mask: niimg, optional Data mask for processing lambda_scale: float, optional Relative intensity scale for total variation smoothing (default is 0.5) tau_step: float, optional Internal step parameter (default is 0.125) max_dist: float, optional Maximum distance for convergence (default is 1e-4) max_iter: int, optional Maximum number of iterations (default is 500) save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * filtered (niimg): The filtered image (_tv-img) * residual (niimg): The image residuals (_tv-res) Notes ---------- Original Java module by Pierre-Louis Bazin. Algorithm adapted from [1]_ References ---------- .. [1] Chambolle (2004). An Algorithm for Total Variation Minimization and Applications. doi:10.1023/B:JMIV.0000011325.36760.1e """ print('\nTotal variation filtering') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image) out_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=image, suffix='tv-img')) res_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=image, suffix='tv-res')) if overwrite is False \ and os.path.isfile(out_file) and os.path.isfile(res_file) : print("skip computation (use existing results)") output = {'filtered': out_file, 'residual': res_file} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create instance algo = nighresjava.TotalVariationFiltering() # set parameters # load image and use it to set dimensions and resolution img = load_volume(image) data = img.get_data() affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape algo.setDimensions(dimensions[0], dimensions[1], dimensions[2]) algo.setResolutions(resolution[0], resolution[1], resolution[2]) algo.setImage(nighresjava.JArray('float')( (data.flatten('F')).astype(float))) if mask is not None: algo.setMaskImage(idx, nighresjava.JArray('int')( (load_volume(mask).get_data().flatten('F')).astype(int).tolist())) # set algorithm parameters algo.setLambdaScale(lambda_scale) algo.setTauStep(tau_step) algo.setMaxDist(max_dist) algo.setMaxIter(max_iter) # execute the algorithm try: algo.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes filtered_data = np.reshape(np.array(algo.getFilteredImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(filtered_data) header['cal_max'] = np.nanmax(filtered_data) out = nb.Nifti1Image(filtered_data, affine, header) # reshape output to what nibabel likes residual_data = np.reshape(np.array(algo.getResidualImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(residual_data) header['cal_max'] = np.nanmax(residual_data) res = nb.Nifti1Image(residual_data, affine, header) if save_data: save_volume(out_file, out) save_volume(res_file, res) return {'filtered': out_file, 'residual': res_file} else: return {'filtered': out, 'residual': res}
def flash_t2s_fitting(image_list, te_list, save_data=False, overwrite=False, output_dir=None, file_name=None): """ FLASH T2* fitting Estimate T2*/R2* by linear least squares fitting in log space. Parameters ---------- image_list: [niimg] List of input images to fit the T2* curve te_list: [float] List of input echo times (TE) save_data: bool, optional Save output data to file (default is False) overwrite: bool, optional Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * t2s (niimg): Map of estimated T2* times (_qt2fit-t2s) * r2s (niimg): Map of estimated R2* relaxation rate (_qt2fit-r2s) * s0 (niimg): Estimated PD weighted image at TE=0 (_qt2fit-s0) * residuals (niimg): Estimated residuals between input and estimated echoes (_qt2fit-err) Notes ---------- Original Java module by Pierre-Louis Bazin. """ print('\nT2* Fitting') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, image_list[0]) t2s_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image_list[0], suffix='qt2fit-t2s')) r2s_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image_list[0], suffix='qt2fit-r2s')) s0_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image_list[0], suffix='qt2fit-s0')) err_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=image_list[0], suffix='qt2fit-err')) if overwrite is False \ and os.path.isfile(t2s_file) \ and os.path.isfile(r2s_file) \ and os.path.isfile(s0_file) \ and os.path.isfile(err_file) : output = { 't2s': t2s_file, 'r2s': r2s_file, 's0': s0_file, 'residuals': err_file } return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create algorithm instance qt2fit = nighresjava.IntensityFlashT2sFitting() # set algorithm parameters qt2fit.setNumberOfEchoes(len(image_list)) # load first image and use it to set dimensions and resolution img = load_volume(image_list[0]) data = img.get_data() #data = data[0:10,0:10,0:10] affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape qt2fit.setDimensions(dimensions[0], dimensions[1], dimensions[2]) qt2fit.setResolutions(resolution[0], resolution[1], resolution[2]) # input images # important: set image number before adding images for idx, image in enumerate(image_list): #print('\nloading ('+str(idx)+'): '+image) data = load_volume(image).get_data() #data = data[0:10,0:10,0:10] qt2fit.setEchoImageAt( idx, nighresjava.JArray('float')((data.flatten('F')).astype(float))) qt2fit.setEchoTimeAt(idx, te_list[idx]) # execute the algorithm try: qt2fit.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes t2s_data = np.reshape(np.array(qt2fit.getT2sImage(), dtype=np.float32), dimensions, 'F') r2s_data = np.reshape(np.array(qt2fit.getR2sImage(), dtype=np.float32), dimensions, 'F') s0_data = np.reshape(np.array(qt2fit.getS0Image(), dtype=np.float32), dimensions, 'F') err_data = np.reshape( np.array(qt2fit.getResidualImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(t2s_data) header['cal_max'] = np.nanmax(t2s_data) t2s = nb.Nifti1Image(t2s_data, affine, header) header['cal_min'] = np.nanmin(r2s_data) header['cal_max'] = np.nanmax(r2s_data) r2s = nb.Nifti1Image(r2s_data, affine, header) header['cal_min'] = np.nanmin(s0_data) header['cal_max'] = np.nanmax(s0_data) s0 = nb.Nifti1Image(s0_data, affine, header) header['cal_min'] = np.nanmin(err_data) header['cal_max'] = np.nanmax(err_data) err = nb.Nifti1Image(err_data, affine, header) if save_data: save_volume(t2s_file, t2s) save_volume(r2s_file, r2s) save_volume(s0_file, s0) save_volume(err_file, err) return { 't2s': t2s_file, 'r2s': r2s_file, 's0': s0_file, 'residuals': err_file } else: return {'t2s': t2s, 'r2s': r2s, 's0': s0, 'residuals': err}
def Simple_Skeleton(input_image, shape_image_type = 'signed_distance', boundary_threshold = 0.0, skeleton_threshold = 2.0, Topology_LUT_directory = None, save_data=False, overwrite=False, output_dir=None, file_name=None): """ Simple Skeleton Create a skeleton for a levelset surface or a probability map (loosely adapted from Bouix et al., 2006) Parameters ---------- input_image: niimg Image containing structure-of-interest shape_image_type: str shape of the input image: either 'signed_distance' or 'probability_map'. boundary_threshold: float Boundary threshold (>0: inside, <0: outside) skeleton_threshold: float Skeleton threshold (>0: inside, <0: outside) Topology_LUT_directory:str Directory of LUT topology save_data: bool, optional Save output data to file (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- Medial_Surface_Image Medial_Curve_Image Notes ---------- Original Java module by Pierre-Louis Bazin. """ if save_data: output_dir = _output_dir_4saving(output_dir, input_image) MedialSurface_file = _fname_4saving(file_name=file_name, rootfile=input_image, suffix='medial', ) Medial_Curve_file = _fname_4saving(file_name=file_name, rootfile=input_image, suffix='skel') if overwrite is False \ and os.path.isfile(MedialSurface_file) \ and os.path.isfile(Medial_Curve_file) : output = {'medial': load_volume(MedialSurface_file), 'skel': load_volume(Medial_Curve_file)} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create algorithm instance skeleton = nighresjava.ShapeSimpleSkeleton() # set parameters skeleton.setBoundaryThreshold(boundary_threshold) skeleton.setSkeletonThreshold(skeleton_threshold) skeleton.setTopologyLUTdirectory(Topology_LUT_directory) skeleton.setShapeImageType(shape_image_type) # load images and set dimensions and resolution input_image = load_volume(input_image) data = input_image.get_data() affine = input_image.get_affine() header = input_image.get_header() resolution = [x.item() for x in header.get_zooms()] dimensions = input_image.shape skeleton.setDimensions(dimensions[0], dimensions[1], dimensions[2]) skeleton.setResolutions(resolution[0], resolution[1], resolution[2]) data = load_volume(input_image).get_data() skeleton.setShapeImage(nighresjava.JArray('float')( (data.flatten('F')).astype(float))) # execute try: skeleton.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # Collect output medialImage_data = np.reshape(np.array( skeleton.getMedialSurfaceImage(), dtype=np.int8), dimensions, 'F') skelImage_data = np.reshape(np.array( skeleton.getMedialCurveImage(), dtype=np.int8), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects # d_head['data_type'] = np.array(8).astype('int8') #convert the header as well header['cal_min'] = np.nanmin(medialImage_data) header['cal_max'] = np.nanmax(medialImage_data) medialImage = nb.Nifti1Image(medialImage_data, affine, header) header['cal_min'] = np.nanmin(skelImage_data) header['cal_max'] = np.nanmax(skelImage_data) skelImage = nb.Nifti1Image(skelImage_data, affine, header) if save_data: save_volume(os.path.join(output_dir, MedialSurface_file), medialImage) save_volume(os.path.join(output_dir, Medial_Curve_file), skelImage) return {'medialImage': medialImage, 'skelImage': skelImage}
def probability_to_levelset(probability_image, mask_image=None, save_data=False, overwrite=False, output_dir=None, file_name=None): """Levelset from probability map Creates a levelset surface representations from a probabilistic map or a mask. The levelset indicates each voxel's distance to the closest boundary. It takes negative values inside and positive values outside of the object. Parameters ---------- probability_image: niimg Probability image to be turned into levelset. Values should be in [0, 1], either a binary mask or defining the boundary at 0.5. mask_image: niimg, optional Mask image defining the region in which to compute the levelset. Values equal to zero are set to maximum distance. save_data: bool, optional Save output data to file (default is False) overwrite: bool, optional Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * result (niimg): Levelset representation of surface (_p2l-surf) Notes ---------- Original Java module by Pierre-Louis Bazin """ print("\nProbability to Levelset") # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, probability_image) levelset_file = os.path.join( output_dir, _fname_4saving(module=__name__, file_name=file_name, rootfile=probability_image, suffix='p2l-surf')) if overwrite is False \ and os.path.isfile(levelset_file) : print("skip computation (use existing results)") output = {'result': levelset_file} return output # start virtual machine if not running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # initiate class prob2level = nighresjava.SurfaceProbabilityToLevelset() # load the data prob_img = load_volume(probability_image) prob_data = prob_img.get_fdata() hdr = prob_img.header aff = prob_img.affine resolution = [x.item() for x in hdr.get_zooms()] dimensions = prob_data.shape # set parameters from input data prob2level.setProbabilityImage( nighresjava.JArray('float')((prob_data.flatten('F')).astype(float))) if (mask_image is not None): mask_data = load_volume(mask_image).get_fdata() prob2level.setMaskImage( nighresjava.JArray('int')( (mask_data.flatten('F')).astype(int).tolist())) if len(dimensions) > 2: prob2level.setResolutions(resolution[0], resolution[1], resolution[2]) prob2level.setDimensions(dimensions[0], dimensions[1], dimensions[2]) else: prob2level.setResolutions(resolution[0], resolution[1], 1.0) prob2level.setDimensions(dimensions[0], dimensions[1], 1) # execute class try: prob2level.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # collect outputs levelset_data = np.reshape( np.array(prob2level.getLevelSetImage(), dtype=np.float32), dimensions, 'F') hdr['cal_max'] = np.nanmax(levelset_data) levelset = nb.Nifti1Image(levelset_data, aff, hdr) if save_data: save_volume(levelset_file, levelset) return {'result': levelset_file} else: return {'result': levelset}
def mp2rage_t1_from_uni(uniform_image, inversion_times, flip_angles, inversion_TR, excitation_TR, N_excitations, efficiency=0.96, correct_B1=False, B1_map=None, B1_scale=1.0, scale_phase=True, save_data=False, overwrite=False, output_dir=None, file_name=None): """ MP2RAGE uniform image to T1 mapping Estimate T1/R1 by a look-up table method adapted from [1]_ Parameters ---------- uniform_image: niimg Uniform image computed from first and second inversion inversion_times: [float] List of {first, second} inversion times, in seconds flip_angles: [float] List of {first, second} flip angles, in degrees inversion_TR: float Inversion repetition time, in seconds excitation_TR: [float] List of {first,second} repetition times,in seconds N_excitations: int Number of excitations efficiency: float Inversion efficiency (default is 0.96) correct_B1: bool Whether to correct for B1 inhomogeneities (default is False) B1_map: niimg Computed B1 map B1_scale: float B1 map scaling factor (default is 1.0) scale_phase: bool Whether to rescale the phase image in [0,2PI] or to assume it is already in radians save_data: bool Save output data to file (default is False) overwrite: bool Overwrite existing results (default is False) output_dir: str, optional Path to desired output directory, will be created if it doesn't exist file_name: str, optional Desired base name for output files with file extension (suffixes will be added) Returns ---------- dict Dictionary collecting outputs under the following keys (suffix of output files in brackets) * t1 (niimg): Map of estimated T1 times in seconds (_qt1map-t1) * r1 (niimg): Map of estimated R1 relaxation rate in hertz (_qt1map-r1) Notes ---------- Original Java module by Pierre-Louis Bazin. References ---------- .. [1] Marques, Kober, Krueger, van der Zwaag, Van de Moortele, Gruetter (2010) MP2RAGE, a self bias-field corrected sequence for improved segmentation and T1-mapping at high field. doi: 10.1016/j.neuroimage.2009.10.002. """ print('\nT1 Mapping') # make sure that saving related parameters are correct if save_data: output_dir = _output_dir_4saving(output_dir, uniform_image) t1_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=uniform_image, suffix='qt1map-t1')) r1_file = os.path.join(output_dir, _fname_4saving(module=__name__,file_name=file_name, rootfile=uniform_image, suffix='qt1map-r1')) if overwrite is False \ and os.path.isfile(t1_file) \ and os.path.isfile(r1_file) : output = {'t1': t1_file, 'r1': r1_file} return output # start virtual machine, if not already running try: mem = _check_available_memory() nighresjava.initVM(initialheap=mem['init'], maxheap=mem['max']) except ValueError: pass # create algorithm instance qt1map = nighresjava.IntensityMp2rageT1Fitting() # set algorithm parameters qt1map.setFirstInversionTime(inversion_times[0]) qt1map.setSecondInversionTime(inversion_times[1]) qt1map.setFirstFlipAngle(flip_angles[0]) qt1map.setSecondFlipAngle(flip_angles[1]) qt1map.setInversionRepetitionTime(inversion_TR) qt1map.setFirstExcitationRepetitionTime(excitation_TR[0]) qt1map.setSecondExcitationRepetitionTime(excitation_TR[1]) qt1map.setNumberExcitations(N_excitations) qt1map.setInversionEfficiency(efficiency) qt1map.setCorrectB1inhomogeneities(correct_B1) # load first image and use it to set dimensions and resolution img = load_volume(uniform_image) data = img.get_fdata() #data = data[0:10,0:10,0:10] affine = img.affine header = img.header resolution = [x.item() for x in header.get_zooms()] dimensions = data.shape qt1map.setDimensions(dimensions[0], dimensions[1], dimensions[2]) qt1map.setResolutions(resolution[0], resolution[1], resolution[2]) # input images qt1map.setUniformImage(nighresjava.JArray('float')( (data.flatten('F')).astype(float))) if (correct_B1): data = load_volume(B1_map).get_fdata() qt1map.setB1mapImage(nighresjava.JArray('float')( (data.flatten('F')).astype(float))) qt1map.setB1mapScaling(B1_scale) # execute the algorithm try: qt1map.execute() except: # if the Java module fails, reraise the error it throws print("\n The underlying Java code did not execute cleanly: ") print(sys.exc_info()[0]) raise return # reshape output to what nibabel likes t1_data = np.reshape(np.array(qt1map.getQuantitativeT1mapImage(), dtype=np.float32), dimensions, 'F') r1_data = np.reshape(np.array(qt1map.getQuantitativeR1mapImage(), dtype=np.float32), dimensions, 'F') # adapt header max for each image so that correct max is displayed # and create nifiti objects header['cal_min'] = np.nanmin(t1_data) header['cal_max'] = np.nanmax(t1_data) t1 = nb.Nifti1Image(t1_data, affine, header) header['cal_min'] = np.nanmin(r1_data) header['cal_max'] = np.nanmax(r1_data) r1 = nb.Nifti1Image(r1_data, affine, header) if save_data: save_volume(t1_file, t1) save_volume(r1_file, r1) return {'t1': t1_file, 'r1': r1_file} else: return {'t1': t1, 'r1': r1}