import plotstyle_plt import numpy as np import matplotlib.pyplot as plt from itertools import product from thunderhopper.filetools import search_files from thunderhopper.modeltools import load_data from thunderhopper.filtertools import find_kern_specs from misc_functions import get_saturation, reduce_kernel_set, exclude_zero_scale,\ divide_by_zero, x_dist, y_dist from color_functions import load_colors from plot_functions import hide_axis, reorder_by_sd, ylimits, super_xlabel, ylabel, title_subplot,\ plot_line, strip_zeros, time_bar, assign_colors,\ letter_subplot, letter_subplots, hide_ticks from IPython import embed def plot_snippets(axes, time, snippets, ymin=None, ymax=None, **kwargs): ymin, ymax = ylimits(snippets, minval=ymin, maxval=ymax, pad=0.05) handles = [] for i, ax in enumerate(axes): handles.append(plot_line(ax, time, snippets[:, ..., i], ymin=ymin, ymax=ymax, **kwargs)) return handles def plot_curves(ax, scales, measures, **kwargs): if measures.ndim == 1: handles = ax.plot(scales, measures, **kwargs) return handles, measures median_measure = np.nanmedian(measures, axis=1) line_handle = ax.plot(scales, median_measure, **kwargs)[0] return line_handle, median_measure def crop_noise_snippets(snippets, nin, nout, stages=['filt', 'env', 'log', 'inv', 'conv', 'feat']): half_offset = int((nin - nout) / 2) segment = np.arange(half_offset, half_offset + nout) for stage in stages: key = f'snip_{stage}' snippets[key] = snippets[key][segment, ...] return snippets # GENERAL SETTINGS: search_target = 'Pseudochorthippus_parallelus' stages = ['filt', 'env', 'log', 'inv', 'conv', 'feat'] song_example = 'Pseudochorthippus_parallelus_micarray-short_JJ_20240815T160355-20240815T160755-1m10s690ms-1m13s614ms' noise_example = 'merged_noise' song_path = '../data/inv/field/song/' noise_path = '../data/inv/field/noise/' raw_path = search_files(search_target, incl='unnormed', dir=song_path + 'condensed/')[0] base_path = search_files(search_target, incl='base', dir=song_path + 'condensed/')[0] range_path = search_files(search_target, incl='range', dir=song_path + 'condensed/')[0] song_snip_path = search_files(song_example, dir=song_path)[0] noise_snip_path = search_files(noise_example, dir=noise_path)[0] save_path = '../figures/fig_invariance_field.pdf' # ANALYSIS SETTINGS: offset_distance = 10 # centimeter # SUBSET SETTINGS: types = np.array([1, -1, 2, -2, 3, -3, 4, -4]) # types = [1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7, 8, -8, 9, -9, 10, -10] sigmas = np.array([0.001, 0.002, 0.004, 0.008, 0.016]) # sigmas = [0.001, 0.002, 0.004, 0.008, 0.016, 0.032] kernels = None reduce_kernels = any(var is not None for var in [kernels, types, sigmas]) # GRAPH SETTINGS: fig_kwargs = dict( figsize=(32/2.54, 32/2.54), ) super_grid_kwargs = dict( nrows=2, ncols=1, wspace=0, hspace=0, left=0, right=1, bottom=0, top=1, height_ratios=[3, 2] ) subfig_specs = dict( snip=(0, 0), big=(1, 0), ) snip_grid_kwargs = dict( nrows=len(stages), ncols=None, wspace=0.1, hspace=0.4, left=0.11, right=0.98, bottom=0.08, top=0.95 ) big_grid_kwargs = dict( nrows=1, ncols=3, wspace=0.4, hspace=0, left=snip_grid_kwargs['left'], right=snip_grid_kwargs['right'], bottom=0.13, top=0.98 ) # PLOT SETTINGS: fs = dict( lab_norm=16, lab_tex=20, letter=22, tit_norm=16, tit_tex=20, bar=16, ) colors = load_colors('../data/stage_colors.npz') conv_colors = load_colors('../data/conv_colors_all.npz') feat_colors = load_colors('../data/feat_colors_all.npz') lw = dict( filt=0.25, env=0.25, log=0.25, inv=0.25, conv=0.25, feat=1, big=3, plateau=1.5, ) xlabels = dict( big='distance [cm]', ) ylabels = dict( filt='$x_{\\text{filt}}$\n$[\\text{a.u.}]$', env='$x_{\\text{env}}$\n$[\\text{a.u.}]$', log='$x_{\\text{log}}$\n$[\\text{dB}]$', inv='$x_{\\text{adapt}}$\n$[\\text{dB}]$', conv='$c_i$\n$[\\text{dB}]$', feat='$f_i$', big=['measure', 'rel. measure', 'norm. measure'] ) xlab_big_kwargs = dict( y=0, fontsize=fs['lab_norm'], ha='center', va='bottom', ) ylab_snip_kwargs = dict( x=0, fontsize=fs['lab_tex'], rotation=0, ha='left', va='center' ) ylab_big_kwargs = dict( x=-0.2, fontsize=fs['lab_norm'], ha='center', va='bottom', ) yloc = dict( filt=0.03, env=0.01, log=50, inv=20, conv=1, feat=1, ) title_kwargs = dict( x=0.5, yref=1, ha='center', va='top', fontsize=fs['tit_norm'], ) letter_snip_kwargs = dict( x=0, yref=0.5, ha='left', va='center', fontsize=fs['letter'], ) letter_big_kwargs = dict( x=0, y=1, ha='left', va='bottom', fontsize=fs['letter'], ) song_bar_time = 1 song_bar_kwargs = dict( dur=song_bar_time, y0=-0.25, y1=-0.1, xshift=1, color='k', lw=0, clip_on=False, text_pos=(-0.1, 0.5), text_str=f'${song_bar_time}\\,\\text{{s}}$', text_kwargs=dict( fontsize=fs['bar'], ha='right', va='center', ) ) noise_bar_time = 0.5 noise_bar_kwargs = song_bar_kwargs.copy() noise_bar_kwargs['dur'] = noise_bar_time noise_bar_kwargs['text_str'] = f'${int(1000 * noise_bar_time)}\\,\\text{{ms}}$' plateau_settings = dict( low=0.05, high=0.95, first=True, last=True, condense=None, ) plateau_line_kwargs = dict( lw=lw['plateau'], ls='--', zorder=1, ) plateau_dot_kwargs = dict( marker='o', markersize=8, markeredgewidth=1, clip_on=False, ) # EXECUTION: # Load raw (unnormed) invariance data: data, config = load_data(raw_path, files='distances', keywords='mean') dists = data['distances'] + offset_distance # Load snippet data: song_snip, _ = load_data(song_snip_path, keywords='snip') t_song = np.arange(song_snip['snip_filt'].shape[0]) / config['rate'] noise_snip, _ = load_data(noise_snip_path, keywords='snip') noise_snip = crop_noise_snippets(noise_snip, noise_snip['snip_filt'].shape[0], t_song.size) t_noise = np.arange(noise_snip['snip_filt'].shape[0]) / config['rate'] snip_dists = ['noise'] + [f'{int(d)}$\\,$cm' for d in dists] # Optional kernel subset: reduce_kernels = False if any(var is not None for var in [kernels, types, sigmas]): kern_inds = find_kern_specs(config['k_specs'], kernels, types, sigmas) data = reduce_kernel_set(data, kern_inds, keyword='mean') song_snip = reduce_kernel_set(song_snip, kern_inds, keyword='snip') noise_snip = reduce_kernel_set(noise_snip, kern_inds, keyword='snip') config['k_specs'] = config['k_specs'][kern_inds, :] config['kernels'] = config['kernels'][:, kern_inds] reduce_kernels = True # Adjust grid parameters: snip_grid_kwargs['ncols'] = len(snip_dists) # Prepare overall graph: fig = plt.figure(**fig_kwargs) super_grid = fig.add_gridspec(**super_grid_kwargs) # Prepare stage-specific snippet axes: snip_subfig = fig.add_subfigure(super_grid[subfig_specs['snip']]) snip_grid = snip_subfig.add_gridspec(**snip_grid_kwargs) snip_axes = np.zeros((snip_grid.nrows, snip_grid.ncols), dtype=object) for i, j in product(range(snip_grid.nrows), range(snip_grid.ncols)): ax = snip_subfig.add_subplot(snip_grid[i, j]) ax.yaxis.set_major_locator(plt.MultipleLocator(yloc[stages[i]])) hide_axis(ax, 'bottom') if i == 0: title = title_subplot(ax, snip_dists[j], ref=snip_subfig, **title_kwargs) if j == 0: ax.set_xlim(t_noise[0], t_noise[-1]) ylabel(ax, ylabels[stages[i]], **ylab_snip_kwargs, transform=snip_subfig.transSubfigure) else: ax.set_xlim(t_song[0], t_song[-1]) hide_axis(ax, 'left') snip_axes[i, j] = ax time_bar(snip_axes[-1, -1], **song_bar_kwargs) # time_bar(snip_axes[-1, 0], **noise_bar_kwargs) letter_subplot(snip_subfig, 'a', ref=title, **letter_snip_kwargs) # Prepare analysis axes: big_subfig = fig.add_subfigure(super_grid[subfig_specs['big']]) big_grid = big_subfig.add_gridspec(**big_grid_kwargs) big_axes = np.zeros((big_grid.ncols,), dtype=object) for i in range(big_grid.ncols): ax = big_subfig.add_subplot(big_grid[0, i]) ax.set_xlim(dists[0], 0) # ax.set_xscale('symlog', linthresh=offset_distance, linscale=0.5) ax.set_yscale('symlog', linthresh=0.01, linscale=0.1) ylabel(ax, ylabels['big'][i], **ylab_big_kwargs) # if i < (big_grid.ncols - 1): # ax.set_ylim(scales[0], scales[-1]) # else: # ax.set_ylim(0, 1) big_axes[i] = ax super_xlabel(xlabels['big'], big_subfig, big_axes[0], big_axes[-1], **xlab_big_kwargs) letter_subplots(big_axes, 'bcd', **letter_big_kwargs) if True: # Plot filtered snippets: plot_snippets(snip_axes[0, 1:], t_song, song_snip['snip_filt'], c=colors['filt'], lw=lw['filt']) plot_line(snip_axes[0, 0], t_noise, noise_snip['snip_filt'][:, 0], *snip_axes[0, 1].get_ylim(), c=colors['filt'], lw=lw['filt']) # Plot envelope snippets: plot_snippets(snip_axes[1, 1:], t_song, song_snip['snip_env'], ymin=0, c=colors['env'], lw=lw['env']) plot_line(snip_axes[1, 0], t_noise, noise_snip['snip_env'][:, 0], *snip_axes[1, 1].get_ylim(), c=colors['env'], lw=lw['env']) # Plot logarithmic snippets: plot_snippets(snip_axes[2, 1:], t_song, song_snip['snip_log'], c=colors['log'], lw=lw['log']) plot_line(snip_axes[2, 0], t_noise, noise_snip['snip_log'][:, 0], *snip_axes[2, 1].get_ylim(), c=colors['log'], lw=lw['log']) # Plot invariant snippets: plot_snippets(snip_axes[3, 1:], t_song, song_snip['snip_inv'], c=colors['inv'], lw=lw['inv']) plot_line(snip_axes[3, 0], t_noise, noise_snip['snip_inv'][:, 0], *snip_axes[3, 1].get_ylim(), c=colors['inv'], lw=lw['inv']) # Plot kernel response snippets: all_handles = plot_snippets(snip_axes[4, 1:], t_song, song_snip['snip_conv'], c=colors['conv'], lw=lw['conv']) for i, handles in enumerate(all_handles): assign_colors(handles, config['k_specs'][:, 0], conv_colors) reorder_by_sd(handles, song_snip['snip_conv'][..., i]) handles = plot_line(snip_axes[4, 0], t_noise, noise_snip['snip_conv'][:, 0], *snip_axes[4, 1].get_ylim(), c=colors['conv'], lw=lw['conv']) assign_colors(handles, config['k_specs'][:, 0], conv_colors) reorder_by_sd(handles, noise_snip['snip_conv'][:, 0]) # Plot feature snippets: all_handles = plot_snippets(snip_axes[5, 1:], t_song, song_snip['snip_feat'], ymin=0, ymax=1, c=colors['feat'], lw=lw['feat']) for i, handles in enumerate(all_handles): assign_colors(handles, config['k_specs'][:, 0], feat_colors) reorder_by_sd(handles, song_snip['snip_feat'][..., i]) handles = plot_line(snip_axes[5, 0], t_noise, noise_snip['snip_feat'][:, 0], ymin=0, ymax=1, c=colors['feat'], lw=lw['feat']) assign_colors(handles, config['k_specs'][:, 0], feat_colors) reorder_by_sd(handles, noise_snip['snip_feat'][:, 0]) del song_snip, noise_snip # Remember saturation points: crit_inds, crit_dists = {}, {} # Unnormed measures: for stage in stages: # Plot average intensity measure across recordings: curve = plot_curves(big_axes[0], dists, data[f'mean_{stage}'], c=colors[stage], lw=lw['big'], fill_kwargs=dict(color=colors[stage], alpha=0.25)) # # Indicate saturation point: # if stage in ['log', 'inv', 'conv', 'feat']: # ind = get_saturation(curve, **plateau_settings)[1] # dist = dists[ind] # big_axes[0].plot(dist, 0, c='w', alpha=1, zorder=5.5, **plateau_dot_kwargs, # transform=big_axes[0].get_xaxis_transform()) # big_axes[0].plot(dist, 0, mfc=colors[stage], mec='k', alpha=0.75, zorder=6, **plateau_dot_kwargs, # transform=big_axes[0].get_xaxis_transform()) # big_axes[0].vlines(dist, big_axes[0].get_ylim()[0], curve[ind], # color=colors[stage], **plateau_line_kwargs) # # Log saturation point: # crit_inds[stage] = ind # crit_dists[stage] = dist del data # Noise baseline-related measures: data, _ = load_data(base_path, files='scales', keywords='mean') if reduce_kernels: data = reduce_kernel_set(data, kern_inds, keyword='mean') for stage in stages: # Plot average intensity measure across recordings: curve = plot_curves(big_axes[1], dists, data[f'mean_{stage}'], c=colors[stage], lw=lw['big'], fill_kwargs=dict(color=colors[stage], alpha=0.25)) # Indicate saturation point: # if stage in ['log', 'inv', 'conv', 'feat']: # ind, dist = crit_inds[stage], crit_dists[stage] # big_axes[1].plot(dist, 0, c='w', alpha=1, zorder=5.5, **plateau_dot_kwargs, # transform=big_axes[1].get_xaxis_transform()) # big_axes[1].plot(dist, 0, mfc=colors[stage], mec='k', alpha=0.75, zorder=6, **plateau_dot_kwargs, # transform=big_axes[1].get_xaxis_transform()) # big_axes[1].vlines(dist, big_axes[1].get_ylim()[0], curve[ind], # color=colors[stage], **plateau_line_kwargs) del data # Min-max normalized measures: data, _ = load_data(range_path, files='scales', keywords='mean') if reduce_kernels: data = reduce_kernel_set(data, kern_inds, keyword='mean') for stage in stages: # Plot average intensity measure across recordings: curve = plot_curves(big_axes[2], dists, data[f'mean_{stage}'], c=colors[stage], lw=lw['big'], fill_kwargs=dict(color=colors[stage], alpha=0.25)) # # Indicate saturation point: # if stage in ['log', 'inv', 'conv', 'feat']: # ind, dist = crit_inds[stage], crit_dists[stage] # big_axes[2].plot(dist, 0, c='w', alpha=1, zorder=5.5, **plateau_dot_kwargs, # transform=big_axes[2].get_xaxis_transform()) # big_axes[2].plot(dist, 0, mfc=colors[stage], mec='k', alpha=0.75, zorder=6, **plateau_dot_kwargs, # transform=big_axes[2].get_xaxis_transform()) # big_axes[2].vlines(dist, big_axes[2].get_ylim()[0], curve[ind], # color=colors[stage], **plateau_line_kwargs) del data # Save graph: if save_path is not None: fig.savefig(save_path) plt.show() print('Done.') embed()