def contour_slices( bg_image, file_template, auto_figsize=False, invert=False, alpha=[0.9], colors=['r', 'g', 'b'], dimming=0., figure_title='', force_reverse_slice_order=True, legend_template='', levels_percentile=[80], linewidths=(), ratio='portrait', save_as='', scale=0.4, slice_spacing=0.5, substitutions=[ {}, ], style='light', title_color='#BBBBBB', ): """ Plot coronal `bg_image` slices at a given spacing, and overlay contours from a list of NIfTI files. Parameters ---------- bg_image : str Path to the NIfTI image to draw in grayscale as th eplot background. This would commonly be some sort of brain template. file_template : str String template giving the path to the overlay stack. To create multiple overlays, this template will iteratively be substituted with each of the substitution dictionaries in the `substitutions` parameter. auto_figsize : boolean, optional Whether to automatically determine the size of the figure. invert : boolean, optional Whether to automatically invert data matrix values (useful if the image consists of negative values, e.g. when dealing with negative contrast agent CBV scans). alpha : list, optional List of floats, specifying with how much alpha to draw each contour. colors : list, optional List of colors in which to plot the overlays. dimming : float, optional Dimming factor, generally between -2 and 2 (-2 increases contrast, 2 decreases it). This parameter is passed directly to `nilearn.plotting.plot_anat()` Set to 'auto', to use nilearn automagick dimming. figure_title : str, optional Title for the figure. force_reverse_slice_order : bool, optional Whether to force the reversal of the slice order. This can be done to enforce a visual presentation without having to modify the underlying data (i.e. visualize neurological-order slices in radiological order). This option should generally be avoided, ideally one would not obfuscate the data orientation when plotting. legend_template : string, optional String template which can be formatted with the dictionaries contained in the `substitutions` parameter. The resulting strings will give the legend text. levels_percentile : list, optional List of integers, specifying at which percentiles of each overlay to draw contours. line_widths : tuple, optional Tuple of desired contour line widths (one per substitution). ratio : list or {'landscape', 'portrait'}, optional Either a list of 2 integers giving the desired number of rows and columns (in this order), or a string, which is either 'landscape' or 'portrait', and which prompts the function to auto-determine the best number of rows and columns given the number of slices and the `scale` attribute. save_as : str, optional Path under which to save the output figure. The string may contain formatting fields from the first dictionary in the `substitutions` variable. scale : float, optional The expected ratio of the slice height divided by the sum of the slice height and width. This somewhat complex metric controls the row and column distribution of slices in the 'landscape' and 'portrait' plotting shapes. slice_spacing : float Slice spacing in mm. substitutions : list of dicts, optional A list of dictionaries, with keys including all substitution keys found in the `file_template` parameter, and values giving desired substitution values which point the `file_template` string templated to existing filed which are to be included in the overlay stack. Such a dictionary is best obtained via `samri.utilities.bids_substitution_iterator()`. style : {'light', 'dark', ''}, optional Default SAMRI styling which to apply, set to an empty string to apply no styling and leave it to the environment matplotlibrc. title_color : string, optional String specifying the desired color for the title. This needs to be specified in-function, because the matplotlibrc styling standard does not provide for title color specification [matplotlibrc_title] References ---------- .. [matplotlibrc_title] https://stackoverflow.com/questions/30109465/matplotlib-set-title-color-in-stylesheet """ if len(substitutions) == 0: print( 'ERROR: You have specified a substitution dictionary of length 0. There needs to be at least one set of substitutions. If your string contains no formatting fields, please pass a list containing an empty dictionary to the `sbstitution parameter` (this is also its default value).' ) plotting_module_path = path.dirname(path.realpath(__file__)) if style == 'light': black_bg = False anatomical_cmap = 'binary' style_path = path.join(plotting_module_path, 'contour_slices.conf') plt.style.use([style_path]) elif style == 'dark': black_bg = True anatomical_cmap = 'binary_r' style_path = path.join(plotting_module_path, 'contour_slices_dark.conf') plt.style.use([style_path]) else: anatomical_cmap = 'binary' black_bg = False bg_image = path.abspath(path.expanduser(bg_image)) bg_img = nib.load(bg_image) if bg_img.header['dim'][0] > 3: bg_data = bg_img.get_data() ndim = 0 for i in range(len(bg_img.header['dim']) - 1): current_dim = bg_img.header['dim'][i + 1] if current_dim == 1: break ndim += 1 bg_img.header['dim'][0] = ndim bg_img.header['pixdim'][ndim + 1:] = 0 bg_data = bg_data.T[0].T bg_img = nib.nifti1.Nifti1Image(bg_data, bg_img.affine, bg_img.header) imgs = [] bounds = [] levels = [] slice_order_is_reversed = 0 for substitution in substitutions: filename = file_template.format(**substitution) filename = path.abspath(path.expanduser(filename)) img = nib.load(filename) data = img.get_data() if img.header['dim'][0] > 3: img = collapse(img) if invert: data = -data img = nib.nifti1.Nifti1Image(data, img.affine, img.header) #we should only be looking at the percentile of the entire data matrix, rather than just the active slice for level_percentile in levels_percentile: level = np.percentile(data, level_percentile) levels.append(level) slice_row = img.affine[1] subthreshold_start_slices = 0 while True: for i in np.arange(data.shape[1]): my_slice = data[:, i, :] if my_slice.max() < min(levels): subthreshold_start_slices += 1 else: break break subthreshold_end_slices = 0 while True: for i in np.arange(data.shape[1])[::-1]: my_slice = data[:, i, :] if my_slice.max() < min(levels): subthreshold_end_slices += 1 else: break break slice_thickness = (slice_row[0]**2 + slice_row[1]**2 + slice_row[2]**2)**(1 / 2) best_guess_negative = abs(min(slice_row[0:3])) > abs( max(slice_row[0:3])) slices_number = data.shape[list(slice_row).index(max(slice_row))] img_min_slice = slice_row[ 3] + subthreshold_start_slices * slice_thickness img_max_slice = slice_row[3] + ( slices_number - subthreshold_end_slices) * slice_thickness bounds.extend([img_min_slice, img_max_slice]) if best_guess_negative: slice_order_is_reversed += 1 else: slice_order_is_reversed -= 1 imgs.append(img) if len(alpha) == 1: alpha = alpha * len(imgs) min_slice = min(bounds) max_slice = max(bounds) cut_coords = np.arange(min_slice, max_slice, slice_spacing) if slice_order_is_reversed > 0: cut_coords = cut_coords[::-1] if force_reverse_slice_order: cut_coords = cut_coords[::-1] if not linewidths: linewidths = (rcParams['axes.linewidth'], ) * len(imgs) if len(cut_coords) > 3: cut_coord_length = len(cut_coords) if legend_template: cut_coord_length += 1 try: nrows, ncols = ratio except ValueError: if ratio == "portrait": ncols = np.floor(cut_coord_length**scale) nrows = np.ceil(cut_coord_length / float(ncols)) elif ratio == "landscape": nrows = np.floor(cut_coord_length**(scale)) ncols = np.ceil(cut_coord_length / float(nrows)) # we adjust the respective rc.Param here, because it needs to be set before drawing to take effect if legend_template and cut_coord_length == ncols * (nrows - 1) + 1: rcParams['figure.subplot.bottom'] = np.max( [rcParams['figure.subplot.bottom'] - 0.05, 0.]) if auto_figsize: figsize = np.array(rcParams['figure.figsize']) figsize_scales = figsize / np.array([float(ncols), float(nrows)]) figsize_scale = figsize_scales.min() fig, ax = plt.subplots( figsize=(ncols * figsize_scale, nrows * figsize_scale), nrows=int(nrows), ncols=int(ncols), ) else: fig, ax = plt.subplots( nrows=int(nrows), ncols=int(ncols), ) flat_axes = list(ax.flatten()) for ix, ax_i in enumerate(flat_axes): try: display = nilearn.plotting.plot_anat( bg_img, axes=ax_i, display_mode='y', cut_coords=[cut_coords[ix]], annotate=False, black_bg=black_bg, dim=dimming, cmap=anatomical_cmap, ) except IndexError: ax_i.axis('off') else: for img_ix, img in enumerate(imgs): color = colors[img_ix] display.add_contours( img, alpha=alpha[img_ix], colors=[color], levels=levels[img_ix], linewidths=(linewidths[img_ix], ), ) if legend_template: for ix, img in enumerate(imgs): insertion_legend, = plt.plot( [], [], color=colors[ix], label=legend_template.format(**substitutions[ix])) if cut_coord_length == ncols * (nrows - 1) + 1: plt.legend(loc='upper left', bbox_to_anchor=(-0.1, -0.3)) else: plt.legend(loc='lower left', bbox_to_anchor=(1.1, 0.)) else: display = nilearn.plotting.plot_anat( bg_img, display_mode='y', cut_coords=cut_coords, black_bg=black_bg, ) for ix, img in enumerate(imgs): color = colors[ix] display.add_contours(img, levels=levels, colors=[color]) if figure_title: fig.suptitle(figure_title, color=title_color) if save_as: save_as = save_as.format(**substitutions[0]) save_as = path.abspath(path.expanduser(save_as)) save_dir, _ = os.path.split(save_as) if not os.path.exists(save_dir): os.makedirs(save_dir) plt.savefig(save_as, #facecolor=fig.get_facecolor(), ) plt.close()
def threshold_volume( in_file, substitution={}, masker='', threshold=45, threshold_is_percentile=False, inverted_data=False, ): """Return the volume which lies above a given threshold in a NIfTI (implicitly, in the volume units of the respective NIfTI). Parameters ---------- in_file : str Path to a NIfTI file. 4D files will be collapsed to 3D using the mean function. masker : str or nilearn.NiftiMasker, optional Path to a NIfTI file containing a mask (1 and 0 values) or a `nilearn.NiftiMasker` object. NOT YET SUPPORTED! threshold : float, optional A float giving the voxel value threshold. threshold_is_percentile : bool, optional Whether `threshold` is to be interpreted not literally, but as a percentile of the data matrix. This is useful for making sure that the volume estimation is not susceptible to the absolute value range, but only the value distribution. inverted_data : bool, optional Whether data is inverted (flipped with respect to 0). If `True`, the inverse of the threshold is automatically computed. Returns ------- float The volume (in volume units of the respective NIfTI) containing values above the given threshold. """ if substitution: in_file = in_file.format(**substitution) in_file = path.abspath(path.expanduser(in_file)) try: img = nib.load(in_file) except FileNotFoundError: return float('NaN') if img.header['dim'][0] > 4: raise ValueError( "Files with more than 4 dimensions are not currently supported.") elif img.header['dim'][0] > 3: img = collapse(img) data = img.get_data() x_len = (img.affine[0][0]**2 + img.affine[0][1]**2 + img.affine[0][2]**2)**(1 / 2.) y_len = (img.affine[1][0]**2 + img.affine[1][1]**2 + img.affine[1][2]**2)**(1 / 2.) z_len = (img.affine[2][0]**2 + img.affine[2][1]**2 + img.affine[2][2]**2)**(1 / 2.) voxel_volume = x_len * y_len * z_len if inverted_data and not threshold_is_percentile: threshold_voxels = (data < threshold).sum() else: if inverted_data: threshold = 100 - threshold if threshold_is_percentile: threshold = np.percentile(data, threshold) threshold_voxels = (data > threshold).sum() threshold_volume = voxel_volume * threshold_voxels return threshold_volume
def slices(heatmap_image, bg_image='/usr/share/mouse-brain-atlases/dsurqec_40micron_masked.nii', contour_image='', heatmap_threshold=3, heatmap_alpha=1.0, contour_threshold=3, auto_figsize=False, invert=False, contour_alpha=0.9, contour_color='g', cmap='autumn_r', dimming=0., figure_title='', force_reverse_slice_order=True, legend=False, aspect='portrait', save_as='', ratio=3/4., slice_spacing=0.4, style='light', title_color='#BBBBBB', position_vspace=0.0, positive_only=False, negative_only=False, skip_start=0, skip_end=0, ): """ Plot coronal `bg_image` slices at a given spacing, and overlay contours from a list of NIfTI files. Parameters ---------- heatmap_image : str Path to an overlay image to be printed as a heatmap. bg_image : str, optional Path to the NIfTI image to draw in grayscale as th eplot background. This would commonly be some sort of brain template. contour_image : str, optional Path to an overlay image to be printed as a contour. heatmap_threshold : float, optional Value at which to threshold the heatmap_image. heatmap_alpha : float, optional Alpha (opacity, from 0.0 to 1.0) with which to draw the contour image. contour_threshold : float, optional Value at which to threshold the contour_image. auto_figsize : boolean, optional Whether to automatically determine the size of the figure. invert : boolean, optional Whether to automatically invert data matrix values (useful if the image consists of negative values, e.g. when dealing with negative contrast agent CBV scans). contour_alpha : float, optional Alpha (opacity, from 0.0 to 1.0) with which to draw the contour image. contour_color : str, optional Color with which to draw the contour image. cmap : str, optional Colormap with which to draw the heatmap image. dimming : float, optional Dimming factor, generally between -2 and 2 (-2 increases contrast, 2 decreases it). This parameter is passed directly to `nilearn.plotting.plot_anat()` Set to 'auto', to use nilearn automagick dimming. figure_title : str, optional Title for the figure. force_reverse_slice_order : bool, optional Whether to force the reversal of the slice order. This can be done to enforce a visual presentation without having to modify the underlying data (i.e. visualize neurological-order slices in radiological order). This option should generally be avoided, ideally one would not obfuscate the data orientation when plotting. legend : string, optional The legend text. aspect : list or {'landscape', 'portrait'}, optional Either a list of 2 integers giving the desired number of rows and columns (in this order), or a string, which is either 'landscape' or 'portrait', and which prompts the function to auto-determine the best number of rows and columns given the number of slices and the `scale` attribute. save_as : str, optional Path under which to save the output figure. ratio : float, optional The desired ratio between the number of columns and the number of rows in the desired figure layout. slice_spacing : float Slice spacing in mm. style : {'light', 'dark', ''}, optional Default SAMRI styling which to apply, set to an empty string to apply no styling and leave it to the environment matplotlibrc. title_color : string, optional String specifying the desired color for the title. This needs to be specified in-function, because the matplotlibrc styling standard does not provide for title color specification [matplotlibrc_title] position_vspace : float, optional Vertical distance adjustment between slice and coordinate text annotation. skip_start : int, optional Number of slices (at the slice spacing given by `slice_spacing`) to skip at the start of the listing. skip_end : int, optional Number of slices (at the slice spacing given by `slice_spacing`) to skip at the end of the listing. References ---------- .. [matplotlibrc_title] https://stackoverflow.com/questions/30109465/matplotlib-set-title-color-in-stylesheet """ plotting_module_path = path.dirname(path.realpath(__file__)) if style=='light': black_bg=False anatomical_cmap = 'binary' style_path = path.join(plotting_module_path,'contour_slices.conf') plt.style.use([style_path]) elif style=='dark': black_bg=True anatomical_cmap = 'binary_r' style_path = path.join(plotting_module_path,'contour_slices_dark.conf') plt.style.use([style_path]) else: anatomical_cmap = 'binary' black_bg=False bg_image = path.abspath(path.expanduser(bg_image)) bg_img = nib.load(bg_image) if bg_img.header['dim'][0] > 3: bg_img = collapse(bg_img) slice_order_is_reversed = 0 heatmap_image = path.abspath(path.expanduser(heatmap_image)) heatmap_img = nib.load(heatmap_image) heatmap_data = heatmap_img.get_data() if heatmap_img.header['dim'][0] > 3: img = collapse(heatmap_img) if contour_image: contour_image = path.abspath(path.expanduser(contour_image)) contour_img = nib.load(contour_image) if contour_img.header['dim'][0] > 3: contour_img = collapse(contour_img) # We apply thresholding here, rather than when drawing the contours, to ensure the same contour color in all slices. # This is possibly a bug in nilearn. contour_img = from_img_threshold(contour_img, contour_threshold) #we should only be looking at the percentile of the entire data matrix, rather than just the active slice slice_row = heatmap_img.affine[1] subthreshold_start_slices = 0 while True: for i in np.arange(heatmap_data.shape[1]): my_slice = heatmap_data[:,i,:] if math.isnan(my_slice.max()) or my_slice.max() < heatmap_threshold: subthreshold_start_slices += 1 else: break break subthreshold_end_slices = 0 while True: for i in np.arange(heatmap_data.shape[1])[::-1]: my_slice = heatmap_data[:,i,:] if math.isnan(my_slice.max()) or my_slice.max() < heatmap_threshold: subthreshold_end_slices += 1 else: break break slice_thickness = (slice_row[0]**2+slice_row[1]**2+slice_row[2]**2)**(1/2) best_guess_negative = abs(min(slice_row[0:3])) > abs(max(slice_row[0:3])) slices_number = heatmap_data.shape[list(slice_row).index(max(slice_row))] skip_start = skip_start*slice_spacing/slice_thickness skip_end = skip_end*slice_spacing/slice_thickness img_min_slice = slice_row[3] + (subthreshold_start_slices+skip_start)*slice_thickness img_max_slice = slice_row[3] + (slices_number-subthreshold_end_slices-skip_end)*slice_thickness bounds = [img_min_slice,img_max_slice] if best_guess_negative: slice_order_is_reversed += 1 else: slice_order_is_reversed -= 1 min_slice = min(bounds) max_slice = max(bounds) cut_coords = np.arange(min_slice, max_slice, slice_spacing) if slice_order_is_reversed > 0: cut_coords = cut_coords[::-1] if force_reverse_slice_order: cut_coords = cut_coords[::-1] linewidth = rcParams['lines.linewidth'] cut_coord_length = len(cut_coords) if legend: cut_coord_length += 1 try: nrows, ncols = aspect except ValueError: if aspect == "portrait": ncols = np.ceil((cut_coord_length*ratio)**(1/2)) nrows = np.ceil(cut_coord_length/ncols) elif aspect == "landscape": nrows = np.ceil((cut_coord_length*ratio)**(1/2)) ncols = np.ceil(cut_coord_length/nrows) # we adjust the respective rc.Param here, because it needs to be set before drawing to take effect if legend and cut_coord_length == ncols*(nrows-1)+1: rcParams['figure.subplot.bottom'] = np.max([rcParams['figure.subplot.bottom']-0.05,0.]) if auto_figsize: figsize = np.array(rcParams['figure.figsize']) figsize_scales = figsize/np.array([float(ncols),float(nrows)]) figsize_scale = figsize_scales.min() fig, ax = plt.subplots(figsize=(ncols*figsize_scale,nrows*figsize_scale), nrows=int(nrows), ncols=int(ncols), ) else: figsize = np.array(rcParams['figure.figsize']) fig, ax = plt.subplots( nrows=int(nrows), ncols=int(ncols), ) flat_axes = list(ax.flatten()) if cmap and heatmap_image: cax, kw,vmin,vmax,cmap = _draw_colorbar(heatmap_image,ax, threshold=heatmap_threshold, aspect=40, fraction=0.05, anchor=(0,-0.5), pad=0.05, panchor=(10.0, 0.5), shrink=0.99, cut_coords = cut_coords, positive_only = positive_only, negative_only = negative_only, cmap=cmap, really_draw=True, ) if positive_only: vmin = 0 elif negative_only: vmax = 0 for ix, ax_i in enumerate(flat_axes): try: display = nilearn.plotting.plot_anat(bg_img, axes=ax_i, display_mode='y', cut_coords=[cut_coords[ix]], annotate=False, black_bg=black_bg, dim=dimming, cmap=anatomical_cmap, ) except IndexError: ax_i.axis('off') else: display.add_overlay(heatmap_img, threshold=heatmap_threshold, alpha=heatmap_alpha, cmap=cmap, vmin = vmin,vmax = vmax, ) if contour_image: display.add_contours(contour_img, alpha=contour_alpha, levels=[0.8], linewidths=linewidth, ) ax_i.set_xlabel('{} label'.format(ix)) slice_title = '{0:.2f}mm'.format(cut_coords[ix]) text = ax_i.text(0.5,-position_vspace, slice_title, horizontalalignment='center', fontsize=rcParams['font.size'], ) if legend: for ix, img in enumerate(imgs): insertion_legend, = plt.plot([],[], color=colors[ix], label=legend) if cut_coord_length == ncols*(nrows-1)+1: plt.legend(loc='upper left',bbox_to_anchor=(-0.1, -0.3)) else: plt.legend(loc='lower left',bbox_to_anchor=(1.1, 0.)) if figure_title: fig.suptitle(figure_title, color=title_color) if save_as: save_as = path.abspath(path.expanduser(save_as)) save_dir,_ = os.path.split(save_as) try: os.makedirs(save_dir) except FileExistsError: pass plt.savefig(save_as) plt.close()
def contour_slices(bg_image, file_template, auto_figsize=False, invert=False, alpha=[0.9], colors=['r','g','b'], dimming=0., figure_title='', force_reverse_slice_order=True, legend_template='', levels_percentile=[80], linewidths=(), ratio='portrait', save_as='', scale=0.4, slice_spacing=0.5, substitutions=[{},], style='light', title_color='#BBBBBB', ): """ Plot coronal `bg_image` slices at a given spacing, and overlay contours from a list of NIfTI files. Parameters ---------- bg_image : str Path to the NIfTI image to draw in grayscale as th eplot background. This would commonly be some sort of brain template. file_template : str String template giving the path to the overlay stack. To create multiple overlays, this template will iteratively be substituted with each of the substitution dictionaries in the `substitutions` parameter. auto_figsize : boolean, optional Whether to automatically determine the size of the figure. invert : boolean, optional Whether to automatically invert data matrix values (useful if the image consists of negative values, e.g. when dealing with negative contrast agent CBV scans). alpha : list, optional List of floats, specifying with how much alpha to draw each contour. colors : list, optional List of colors in which to plot the overlays. dimming : float, optional Dimming factor, generally between -2 and 2 (-2 increases contrast, 2 decreases it). This parameter is passed directly to `nilearn.plotting.plot_anat()` Set to 'auto', to use nilearn automagick dimming. figure_title : str, optional Title for the figure. force_reverse_slice_order : bool, optional Whether to force the reversal of the slice order. This can be done to enforce a visual presentation without having to modify the underlying data (i.e. visualize neurological-order slices in radiological order). This option should generally be avoided, ideally one would not obfuscate the data orientation when plotting. legend_template : string, optional String template which can be formatted with the dictionaries contained in the `substitutions` parameter. The resulting strings will give the legend text. levels_percentile : list, optional List of integers, specifying at which percentiles of each overlay to draw contours. line_widths : tuple, optional Tuple of desired contour line widths (one per substitution). ratio : list or {'landscape', 'portrait'}, optional Either a list of 2 integers giving the desired number of rows and columns (in this order), or a string, which is either 'landscape' or 'portrait', and which prompts the function to auto-determine the best number of rows and columns given the number of slices and the `scale` attribute. save_as : str, optional Path under which to save the output figure. The string may contain formatting fields from the first dictionary in the `substitutions` variable. scale : float, optional The expected ratio of the slice height divided by the sum of the slice height and width. This somewhat complex metric controls the row and column distribution of slices in the 'landscape' and 'portrait' plotting shapes. slice_spacing : float Slice spacing in mm. substitutions : list of dicts, optional A list of dictionaries, with keys including all substitution keys found in the `file_template` parameter, and values giving desired substitution values which point the `file_template` string templated to existing filed which are to be included in the overlay stack. Such a dictionary is best obtained via `samri.utilities.bids_substitution_iterator()`. style : {'light', 'dark', ''}, optional Default SAMRI styling which to apply, set to an empty string to apply no styling and leave it to the environment matplotlibrc. title_color : string, optional String specifying the desired color for the title. This needs to be specified in-function, because the matplotlibrc styling standard does not provide for title color specification [matplotlibrc_title] References ---------- .. [matplotlibrc_title] https://stackoverflow.com/questions/30109465/matplotlib-set-title-color-in-stylesheet """ if len(substitutions) == 0: print('ERROR: You have specified a substitution dictionary of length 0. There needs to be at least one set of substitutions. If your string contains no formatting fields, please pass a list containing an empty dictionary to the `sbstitution parameter` (this is also its default value).') plotting_module_path = path.dirname(path.realpath(__file__)) if style=='light': black_bg=False anatomical_cmap = 'binary' style_path = path.join(plotting_module_path,'contour_slices.conf') plt.style.use([style_path]) elif style=='dark': black_bg=True anatomical_cmap = 'binary_r' style_path = path.join(plotting_module_path,'contour_slices_dark.conf') plt.style.use([style_path]) else: anatomical_cmap = 'binary' black_bg=False bg_image = path.abspath(path.expanduser(bg_image)) bg_img = nib.load(bg_image) if bg_img.header['dim'][0] > 3: bg_data = bg_img.get_data() ndim = 0 for i in range(len(bg_img.header['dim'])-1): current_dim = bg_img.header['dim'][i+1] if current_dim == 1: break ndim += 1 bg_img.header['dim'][0] = ndim bg_img.header['pixdim'][ndim+1:] = 0 bg_data = bg_data.T[0].T bg_img = nib.nifti1.Nifti1Image(bg_data, bg_img.affine, bg_img.header) imgs = [] bounds = [] levels = [] slice_order_is_reversed = 0 for substitution in substitutions: filename = file_template.format(**substitution) filename = path.abspath(path.expanduser(filename)) img = nib.load(filename) data = img.get_data() if img.header['dim'][0] > 3: img = collapse(img) if invert: data = -data img = nib.nifti1.Nifti1Image(data, img.affine, img.header) #we should only be looking at the percentile of the entire data matrix, rather than just the active slice for level_percentile in levels_percentile: level = np.percentile(data,level_percentile) levels.append(level) slice_row = img.affine[1] subthreshold_start_slices = 0 while True: for i in np.arange(data.shape[1]): my_slice = data[:,i,:] if my_slice.max() < min(levels): subthreshold_start_slices += 1 else: break break subthreshold_end_slices = 0 while True: for i in np.arange(data.shape[1])[::-1]: my_slice = data[:,i,:] if my_slice.max() < min(levels): subthreshold_end_slices += 1 else: break break slice_thickness = (slice_row[0]**2+slice_row[1]**2+slice_row[2]**2)**(1/2) best_guess_negative = abs(min(slice_row[0:3])) > abs(max(slice_row[0:3])) slices_number = data.shape[list(slice_row).index(max(slice_row))] img_min_slice = slice_row[3] + subthreshold_start_slices*slice_thickness img_max_slice = slice_row[3] + (slices_number-subthreshold_end_slices)*slice_thickness bounds.extend([img_min_slice,img_max_slice]) if best_guess_negative: slice_order_is_reversed += 1 else: slice_order_is_reversed -= 1 imgs.append(img) if len(alpha) == 1: alpha = alpha * len(imgs) min_slice = min(bounds) max_slice = max(bounds) cut_coords = np.arange(min_slice, max_slice, slice_spacing) if slice_order_is_reversed > 0: cut_coords = cut_coords[::-1] if force_reverse_slice_order: cut_coords = cut_coords[::-1] if not linewidths: linewidths = (rcParams['axes.linewidth'],)*len(imgs) if len(cut_coords) > 3: cut_coord_length = len(cut_coords) if legend_template: cut_coord_length += 1 try: nrows, ncols = ratio except ValueError: if ratio == "portrait": ncols = np.floor(cut_coord_length**scale) nrows = np.ceil(cut_coord_length/float(ncols)) elif ratio == "landscape": nrows = np.floor(cut_coord_length**(scale)) ncols = np.ceil(cut_coord_length/float(nrows)) # we adjust the respective rc.Param here, because it needs to be set before drawing to take effect if legend_template and cut_coord_length == ncols*(nrows-1)+1: rcParams['figure.subplot.bottom'] = np.max([rcParams['figure.subplot.bottom']-0.05,0.]) if auto_figsize: figsize = np.array(rcParams['figure.figsize']) figsize_scales = figsize/np.array([float(ncols),float(nrows)]) figsize_scale = figsize_scales.min() fig, ax = plt.subplots(figsize=(ncols*figsize_scale,nrows*figsize_scale), nrows=int(nrows), ncols=int(ncols), ) else: fig, ax = plt.subplots( nrows=int(nrows), ncols=int(ncols), ) flat_axes = list(ax.flatten()) for ix, ax_i in enumerate(flat_axes): try: display = nilearn.plotting.plot_anat(bg_img, axes=ax_i, display_mode='y', cut_coords=[cut_coords[ix]], annotate=False, black_bg=black_bg, dim=dimming, cmap=anatomical_cmap, ) except IndexError: ax_i.axis('off') else: for img_ix, img in enumerate(imgs): color = colors[img_ix] display.add_contours(img, alpha=alpha[img_ix], colors=[color], levels=levels[img_ix], linewidths=(linewidths[img_ix],), ) if legend_template: for ix, img in enumerate(imgs): insertion_legend, = plt.plot([],[], color=colors[ix], label=legend_template.format(**substitutions[ix])) if cut_coord_length == ncols*(nrows-1)+1: plt.legend(loc='upper left',bbox_to_anchor=(-0.1, -0.3)) else: plt.legend(loc='lower left',bbox_to_anchor=(1.1, 0.)) else: display = nilearn.plotting.plot_anat(bg_img, display_mode='y', cut_coords=cut_coords, black_bg=black_bg, ) for ix, img in enumerate(imgs): color = colors[ix] display.add_contours(img, levels=levels, colors=[color]) if figure_title: fig.suptitle(figure_title, color=title_color) if save_as: save_as = save_as.format(**substitutions[0]) save_as = path.abspath(path.expanduser(save_as)) save_dir,_ = os.path.split(save_as) try: os.makedirs(save_dir) except FileExistsError: pass plt.savefig(save_as, #facecolor=fig.get_facecolor(), ) plt.close()
def threshold_volume(in_file, substitution={}, masker='', threshold=45, threshold_is_percentile=False, inverted_data=False, ): """Return the volume which lies above a given threshold in a NIfTI (implicitly, in the volume units of the respective NIfTI). Parameters ---------- in_file : str Path to a NIfTI file. 4D files will be collapsed to 3D using the mean function. masker : str or nilearn.NiftiMasker, optional Path to a NIfTI file containing a mask (1 and 0 values) or a `nilearn.NiftiMasker` object. NOT YET SUPPORTED! threshold : float, optional A float giving the voxel value threshold. threshold_is_percentile : bool, optional Whether `threshold` is to be interpreted not literally, but as a percentile of the data matrix. This is useful for making sure that the volume estimation is not susceptible to the absolute value range, but only the value distribution. inverted_data : bool, optional Whether data is inverted (flipped with respect to 0). If `True`, the inverse of the threshold is automatically computed. Returns ------- float The volume (in volume units of the respective NIfTI) containing values above the given threshold. """ if substitution: in_file = in_file.format(**substitution) in_file = path.abspath(path.expanduser(in_file)) try: img = nib.load(in_file) except FileNotFoundError: return float('NaN') if img.header['dim'][0] > 4: raise ValueError("Files with more than 4 dimensions are not currently supported.") elif img.header['dim'][0] > 3: img = collapse(img) data = img.get_data() x_len = (img.affine[0][0]**2+img.affine[0][1]**2+img.affine[0][2]**2)**(1/2.) y_len = (img.affine[1][0]**2+img.affine[1][1]**2+img.affine[1][2]**2)**(1/2.) z_len = (img.affine[2][0]**2+img.affine[2][1]**2+img.affine[2][2]**2)**(1/2.) voxel_volume = x_len*y_len*z_len if inverted_data and not threshold_is_percentile: threshold_voxels = (data < threshold).sum() else: if inverted_data: threshold = 100-threshold if threshold_is_percentile: threshold = np.percentile(data,threshold) threshold_voxels = (data > threshold).sum() threshold_volume = voxel_volume * threshold_voxels return threshold_volume