def compute_spharm(self, grid_size=None, normalize=False, normalization_method='zero-component', ri=False): """ Compute the spherical harmonics spectrum of the current surface. Parameters ---------- grid_size : int, optional Dimension of the square grid to interpolate the surface points. Will be used to interpolate the surface coordinates if self.Rgrid is None (in this case it is a mandatory parameter). Default is None. normalize : bool, optional If True, the values of the spectrum will be normalized according to the `normalization_method`. Default is False. normalization_method : str, optional If 'mean-radius', the grid values will be divided by the mean grid value prior to the SPHARM transform. If 'zero-component', all spectral components will be divided by the value of the first component (m=0, n=0). Default is 'zero-component'. ri : bool, optional If True, rotation-invariant spectrum based on Cartesian coordinates is computed. Default is False. Returns ------- Spectrum : spherical harmonics spectrum of the surface. """ self.spharm = Spectrum() if ri: data = [] for r in [self.x, self.y, self.z]: grid = self.interpolate(grid_size=grid_size, r=r) self.spharm.from_surface(surface=grid, normalize=normalize) data.append(self.spharm.harmonics_csv) for c in ['value', 'amplitude', 'power', 'real', 'imag']: self.spharm.harmonics_csv[c] = np.sqrt(data[0][c]**2 + data[1][c]**2 + data[2][c]**2) self.spharm.harmonics_csv['amplitude2'] = np.abs( self.spharm.harmonics_csv['value']) self.spharm.convert_to_shtools_array() else: if self.Rgrid is None: if grid_size is None: raise TypeError( 'Grid size for interpolation must be provided') else: self.Rgrid = self.interpolate(grid_size=grid_size) if self.Rgrid is None: print(len(self.x), grid_size) self.spharm.from_surface(surface=self.Rgrid, normalize=normalize, normalization_method=normalization_method) self.spharm.metadata = self.metadata return self.spharm
def test_heatmap(self): sp = Spectrum() sp.from_surface(surface=np.ones([10, 10])) pl = sp.heatmap() os.makedirs('data/test_data') pl.savefig('data/test_data/heatmap.png') self.assertEqual(os.path.exists('data/test_data/heatmap.png'), True) shutil.rmtree('data/test_data/')
def plot_mean_abs_derivative(inputfile, outputfolder, group='Group', cutoff=None, id_col='TrackID'): """ Plot the derivative of each spectral component of different groups over time. Parameters ---------- inputfile : str Path to the file with spectral data. outputfolder : str Directory to save the plotted derivative. group : str, optional Column in the input data sheet to use for grouping. Default is 'Group'. cutoff : int, optional The number of degrees to display. If None, all degrees will be displayed. id_col : str Column in the input data sheet to group connected time points. Default is 'TrackID' """ filelib.make_folders([os.path.dirname(outputfolder)]) if not os.path.exists(inputfile[:-4] + '_mean_abs_derivative.csv'): stat = pd.read_csv(inputfile, sep='\t', index_col=0) if id_col == 'CellID': stat.loc[:, 'Time'] = np.int_(np.round_(stat['Time'] / 10.)) * 10 nstat = pd.DataFrame() if cutoff is not None: stat = stat[stat['degree'] <= cutoff] for gr in stat[group].unique(): substat = stat[stat[group] == gr] for id in substat[id_col].unique(): subsubstat = substat[substat[id_col] == id] subsubstat = subsubstat.sort_values('Time') time_spectrum = TimeSpectrum() for t in subsubstat['Time'].unique(): sp = Spectrum() sp.harmonics_csv = subsubstat[subsubstat['Time'] == t] time_spectrum.add_spectrum(sp, timepoint=t) time_spectrum.compute_derivative() meanderivstat = time_spectrum.mean_abs_derivative meanderivstat['Group'] = gr meanderivstat['TrackID'] = id nstat = pd.concat([nstat, meanderivstat], ignore_index=True) nstat.to_csv(inputfile[:-4] + '_mean_abs_derivative.csv', sep='\t') nstat = pd.read_csv(inputfile[:-4] + '_mean_abs_derivative.csv', sep='\t', index_col=0) nstat = nstat.sort_values(['harmonic', group]) plt.clf() plt.figure(figsize=(20, 5)) sns.barplot(x='harmonic', y='absolute amplitude', data=nstat, hue=group) plt.ylabel('Mean absolute derivative of amplitude') labels = nstat['harmonic'].unique() plt.xticks(np.arange(len(labels)) + 0.6, labels, rotation='vertical') margins = {'left': 0.07, 'right': 0.98, 'top': 0.93, 'bottom': 0.25} plt.subplots_adjust(**margins) plt.savefig(outputfolder + 'mean_abs_derivative.png') plt.close()
def test_frequency_plot(self): sp = Spectrum(name='Example 1') sp.from_surface(surface=np.ones([10, 10])) pl = sp.frequency_plot() os.makedirs('data/test_data') pl.savefig('data/test_data/frequency_plot.png') self.assertEqual(os.path.exists('data/test_data/frequency_plot.png'), True) shutil.rmtree('data/test_data/')
def plot_average_frequency_heatmaps(inputfile, outputfolder, group='Group', cutoff=None, logscale=False, id_col='TrackID'): """ Plot the Fourier frequencies of spectral components of different groups as a heatmap. Parameters ---------- inputfile : str Path to the file with spectral data. outputfolder : str Directory to save the plotted heat maps. group : str, optional Column in the input data sheet to use for grouping. Default is 'Group'. cutoff : int, optional The number of degrees to display. If None, all degrees will be displayed. logscale : bool, optional If True, the natural logarithm of the value will be displayed. Default is False. id_col : str Column in the input data sheet to group connected time points. Default is 'TrackID' """ filelib.make_folders([os.path.dirname(outputfolder)]) stat = pd.read_csv(inputfile, sep='\t', index_col=0) stat.loc[:, 'Time'] = np.int_(np.round_(stat['Time'] / 10.)) * 10 if cutoff is not None: stat = stat[stat['degree'] <= cutoff] frequency_stat = pd.DataFrame() for gr in stat[group].unique(): substat = stat[stat[group] == gr] for id in substat[id_col].unique(): subsubstat = substat[substat[id_col] == id] time_spectrum = TimeSpectrum() for t in subsubstat['Time'].unique(): sp = Spectrum() sp.harmonics_csv = subsubstat[subsubstat['Time'] == t] time_spectrum.add_spectrum(sp, timepoint=t) time_spectrum.fourier_analysis(value='amplitude') time_spectrum.frequencies['Group'] = gr frequency_stat = pd.concat([frequency_stat, time_spectrum.frequencies], ignore_index=True) frequency_stat = frequency_stat.groupby(['Group', 'frequency', 'harmonic']).mean().reset_index() for gr in stat[group].unique(): time_spectrum = TimeSpectrum() time_spectrum.frequencies = frequency_stat[frequency_stat['Group'] == gr] pl = time_spectrum.frequency_heatmap(value='amplitude', logscale=logscale) if pl is not None: pl.savefig(outputfolder + gr + '.png')
def test_saving(self): sp = Spectrum() sp.from_surface(surface=np.ones([10, 10])) sp.convert_to_csv() sp.save_to_csv(filename='data/test_data/spectrum.csv') sp2 = Spectrum(filename='data/test_data/spectrum.csv') self.assertAlmostEqual( np.sum(abs(sp.harmonics_shtools - sp2.harmonics_shtools)), 0, 15) shutil.rmtree('data/test_data/')
def plot_individual_time_heatmaps(inputfile, outputfolder, group='Group', cutoff=None, logscale=False, id_col='TrackID'): """ Plot the amplitude of spectral components over time for different groups as a heatmap. Parameters ---------- inputfile : str Path to the file with spectral data. outputfolder : str Directory to save the plotted heat maps. group : str, optional Column in the input data sheet to use for grouping. Default is 'Group'. cutoff : int, optional The number of degrees to display. If None, all degrees will be displayed. logscale : bool, optional If True, the natural logarithm of the value will be displayed. Default is False. id_col : str Column in the input data sheet to group connected time points. Default is 'TrackID' """ filelib.make_folders([os.path.dirname(outputfolder)]) stat = pd.read_csv(inputfile, sep='\t', index_col=0) stat['Time'] = np.int_(np.round_(stat['Time'] / 10.)) * 10 if cutoff is not None: stat = stat[stat['degree'] <= cutoff] for gr in stat[group].unique(): substat = stat[stat[group] == gr] for id in substat[id_col].unique(): subsubstat = substat[substat[id_col] == id] subsubstat = subsubstat.sort_values('Time').reset_index() time_spectrum = TimeSpectrum() for t in subsubstat['Time'].unique(): sp = Spectrum() sp.harmonics_csv = subsubstat[subsubstat['Time'] == t] time_spectrum.add_spectrum(sp, timepoint=t) pl = time_spectrum.time_heatmap(value='amplitude', logscale=logscale) if pl is not None: pl.savefig(outputfolder + '_' + gr + '_' + 'track_' + str(id) + '.png')
def plot_inverse_shapes(inputfile, outputfolder, group='Group'): """ Plot average cells shapes obtained by inverse SPHARM. Parameters ---------- inputfile : str Path to the file with spectral data. outputfolder : str Directory to save the plotted distributions. group : str, optional Column in the input data sheet to use for grouping. Default is 'Group'. """ filelib.make_folders([os.path.dirname(outputfolder)]) stat = pd.read_csv(inputfile, sep='\t', index_col=0) stat['value'] = stat['real'] + stat['imag']*1j if 'Group' not in stat.columns: for name in stat['Name'].unique(): group = name.split('/')[0] stat = stat.set_value(stat[stat['Name'] == name].index, 'Group', group) data = stat.groupby(['degree', 'order', 'Group']).mean().reset_index() groups = data[group].unique() for gr in groups: curdata = data[data[group] == gr] sp = Spectrum() sp.harmonics_csv = curdata sp.convert_to_shtools_array() surf = Surface() surf.spharm = sp maxdegree = np.max(sp.harmonics_csv['degree']) for lmax in np.arange(5, maxdegree + 1, 5): surf.inverse_spharm(lmax=lmax) surf.plot_surface(points=False).save(outputfolder + '_' + gr + '_inverse_lmax=' + str(lmax) + '.png', size=(200, 200)) surf.inverse_spharm(lmax=None) surf.plot_surface(points=False).save(outputfolder + '_' + gr + '_inverse_full.png', size=(200, 200))
def plot_spectra(inputfolder, outputfolder, **kwargs): """ Plot bar plots for individual frequency spectra in a given directory. Parameters ---------- inputfolder : str Input directory with spectra to plot. outputfolder : str Output directory to save the bar plots. kwargs : key, value pairings Arbitrary keyword arguments to pass to the Spectrum.frequency_plot function. """ files = filelib.list_subfolders(inputfolder, extensions=['csv']) for fn in files: s = Spectrum(filename=inputfolder + fn) pl = s.frequency_plot(title=fn[:-4], **kwargs) filelib.make_folders([os.path.dirname(outputfolder + fn[:-4])]) pl.savefig(outputfolder + fn[:-4] + '.png') pl.clf()
def plot_average_heatmaps(inputfile, outputfolder, **kwargs): """ Plot heatmaps for group-averaged SPHARM spectra. Parameters ---------- inputfile : str Path to the file with spectral data. outputfolder : str Output directory to save the heatmaps. kwargs : key, value pairings Arbitrary keyword arguments to pass to the Spectrum.heatmap function. """ filelib.make_folders([os.path.dirname(outputfolder), outputfolder + 'timepoints/']) stat = pd.read_csv(inputfile, sep='\t', index_col=0) stat.loc[:, 'Time'] = np.int_(np.round_(stat['Time'] / 10.)) * 10 if 'Group' not in stat.columns: for name in stat['Name'].unique(): group = name.split('/')[0] stat = stat.set_value(stat[stat['Name'] == name].index, 'Group', group) data = stat.groupby(['degree', 'order', 'Group']).mean().reset_index() for gr in data['Group'].unique(): curdata = data[data['Group'] == gr] s = Spectrum() s.harmonics_csv = curdata pl = s.heatmap(title=gr + ' average', **kwargs) pl.savefig(outputfolder + gr + '.png') pl.clf() # plot separate time points stat = stat.groupby(['Time', 'Group', 'degree', 'order']).mean().reset_index() for t in stat['Time'].unique(): for gr in stat['Group'].unique(): curdata = stat[(stat['Group'] == gr) & (stat['Time'] == t)] if len(curdata) > 0: s = Spectrum() s.harmonics_csv = curdata pl = s.heatmap(title=gr + ' average, time point ' + str(t), **kwargs) pl.savefig(outputfolder + 'timepoints/' + gr + '_time=' + str(t) + '.png') pl.clf()
def test_fourier(self): sp = Spectrum() sp.from_surface(surface=np.ones([10, 10])) sp.convert_to_csv() tsp = TimeSpectrum() for i in range(10): tsp.add_spectrum(sp) tsp.fourier_analysis() tsp.save_frequencies_to_csv('data/test_data/time_spectrum_freq.csv') self.assertEqual(os.path.exists('data/test_data/time_spectrum_freq.csv'), True) pl = tsp.frequency_heatmap() pl.savefig('data/test_data/frequency_heatmap.png') self.assertEqual(os.path.exists('data/test_data/frequency_heatmap.png'), True) shutil.rmtree('data/test_data/')
def test_from_surface(self, case): sp = Spectrum() harm = sp.from_surface(surface=case) self.assertEqual(tuple(harm.shape), (2, case.shape[0] / 2, case.shape[0] / 2))
def test_feature_vector(self): surf = np.ones([10, 10]) sp = Spectrum() sp.from_surface(surface=surf) sp.convert_to_csv() surf[3:4, 4:8] = 10 sp2 = Spectrum() sp2.from_surface(surface=surf) sp2.convert_to_csv() tsp = TimeSpectrum() tsp.add_spectrum(sp) tsp.add_spectrum(sp2) tsp.add_spectrum(sp) tsp.compute_derivative() self.assertEqual(len(tsp.return_feature_vector(cutoff=2)), 3)
def test_frequency_spectrum(self): sp = Spectrum() sp.from_surface(surface=np.ones([10, 10])) sp.compute_frequency_spectrum() self.assertEqual(len(sp.frequency_spectrum), 5)
def test_convertion(self): sp = Spectrum() harm_shtools = sp.from_surface(surface=np.ones([10, 10])) sp.convert_to_csv() harm_shtools2 = sp.convert_to_shtools_array() self.assertEqual(np.sum(abs(harm_shtools - harm_shtools2)), 0)
def test_from_surface_norm2(self, case): sp = Spectrum() harm = sp.from_surface(surface=case, normalize=True, normalization_method='mean-radius') self.assertAlmostEqual(abs(np.max(harm)), 1, 8)
class Surface(object): """ Class for a surface grid of an object """ def __init__(self, filename=None, grid=None, data=None, **kwargs): """ Initialize a surface from file or grid. Parameters ---------- filename : str, optional Path to a surface file to read the surface data. If None, an empty surface will be initialized. Default is None. grid : numpy.ndarray, dimension (n, n) or (n, 2*n), n is even, optional A 2D equally sampled (default) or equally spaced complex grid that conforms to the sampling theorem of Driscoll and Healy (1994). The first latitudinal band corresponds to 90 N, the latitudinal band for 90 S is not included, and the latitudinal sampling interval is 180/n degrees. The first longitudinal band is 0 E, the longitude band for 360 E is not included, and the longitudinal sampling interval is 360/n for an equally and 180/n for an equally spaced grid, respectively. data : pandas DataFrame, optional DataFrame with surface coordinates. If None, an empty surface will be initialized. Default is None. kwargs : key, value pairings Arbitrary keyword arguments to pass to the self.read_from_file function. """ self.X = None # grid self.Y = None # grid self.Z = None # grid self.Phi = None # grid self.Theta = None # grid self.Rgrid = grid # grid self.x = None # list of points self.y = None # list of points self.z = None # list of points self.R = None # list of points self.phi = None # list of points self.theta = None # list of points self.filename = filename self.spharm = None self.metadata = pd.Series() self.center = None self.migration_angles = None if grid is None: if filename is not None: self.read_from_file(filename, voxel_size=kwargs.get('voxel_size', 1)) elif data is not None: self.from_dataframe(data) if self.Rgrid is not None: self.R = self.Rgrid.flatten() self.Theta = np.linspace(0, np.pi, self.Rgrid.shape[0], endpoint=False) self.Phi = np.linspace(0, 2 * np.pi, self.Rgrid.shape[0], endpoint=False) self.Phi, self.Theta = np.meshgrid(self.Phi, self.Theta) self.phi = self.Phi.flatten() self.theta = self.Theta.flatten() self.x, self.y, self.z = tr.spherical_to_cart( self.R, self.theta, self.phi) def read_from_file(self, filename, voxel_size=1): """ Read surface coordinates from file. Parameters ---------- filename : str, optional Path to a surface file to read the surface data. voxel_size : scalar or sequence of scalars, optional Voxel size of the image. Specified either by individual value for each axis, or by one value for all axes. Default is 1. """ if os.path.exists(filename): f = open(filename) st = f.readlines() f.close() if len(st) > 0: stat = pd.read_csv(filename, sep='\t', index_col=0) if 'X' in stat.columns and 'Y' in stat.columns and 'Z' in stat.columns: self.x = np.array(stat.X) self.y = np.array(stat.Y) self.z = np.array(stat.Z) else: stat = pd.read_csv(filename, sep=',', header=None) px = voxel_size if 0 in stat.columns and 1 in stat.columns and 2 in stat.columns: self.x = np.array(stat[0]) * px self.y = np.array(stat[1]) * px self.z = np.array(stat[2]) * px self.to_spherical() self.metadata['Name'] = filename p = re.compile('[-+]?\d*\.*\d+') if 'Time' in stat.columns: self.metadata['Time'] = stat['Time'].iloc[0] elif len(filename.split('Time')) > 1: self.metadata['Time'] = float( p.findall(filename.split('Time')[-1])[0]) else: num = p.findall(filename) if len(num) > 0: self.metadata['Time'] = float(num[-1]) else: self.metadata['Time'] = 0 if 'TrackID' in stat.columns: self.metadata['TrackID'] = stat['TrackID'].iloc[0] elif len(filename.split('Cell')) > 1: self.metadata['TrackID'] = float( p.findall(filename.split('cells')[-1])[0]) else: self.metadata['TrackID'] = 0 def from_dataframe(self, stat): """ Get surface coordinates from a pandas DataFrame. Parameters ---------- stat : pandas DataFrame, optional DataFrame with surface coordinates. """ self.x = np.array(stat.X) self.y = np.array(stat.Y) self.z = np.array(stat.Z) self.metadata['Time'] = stat['Time'].iloc[0] def save(self, filename): """ Save the surface coordinates to a csv file. Parameters ---------- filename : str Output file name. """ if self.x is not None: filelib.make_folders([os.path.dirname(filename)]) stat = pd.DataFrame({'X': self.x, 'Y': self.y, 'Z': self.z}) stat['Name'] = self.filename stat.to_csv(filename, sep='\t') def save_as_stack(self, filename, voxel_size): """ Save the surface as a 3D stack. Parameters ---------- filename : str Output file name. voxel_size : scalar or sequence of scalars Voxel size of the image. Specified either by individual value for each axis, or by one value for all axes. """ if self.x is not None: voxel_size = np.array([voxel_size]).flatten() if len(voxel_size) == 1: voxel_size = np.ones(3) * voxel_size filelib.make_folders([os.path.dirname(filename)]) img = self.as_stack(voxel_size) with warnings.catch_warnings(): warnings.simplefilter("ignore") io.imsave(filename, img.astype(np.uint8)) metadata = pd.Series({ 'voxel_size_xy': voxel_size[2], 'voxel_size_z': voxel_size[0] }) metadata['min_x'] = self.x.min() - 1 metadata['min_y'] = self.y.min() - 1 metadata['min_z'] = self.z.min() - 1 metadata.to_csv(filename[:-4] + '.txt', sep='\t', header=False) def as_stack(self, voxel_size, minmax=None): """ Convert the surface to a 3D image stack. Parameters ---------- voxel_size : scalar or sequence of scalars Voxel size of the image. Specified either by individual value for each axis, or by one value for all axes. minmax : ndarray, optional Boundaries to crop the image stack of the form [[z_min, z_max], [y_min, y_max], [x_min, x_max]]. If None, set to the minimal and maximal given coordinates. Default is None. Returns ------- ndimage : 3D binary image with surface point as foreground. """ if minmax is not None: minmax = [ minmax[0] / voxel_size[0], minmax[1] / voxel_size[1], minmax[2] / voxel_size[2] ] img = tr.as_stack(np.array(self.x) / voxel_size[2], np.array(self.y) / voxel_size[1], np.array(self.z) / voxel_size[0], minmax=minmax) return img def centrate(self): """ Substract the mean coordinate form each coordinate value. """ self.center = np.array( [np.mean(self.x), np.mean(self.y), np.mean(self.z)]) self.x = self.x - np.mean(self.x) self.y = self.y - np.mean(self.y) self.z = self.z - np.mean(self.z) def to_spherical(self): """ Convert coordinates from Cartesian to spherical. """ self.R, self.theta, self.phi = tr.cart_to_spherical( self.x, self.y, self.z) def rotate(self, theta, phi): """ Rotate the coordinates of the surface by given asimuthal and polar angles. Parameters ---------- theta : float Polar angle. phi : float Azimuthal angle. """ self.x, self.y, self.z = tr.rotate_spherical(self.x, self.y, self.z, theta, phi) def interpolate(self, grid_size, r=None): """ Interpolate the surface points on a regular grid. Parameters ---------- grid_size : int Dimension of the square grid to interpolate the surface points. r : array, optional The array of values to interpolate. If None, the self.R (radius) will be interpolated. Default is None. Returns ------- ndarray of size (grid_size x grid_size) : interpolated grid. """ R, theta, phi = (self.R, self.theta, self.phi) if r is None: r = R if len(r) > 4: # make a lattice I = np.linspace(0, np.pi, grid_size, endpoint=False) J = np.linspace(0, 2 * np.pi, grid_size, endpoint=False) J, I = np.meshgrid(J, I) # make a list of shape points (theta and phi angles) and values (radius) values = r points = np.array([theta, phi]).transpose() # add 0 and pi to theta points = np.concatenate( (points, np.array([[0, 0], [0, 2 * np.pi], [np.pi, 0], [np.pi, 2 * np.pi]])), axis=0) rmin = np.mean(r[np.where(theta == theta.min())]) rmax = np.mean(r[np.where(theta == theta.max())]) values = np.concatenate( (values, np.array([rmin, rmin, rmax, rmax])), axis=0) # add shape points shifted to the left and right in the longitude dimension, to fill the edges points = np.concatenate( (points, points - [0, 2 * np.pi], points + [0, 2 * np.pi]), axis=0) values = np.concatenate((values, values, values), axis=0) # make list of lattice points xi = np.asarray([[I[i, j], J[i, j]] for i in range(len(I)) for j in range(len(I[0]))]) # interpolate the shape points on the lattice grid = griddata(points, values, xi, method='linear') grid = grid.reshape((grid_size, grid_size)) else: grid = None return grid def plot_points(self, scale_factor=0.1): """ Plot a 3D view of the surface points with mayavi. Returns ------- mayavi scene """ with warnings.catch_warnings(): warnings.simplefilter("ignore") mesh = mlab.points3d(self.x, self.y, self.z, self.z, scale_mode='none', scale_factor=scale_factor, mode='sphere', colormap='gray').scene mesh.background = (1, 1, 1) mesh.magnification = 10 return mesh def plot_surface(self, points=False, extent=None): """ Plot a 3D view of the surface grid with mayavi. Parameters ---------- points : bool, optional If True, surface points will be displayed. Default is False. extent : [xmin, xmax, ymin, ymax, zmin, zmax], optional Minimal and maximal coordinates to display. Default is the x, y, z arrays extent. Returns ------- mayavi scene """ if self.Rgrid is not None: grid_size = self.Rgrid.shape[0] I = np.linspace(0, np.pi, grid_size + 1, endpoint=True) J = np.linspace(0, 2 * np.pi, grid_size + 1, endpoint=True) J, I = np.meshgrid(J, I) theta = I phi = J grid = np.zeros((grid_size + 1, grid_size + 1)) grid[:-1, :-1] = self.Rgrid grid[-1] = grid[0] grid[:, -1] = grid[:, 0] x, y, z = tr.spherical_to_cart(1, theta, phi) mlab.clf() if extent is not None: mesh = mlab.mesh(x * np.abs(grid), y * np.abs(grid), z * np.abs(grid), scalars=grid, colormap='jet', extent=extent) else: mesh = mlab.mesh(x * np.abs(grid), y * np.abs(grid), z * np.abs(grid), scalars=grid, colormap='jet') if points: mesh = mlab.points3d(self.x, self.y, self.z, self.z, scale_mode='none', scale_factor=0.05) mesh.scene.background = (1, 1, 1) mesh.scene.magnification = 10 return mesh.scene def compute_spharm(self, grid_size=None, normalize=False, normalization_method='zero-component', ri=False): """ Compute the spherical harmonics spectrum of the current surface. Parameters ---------- grid_size : int, optional Dimension of the square grid to interpolate the surface points. Will be used to interpolate the surface coordinates if self.Rgrid is None (in this case it is a mandatory parameter). Default is None. normalize : bool, optional If True, the values of the spectrum will be normalized according to the `normalization_method`. Default is False. normalization_method : str, optional If 'mean-radius', the grid values will be divided by the mean grid value prior to the SPHARM transform. If 'zero-component', all spectral components will be divided by the value of the first component (m=0, n=0). Default is 'zero-component'. ri : bool, optional If True, rotation-invariant spectrum based on Cartesian coordinates is computed. Default is False. Returns ------- Spectrum : spherical harmonics spectrum of the surface. """ self.spharm = Spectrum() if ri: data = [] for r in [self.x, self.y, self.z]: grid = self.interpolate(grid_size=grid_size, r=r) self.spharm.from_surface(surface=grid, normalize=normalize) data.append(self.spharm.harmonics_csv) for c in ['value', 'amplitude', 'power', 'real', 'imag']: self.spharm.harmonics_csv[c] = np.sqrt(data[0][c]**2 + data[1][c]**2 + data[2][c]**2) self.spharm.harmonics_csv['amplitude2'] = np.abs( self.spharm.harmonics_csv['value']) self.spharm.convert_to_shtools_array() else: if self.Rgrid is None: if grid_size is None: raise TypeError( 'Grid size for interpolation must be provided') else: self.Rgrid = self.interpolate(grid_size=grid_size) if self.Rgrid is None: print(len(self.x), grid_size) self.spharm.from_surface(surface=self.Rgrid, normalize=normalize, normalization_method=normalization_method) self.spharm.metadata = self.metadata return self.spharm def inverse_spharm(self, lmax=None): """ Inverse transform the SPHARM spectrum to surface using the given number of components. Parameters ---------- lmax : int, optional The maximum spherical harmonic degree to be used in the inverse transform. If None, all degrees will be used. Default is None. Returns ------- ndarray : reconstructed surface grid. """ self.Rgrid = self.spharm.spharm_to_surface(lmax=lmax) return self.Rgrid
def test_inverse_transform(self): sp = Spectrum() surf = np.ones([10, 10]) sp.from_surface(surface=surf) grid = sp.spharm_to_surface() self.assertAlmostEqual(np.sum(np.abs(surf - grid)), 0, 7)
def test_feature_vector(self): sp = Spectrum() surf = np.ones([10, 10]) sp.from_surface(surface=surf) self.assertEqual(len(sp.return_feature_vector(cutoff=3)), 4)
def test_from_surface_errors(self, case): sp = Spectrum() self.assertRaises(ValueError, sp.from_surface, surface=case)
def test_from_surface_norm(self, case): sp = Spectrum() harm = sp.from_surface(surface=case, normalize=True, normalization_method='zero-component') self.assertEqual(np.max(harm), 1)
def test_add_spectrum_and_plotting(self): tsp = TimeSpectrum() sp = Spectrum() sp.from_surface(surface=np.ones([10, 10])) sp.convert_to_csv() for i in range(3): tsp.add_spectrum(sp, timepoint=i*20) sp = Spectrum() surf = np.ones([10, 10]) surf[3:4, 4:8] = 10 sp.from_surface(surface=surf) sp.convert_to_csv() for i in range(2): tsp.add_spectrum(sp, timepoint=i*20+60) sp = Spectrum() sp.from_surface(surface=np.ones([10, 10])) sp.convert_to_csv() for i in range(3): tsp.add_spectrum(sp, timepoint=i*20+100) self.assertEqual(len(tsp.spectra), 8) tsp.save_to_csv('data/test_data/time_spectrum.csv') self.assertEqual(os.path.exists('data/test_data/time_spectrum.csv'), True) pl = tsp.time_heatmap() pl.savefig('data/test_data/time_heatmap.png') self.assertEqual(os.path.exists('data/test_data/time_heatmap.png'), True) tsp.compute_derivative() pl = tsp.derivative_heatmap() pl.savefig('data/test_data/derivative_heatmap.png') self.assertEqual(os.path.exists('data/test_data/derivative_heatmap.png'), True) pl = tsp.plot_mean_abs_derivative() pl.savefig('data/test_data/mean_abs_derivative.png') self.assertEqual(os.path.exists('data/test_data/mean_abs_derivative.png'), True) shutil.rmtree('data/test_data/')