From 7294d7f7d8d4271ee7cec44e8aaedbc39551053b Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Thu, 15 Aug 2024 21:21:09 -0400 Subject: [PATCH] remove more bokeh, move get_contours() to cnmf.utilities --- caiman/source_extraction/cnmf/estimates.py | 548 --------------------- caiman/source_extraction/cnmf/utilities.py | 113 +++++ 2 files changed, 113 insertions(+), 548 deletions(-) diff --git a/caiman/source_extraction/cnmf/estimates.py b/caiman/source_extraction/cnmf/estimates.py index a06250881..c61cf8c34 100644 --- a/caiman/source_extraction/cnmf/estimates.py +++ b/caiman/source_extraction/cnmf/estimates.py @@ -9,7 +9,6 @@ import caiman from caiman.base.rois import detect_duplicates_and_subsets, nf_match_neurons_in_binary_masks, nf_masks_to_neurof_dict from caiman.components_evaluation import evaluate_components_CNN, estimate_components_quality_auto, select_components_from_metrics, compute_eccentricity -from caiman.source_extraction.cnmf.initialization import downscale from caiman.source_extraction.cnmf.merging import merge_iteration, merge_components from caiman.source_extraction.cnmf.spatial import threshold_components from caiman.source_extraction.cnmf.temporal import constrained_foopsi_parallel @@ -158,553 +157,6 @@ def __init__(self, A=None, b=None, C=None, f=None, R=None, dims=None): self.A_thr = None self.discarded_components = None - - - def plot_contours(self, img=None, idx=None, thr_method='max', - thr=0.2, display_numbers=True, params=None, - cmap='viridis'): - """view contours of all spatial footprints. - - Args: - img : np.ndarray - background image for contour plotting. Default is the mean - image of all spatial components (d1 x d2) - idx : list - list of accepted components - thr_method : str - thresholding method for computing contours ('max', 'nrg') - if list of coordinates self.coordinates is None, i.e. not already computed - thr : float - threshold value - only effective if self.coordinates is None, i.e. not already computed - display_numbers : bool - flag for displaying the id number of each contour - params : params object - set of dictionary containing the various parameters - """ - if 'csc_matrix' not in str(type(self.A)): - self.A = scipy.sparse.csc_matrix(self.A) - if img is None: - img = np.reshape(np.array(self.A.mean(1)), self.dims, order='F') - if self.coordinates is None: # not hasattr(self, 'coordinates'): - self.coordinates = caiman.utils.visualization.get_contours(self.A, img.shape, thr=thr, thr_method=thr_method) - plt.figure() - if params is not None: - plt.suptitle('min_SNR=%1.2f, rval_thr=%1.2f, use_cnn=%i' - %(params.quality['min_SNR'], - params.quality['rval_thr'], - int(params.quality['use_cnn']))) - if idx is None: - caiman.utils.visualization.plot_contours(self.A, img, coordinates=self.coordinates, - display_numbers=display_numbers, - cmap=cmap) - else: - if not isinstance(idx, list): - idx = idx.tolist() - coor_g = [self.coordinates[cr] for cr in idx] - bad = list(set(range(self.A.shape[1])) - set(idx)) - coor_b = [self.coordinates[cr] for cr in bad] - plt.subplot(1, 2, 1) - caiman.utils.visualization.plot_contours(self.A[:, idx], img, - coordinates=coor_g, - display_numbers=display_numbers, - cmap=cmap) - plt.title('Accepted Components') - bad = list(set(range(self.A.shape[1])) - set(idx)) - plt.subplot(1, 2, 2) - caiman.utils.visualization.plot_contours(self.A[:, bad], img, - coordinates=coor_b, - display_numbers=display_numbers, - cmap=cmap) - plt.title('Rejected Components') - return self - - def plot_contours_nb(self, img=None, idx=None, thr_method='max', - thr=0.2, params=None, line_color='white', cmap='viridis'): - """view contours of all spatial footprints (notebook environment). - - Args: - img : np.ndarray - background image for contour plotting. Default is the mean - image of all spatial components (d1 x d2) - idx : list - list of accepted components - thr_method : str - thresholding method for computing contours ('max', 'nrg') - if list of coordinates self.coordinates is None, i.e. not already computed - thr : float - threshold value - only effective if self.coordinates is None, i.e. not already computed - params : params object - set of dictionary containing the various parameters - """ - try: - if 'csc_matrix' not in str(type(self.A)): - self.A = scipy.sparse.csc_matrix(self.A) - if self.dims is None: - self.dims = img.shape - if img is None: - img = np.reshape(np.array(self.A.mean(1)), self.dims, order='F') - if self.coordinates is None: # not hasattr(self, 'coordinates'): - self.coordinates = caiman.utils.visualization.get_contours(self.A, - self.dims, thr=thr, thr_method=thr_method) - if idx is None: - p = caiman.utils.visualization.nb_plot_contour(img, self.A, self.dims[0], - self.dims[1], coordinates=self.coordinates, - thr_method=thr_method, thr=thr, show=False, - line_color=line_color, cmap=cmap) - p.title.text = 'Contour plots of found components' - if params is not None: - p.xaxis.axis_label = '''\ - min_SNR={min_SNR}, rval_thr={rval_thr}, use_cnn={use_cnn}\ - '''.format(min_SNR=params.quality['min_SNR'], - rval_thr=params.quality['rval_thr'], - use_cnn=params.quality['use_cnn']) - bokeh.plotting.show(p) - else: - if not isinstance(idx, list): - idx = idx.tolist() - coor_g = [self.coordinates[cr] for cr in idx] - bad = list(set(range(self.A.shape[1])) - set(idx)) - coor_b = [self.coordinates[cr] for cr in bad] - p1 = caiman.utils.visualization.nb_plot_contour(img, self.A[:, idx], - self.dims[0], self.dims[1], coordinates=coor_g, - thr_method=thr_method, thr=thr, show=False, - line_color=line_color, cmap=cmap) - p1.width = 450 - p1.height = 450 * self.dims[0] // self.dims[1] - p1.title.text = "Accepted Components" - if params is not None: - p1.xaxis.axis_label = '''\ - min_SNR={min_SNR}, rval_thr={rval_thr}, use_cnn={use_cnn}\ - '''.format(min_SNR=params.quality['min_SNR'], - rval_thr=params.quality['rval_thr'], - use_cnn=params.quality['use_cnn']) - bad = list(set(range(self.A.shape[1])) - set(idx)) - p2 = caiman.utils.visualization.nb_plot_contour(img, self.A[:, bad], - self.dims[0], self.dims[1], coordinates=coor_b, - thr_method=thr_method, thr=thr, show=False, - line_color=line_color, cmap=cmap) - p2.width = 450 - p2.height = 450 * self.dims[0] // self.dims[1] - p2.title.text = 'Rejected Components' - if params is not None: - p2.xaxis.axis_label = '''\ - min_SNR={min_SNR}, rval_thr={rval_thr}, use_cnn={use_cnn}\ - '''.format(min_SNR=params.quality['min_SNR'], - rval_thr=params.quality['rval_thr'], - use_cnn=params.quality['use_cnn']) - bokeh.plotting.show(bokeh.layouts.row(p1, p2)) - except: - print("Error with bokeh plotter.") - print("Using non-interactive plot as fallback") - self.plot_contours(img=img, idx=idx, thr_method=thr_method, - thr=thr, params=params, cmap=cmap) - return self - - def view_components(self, Yr=None, img=None, idx=None): - """view spatial and temporal components interactively - - Args: - Yr : np.ndarray - movie in format pixels (d) x frames (T) - - img : np.ndarray - background image for contour plotting. Default is the mean - image of all spatial components (d1 x d2) - - idx : list - list of components to be plotted - """ - if 'csc_matrix' not in str(type(self.A)): - self.A = scipy.sparse.csc_matrix(self.A) - - plt.ion() - nr, T = self.C.shape - if self.R is None: - self.R = self.YrA - if self.R.shape != (nr, T): - if self.YrA is None: - self.compute_residuals(Yr) - else: - self.R = self.YrA - - if img is None: - img = np.reshape(np.array(self.A.mean(axis=1)), self.dims, order='F') - - if idx is None: - caiman.utils.visualization.view_patches_bar(Yr, self.A, self.C, - self.b, self.f, self.dims[0], self.dims[1], YrA=self.R, img=img, - r_values=self.r_values, SNR=self.SNR_comp, cnn_preds=self.cnn_preds) - else: - caiman.utils.visualization.view_patches_bar( - Yr, self.A.tocsc()[:,idx], self.C[idx], self.b, self.f, - self.dims[0], self.dims[1], YrA=self.R[idx], img=img, - r_values=None if self.r_values is None else self.r_values[idx], - SNR=None if self.SNR_comp is None else self.SNR_comp[idx], - cnn_preds=None if np.sum(self.cnn_preds) in (0, None) else self.cnn_preds[idx]) - return self - - def nb_view_components(self, Yr=None, img=None, idx=None, - denoised_color=None, cmap='jet', thr=0.99): - """view spatial and temporal components interactively in a notebook - - Args: - Yr : np.ndarray - movie in format pixels (d) x frames (T) - - img : np.ndarray - background image for contour plotting. Default is the mean - image of all spatial components (d1 x d2) - - idx : list - list of components to be plotted - - thr: double - threshold regulating the extent of the displayed patches - - denoised_color: string or None - color name (e.g. 'red') or hex color code (e.g. '#F0027F') - - cmap: string - name of colormap (e.g. 'viridis') used to plot image_neurons - """ - if 'csc_matrix' not in str(type(self.A)): - self.A = scipy.sparse.csc_matrix(self.A) - - plt.ion() - nr, T = self.C.shape - if self.R is None or not isinstance(self.R, np.ndarray): - self.R = self.YrA - if self.R.shape != [nr, T]: - if self.YrA is None: - self.compute_residuals(Yr) - else: - self.R = self.YrA - - if img is None: - img = np.reshape(np.array(self.A.mean(axis=1)), self.dims, order='F') - - if idx is None: - caiman.utils.visualization.nb_view_patches( - Yr, self.A, self.C, self.b, self.f, self.dims[0], self.dims[1], - YrA=self.R, image_neurons=img, thr=thr, denoised_color=denoised_color, cmap=cmap, - r_values=self.r_values, SNR=self.SNR_comp, cnn_preds=self.cnn_preds) - else: - caiman.utils.visualization.nb_view_patches( - Yr, self.A.tocsc()[:,idx], self.C[idx], self.b, self.f, - self.dims[0], self.dims[1], YrA=self.R[idx], image_neurons=img, - thr=thr, denoised_color=denoised_color, cmap=cmap, - r_values=None if self.r_values is None else self.r_values[idx], - SNR=None if self.SNR_comp is None else self.SNR_comp[idx], - cnn_preds=None if np.sum(self.cnn_preds) in (0, None) else self.cnn_preds[idx]) - return self - - def hv_view_components(self, Yr=None, img=None, idx=None, - denoised_color=None, cmap='viridis'): - """view spatial and temporal components interactively in a notebook - - Args: - Yr : np.ndarray - movie in format pixels (d) x frames (T) - - img : np.ndarray - background image for contour plotting. Default is the mean - image of all spatial components (d1 x d2) - - idx : list - list of components to be plotted - - denoised_color: string or None - color name (e.g. 'red') or hex color code (e.g. '#F0027F') - - cmap: string - name of colormap (e.g. 'viridis') used to plot image_neurons - """ - if 'csc_matrix' not in str(type(self.A)): - self.A = scipy.sparse.csc_matrix(self.A) - - plt.ion() - nr, T = self.C.shape - if self.R is None or not isinstance(self.R, np.ndarray): - self.R = self.YrA - if self.R.shape != (nr, T): - if self.YrA is None: - self.compute_residuals(Yr) - else: - self.R = self.YrA - - if img is None: - img = np.reshape(np.array(self.A.mean(axis=1)), self.dims, order='F') - - if idx is None: - hv_plot = caiman.utils.visualization.hv_view_patches( - Yr, self.A, self.C, self.b, self.f, self.dims[0], self.dims[1], - YrA=self.R, image_neurons=img, denoised_color=denoised_color, cmap=cmap, - r_values=self.r_values, SNR=self.SNR_comp, cnn_preds=self.cnn_preds) - else: - hv_plot = caiman.utils.visualization.hv_view_patches( - Yr, self.A.tocsc()[:, idx], self.C[idx], self.b, self.f, - self.dims[0], self.dims[1], YrA=self.R[idx], image_neurons=img, - denoised_color=denoised_color, cmap=cmap, - r_values=None if self.r_values is None else self.r_values[idx], - SNR=None if self.SNR_comp is None else self.SNR_comp[idx], - cnn_preds=None if np.sum(self.cnn_preds) in (0, None) else self.cnn_preds[idx]) - return hv_plot - - def nb_view_components_3d(self, Yr=None, image_type='mean', dims=None, - max_projection=False, axis=0, - denoised_color=None, cmap='jet', thr=0.9): - """view spatial and temporal components interactively in a notebook - (version for 3d data) - - Args: - Yr : np.ndarray - movie in format pixels (d) x frames (T) (only required to - compute the correlation image) - - - dims: tuple of ints - dimensions of movie (x, y and z) - - image_type: 'mean'|'max'|'corr' - image to be overlaid to neurons (average of shapes, - maximum of shapes or nearest neighbor correlation of raw data) - - max_projection: bool - plot max projection along specified axis if True, o/w plot layers - - axis: int (0, 1 or 2) - axis along which max projection is performed or layers are shown - - thr: scalar between 0 and 1 - Energy threshold for computing contours - - denoised_color: string or None - color name (e.g. 'red') or hex color code (e.g. '#F0027F') - - cmap: string - name of colormap (e.g. 'viridis') used to plot image_neurons - - """ - if 'csc_matrix' not in str(type(self.A)): - self.A = scipy.sparse.csc_matrix(self.A) - if dims is None: - dims = self.dims - plt.ion() - nr, T = self.C.shape - if self.R is None or not isinstance(self.R, np.ndarray): - self.R = self.YrA - if self.R.shape != [nr, T]: - if self.YrA is None: - self.compute_residuals(Yr) - else: - self.R = self.YrA - - caiman.utils.visualization.nb_view_patches3d(self.YrA, self.A, self.C, - dims=dims, image_type=image_type, Yr=Yr, - max_projection=max_projection, axis=axis, thr=thr, - denoised_color=denoised_color, cmap=cmap) - - return self - - def make_color_movie(self, imgs, q_max=99.75, q_min=2, gain_res=1, - magnification=1, include_bck=True, - frame_range=slice(None, None, None), - bpx=0, save_movie=False, display=True, - movie_name='results_movie_color.avi', - opencv_code='H264'): - """ - Displays a color movie where each component is given an arbitrary - color. Will be merged with play_movie soon. Check that function for - arg definitions. - """ - dims = imgs.shape[1:] - cols_c = np.random.rand(self.C.shape[0], 1, 3) - cols_f = np.ones((self.f.shape[0], 1, 3))/8 - Cs = np.vstack((np.expand_dims(self.C[:, frame_range], -1)*cols_c, - np.expand_dims(self.f[:, frame_range], -1)*cols_f)) - AC = np.tensordot(np.hstack((self.A.toarray(), self.b)), Cs, axes=(1, 0)) - AC = AC.reshape((dims) + (-1, 3)).transpose(2, 0, 1, 3) - - AC /= np.percentile(AC, 99.75, axis=(0, 1, 2)) - mov = caiman.movie(np.concatenate((np.repeat(np.expand_dims(imgs[frame_range]/np.percentile(imgs[:1000], 99.75), -1), 3, 3), - AC), axis=2)) - if not display: - return mov - - mov.play(q_min=q_min, q_max=q_max, magnification=magnification, - save_movie=save_movie, movie_name=movie_name) - - return mov - - - def play_movie(self, imgs, q_max=99.75, q_min=2, gain_res=1, - magnification=1, include_bck=True, - frame_range=slice(None, None, None), - bpx=0, thr=0., save_movie=False, - movie_name='results_movie.avi', - display=True, opencv_codec='H264', - use_color=False, gain_color=4, gain_bck=0.2): - """ - Displays a movie with three panels (original data (left panel), - reconstructed data (middle panel), residual (right panel)) - - Args: - imgs: np.array (possibly memory mapped, t,x,y[,z]) - Imaging data - - q_max: float (values in [0, 100], default: 99.75) - percentile for maximum plotting value - - q_min: float (values in [0, 100], default: 1) - percentile for minimum plotting value - - gain_res: float (1) - amplification factor for residual movie - - magnification: float (1) - magnification factor for whole movie - - include_bck: bool (True) - flag for including background in original and reconstructed movie - - frame_range: range or slice or list (default: slice(None)) - display only a subset of frames - - bpx: int (default: 0) - number of pixels to exclude on each border - - thr: float (values in [0, 1[) (default: 0) - threshold value for contours, no contours if thr=0 - - save_movie: bool (default: False) - flag to save an avi file of the movie - - movie_name: str (default: 'results_movie.avi') - name of saved file - - display: bool (default: True) - flag for playing the movie (to stop the movie press 'q') - - opencv_codec: str (default: 'H264') - FourCC video codec for saving movie. Check http://www.fourcc.org/codecs.php - - use_color: bool (default: False) - flag for making a color movie. If True a random color will be assigned - for each of the components - - gain_color: float (default: 4) - amplify colors in the movie to make them brighter - - gain_bck: float (default: 0.2) - dampen background in the movie to expose components (applicable - only when color is used.) - - Returns: - mov: The concatenated output movie - """ - dims = imgs.shape[1:] - if 'movie' not in str(type(imgs)): - imgs = caiman.movie(imgs[frame_range]) - else: - imgs = imgs[frame_range] - - if use_color: - cols_c = np.random.rand(self.C.shape[0], 1, 3)*gain_color - Cs = np.expand_dims(self.C[:, frame_range], -1)*cols_c - #AC = np.tensordot(np.hstack((self.A.toarray(), self.b)), Cs, axes=(1, 0)) - Y_rec_color = np.tensordot(self.A.toarray(), Cs, axes=(1, 0)) - Y_rec_color = Y_rec_color.reshape((dims) + (-1, 3), order='F').transpose(2, 0, 1, 3) - - AC = self.A.dot(self.C[:, frame_range]) - Y_rec = AC.reshape(dims + (-1,), order='F') - Y_rec = Y_rec.transpose([2, 0, 1]) - if self.W is not None: - ssub_B = int(round(np.sqrt(np.prod(dims) / self.W.shape[0]))) - B = imgs.reshape((-1, np.prod(dims)), order='F').T - AC - if ssub_B == 1: - B = self.b0[:, None] + self.W.dot(B - self.b0[:, None]) - else: - WB = self.W.dot(downscale(B.reshape(dims + (B.shape[-1],), order='F'), - (ssub_B, ssub_B, 1)).reshape((-1, B.shape[-1]), order='F')) - Wb0 = self.W.dot(downscale(self.b0.reshape(dims, order='F'), - (ssub_B, ssub_B)).reshape((-1, 1), order='F')) - B = self.b0.flatten('F')[:, None] + (np.repeat(np.repeat((WB - Wb0).reshape(((dims[0] - 1) // ssub_B + 1, (dims[1] - 1) // ssub_B + 1, -1), order='F'), - ssub_B, 0), ssub_B, 1)[:dims[0], :dims[1]].reshape((-1, B.shape[-1]), order='F')) - B = B.reshape(dims + (-1,), order='F').transpose([2, 0, 1]) - elif self.b is not None and self.f is not None: - B = self.b.dot(self.f[:, frame_range]) - if 'matrix' in str(type(B)): - B = B.toarray() - B = B.reshape(dims + (-1,), order='F').transpose([2, 0, 1]) - else: - B = np.zeros_like(Y_rec) - if bpx > 0: - B = B[:, bpx:-bpx, bpx:-bpx] - Y_rec = Y_rec[:, bpx:-bpx, bpx:-bpx] - imgs = imgs[:, bpx:-bpx, bpx:-bpx] - - Y_res = imgs - Y_rec - B - if use_color: - if bpx > 0: - Y_rec_color = Y_rec_color[:, bpx:-bpx, bpx:-bpx] - mov = caiman.concatenate((np.repeat(np.expand_dims(imgs - (not include_bck) * B, -1), 3, 3), - Y_rec_color + include_bck * np.expand_dims(B*gain_bck, -1), - np.repeat(np.expand_dims(Y_res * gain_res, -1), 3, 3)), axis=2) - else: - mov = caiman.concatenate((imgs - (not include_bck) * B, - Y_rec + include_bck * B, Y_res * gain_res), axis=2) - if not display: - return mov - - if thr > 0: - if save_movie: - fourcc = cv2.VideoWriter_fourcc(*opencv_codec) - out = cv2.VideoWriter(movie_name, fourcc, 30.0, - tuple([int(magnification*s) for s in mov.shape[1:][::-1]])) - contours = [] - for a in self.A.T.toarray(): - a = a.reshape(dims, order='F') - if bpx > 0: - a = a[bpx:-bpx, bpx:-bpx] - # a = cv2.GaussianBlur(a, (9, 9), .5) - if magnification != 1: - a = cv2.resize(a, None, fx=magnification, fy=magnification, - interpolation=cv2.INTER_LINEAR) - ret, thresh = cv2.threshold(a, thr * np.max(a), 1., 0) - contour, hierarchy = cv2.findContours( - thresh.astype('uint8'), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) - contours.append(contour) - contours.append(list([c + np.array([[a.shape[1], 0]]) for c in contour])) - contours.append(list([c + np.array([[2 * a.shape[1], 0]]) for c in contour])) - - maxmov = np.nanpercentile(mov[0:10], q_max) if q_max < 100 else np.nanmax(mov) - minmov = np.nanpercentile(mov[0:10], q_min) if q_min > 0 else np.nanmin(mov) - for iddxx, frame in enumerate(mov): - if magnification != 1: - frame = cv2.resize(frame, None, fx=magnification, fy=magnification, - interpolation=cv2.INTER_LINEAR) - frame = np.clip((frame - minmov) * 255. / (maxmov - minmov), 0, 255) - if frame.ndim < 3: - frame = np.repeat(frame[..., None], 3, 2) - for contour in contours: - cv2.drawContours(frame, contour, -1, (0, 255, 255), 1) - cv2.imshow('frame', frame.astype('uint8')) - if save_movie: - out.write(frame.astype('uint8')) - if cv2.waitKey(30) & 0xFF == ord('q'): - break - if save_movie: - out.release() - cv2.destroyAllWindows() - - else: - mov.play(q_min=q_min, q_max=q_max, magnification=magnification, - save_movie=save_movie, movie_name=movie_name) - - return mov - def compute_background(self, Yr): """compute background (has big memory requirements) diff --git a/caiman/source_extraction/cnmf/utilities.py b/caiman/source_extraction/cnmf/utilities.py index 5db5ce4e5..6e20690be 100644 --- a/caiman/source_extraction/cnmf/utilities.py +++ b/caiman/source_extraction/cnmf/utilities.py @@ -14,6 +14,8 @@ import scipy from scipy.sparse import spdiags, issparse, csc_matrix, csr_matrix import scipy.ndimage as ndi +from skimage.measure import find_contours +from warnings import warn import caiman.base.rois import caiman.cluster @@ -1109,3 +1111,114 @@ def fast_graph_Laplacian_pixel(pars): ind = np.where(w>0)[0] return indices[ind].tolist(), w[ind].tolist() + + +def get_contours(A, dims, thr=0.9, thr_method='nrg', swap_dim=False, slice_dim: int = None): + """Gets contour of spatial components and returns their coordinates + + Args: + A: np.ndarray or sparse matrix + Matrix of Spatial components (d x K) + + dims: tuple of ints + Spatial dimensions of movie + + thr: scalar between 0 and 1 + Energy threshold for computing contours (default 0.9) + + thr_method: string + Method of thresholding: + 'max' sets to zero pixels that have value less than a fraction of the max value + 'nrg' keeps the pixels that contribute up to a specified fraction of the energy + + swap_dim: bool + If False (default), each column of A should be reshaped in F-order to recover the mask; + this is correct if the dimensions have not been reordered from (y, x[, z]). + If True, each column should be reshaped in C-order; this is correct for dims = ([z, ]x, y). + + slice_dim: int or None + Which dimension to slice along if we have 3D data. (i.e., get contours on each plane along this axis). + The default (None) is 0 if swap_dim is True, else -1. + + Returns: + Coor: list of coordinates with center of mass and + contour plot coordinates (per layer) for each component + """ + + if 'csc_matrix' not in str(type(A)): + A = csc_matrix(A) + d, nr = np.shape(A) + + coordinates = [] + + # get the center of mass of neurons( patches ) + cm = caiman.base.rois.com(A, *dims, order='C' if swap_dim else 'F') + + # for each patches + for i in range(nr): + pars: dict = dict() + # we compute the cumulative sum of the energy of the Ath component that has been ordered from least to highest + patch_data = A.data[A.indptr[i]:A.indptr[i + 1]] + indx = np.argsort(patch_data)[::-1] + if thr_method == 'nrg': + cumEn = np.cumsum(patch_data[indx] ** 2) + if len(cumEn) == 0: + pars = dict( + coordinates=np.array([]), + CoM=np.array([np.NaN, np.NaN]), + neuron_id=i + 1, + ) + coordinates.append(pars) + continue + else: + # we work with normalized values + cumEn /= cumEn[-1] + Bvec = np.ones(d) + # we put it in a similar matrix + Bvec[A.indices[A.indptr[i]:A.indptr[i + 1]][indx]] = cumEn + else: + if thr_method != 'max': + warn("Unknown threshold method. Choosing max") + Bvec = np.zeros(d) + Bvec[A.indices[A.indptr[i]:A.indptr[i + 1]]] = patch_data / patch_data.max() + + if swap_dim: + Bmat = np.reshape(Bvec, dims, order='C') + else: + Bmat = np.reshape(Bvec, dims, order='F') + + def get_slice_coords(B: np.ndarray) -> np.ndarray: + """Get contour coordinates for a 2D slice""" + d1, d2 = B.shape + vertices = find_contours(B.T, thr) + # this fix is necessary for having disjoint figures and borders plotted correctly + v = np.atleast_2d([np.nan, np.nan]) + for _, vtx in enumerate(vertices): + num_close_coords = np.sum(np.isclose(vtx[0, :], vtx[-1, :])) + if num_close_coords < 2: + if num_close_coords == 0: + # case angle + newpt = np.round(np.mean(vtx[[0, -1], :], axis=0) / [d2, d1]) * [d2, d1] + vtx = np.concatenate((newpt[np.newaxis, :], vtx, newpt[np.newaxis, :]), axis=0) + else: + # case one is border + vtx = np.concatenate((vtx, vtx[0, np.newaxis]), axis=0) + v = np.concatenate( + (v, vtx, np.atleast_2d([np.nan, np.nan])), axis=0) + return v + + if len(dims) == 2: + pars['coordinates'] = get_slice_coords(Bmat) + else: + # make a list of the contour coordinates for each 2D slice + pars['coordinates'] = [] + if slice_dim is None: + slice_dim = 0 if swap_dim else -1 + for s in range(dims[slice_dim]): + B = Bmat.take(s, axis=slice_dim) + pars['coordinates'].append(get_slice_coords(B)) + + pars['CoM'] = np.squeeze(cm[i, :]) + pars['neuron_id'] = i + 1 + coordinates.append(pars) + return coordinates