Cross-checked and polished remainders of fig_invariance_thresh_lp_species.pdf.

Added misc_functions.py for anything not plot-related.
This commit is contained in:
j-hartling
2026-03-31 09:36:55 +02:00
parent 411d50ffcf
commit 298969a067
12 changed files with 251 additions and 165 deletions

View File

@@ -4,10 +4,11 @@ import matplotlib.pyplot as plt
from itertools import product
from thunderhopper.filetools import search_files
from thunderhopper.modeltools import load_data
from misc_functions import shorten_species, get_saturation
from color_functions import load_colors
from plot_functions import hide_axis, ylimits, xlabel, ylabel, hide_ticks,\
from plot_functions import hide_axis, ylimits, super_xlabel, ylabel, hide_ticks,\
plot_line, strip_zeros, time_bar, zoom_inset,\
letter_subplot, title_subplot
letter_subplot, letter_subplots, title_subplot
from IPython import embed
def add_snip_axes(fig, grid_kwargs):
@@ -30,7 +31,14 @@ def plot_snippets(axes, time, snippets, ymin=None, ymax=None, **kwargs):
# GENERAL SETTINGS:
target = 'Omocestus_rufipes'
data_paths = search_files(target, excl='noise', dir='../data/inv/log_hp/')
species_paths = search_files('*', incl='noise', dir='../data/inv/log_hp/')
target_species = [
'Omocestus_rufipes',
'Chorthippus_biguttulus',
'Chorthippus_mollis',
'Chrysochraon_dispar',
'Gomphocerippus_rufus',
'Pseudochorthippus_parallelus',
]
stages = ['env', 'log', 'inv']
load_kwargs = dict(
files=stages,
@@ -39,28 +47,29 @@ load_kwargs = dict(
save_path = '../figures/fig_invariance_log_hp.pdf'
compute_ratios = True
show_diag = True
show_noise = True
show_plateaus = True
# GRAPH SETTINGS:
fig_kwargs = dict(
figsize=(32/2.54, 32/2.54),
)
snip_rows = 1
big_rows = 1
# snip_rows = 1
# big_rows = 1
super_grid_kwargs = dict(
nrows=2 * snip_rows + big_rows,
nrows=3,
ncols=1,
wspace=0,
hspace=0,
left=0,
right=1,
bottom=0,
top=1
top=1,
height_ratios=[1, 1, 1]
)
subfig_specs = dict(
pure=(slice(0, snip_rows), slice(None)),
noise=(slice(snip_rows, 2 * snip_rows), slice(None)),
big=(slice(-big_rows, None), slice(None)),
pure=(0, slice(None)),
noise=(1, slice(None)),
big=(2, slice(None)),
)
block_height = 0.8
edge_padding = 0.08
@@ -112,6 +121,8 @@ fs = dict(
bar=16,
)
colors = load_colors('../data/stage_colors.npz')
species_colors = load_colors('../data/species_colors.npz')
noise_colors = [(0.5, 0.5, 0.5), (0.7, 0.7, 0.7)]
lw_snippets = 1
lw_big = 3
xlabels = dict(
@@ -206,15 +217,34 @@ bar_kwargs = dict(
va='center',
)
)
leg_kwargs = dict(
ncols=2,
loc='upper right',
bbox_to_anchor=(0, 0.6, 1, 0.4),
frameon=False,
prop=dict(
size=12,
style='italic',
),
borderpad=0,
borderaxespad=0,
handlelength=1,
columnspacing=1,
)
diag_kwargs = dict(
c=(0.75, 0.75, 0.75),
lw=2,
ls='--',
zorder=1.9,
)
noise_rel_thresh = 0.95
noise_kwargs = dict(
fc=(0.9, 0.9, 0.9),
plateau_settings = dict(
low=0.05,
high=0.95,
first=True,
last=True,
condense=None,
)
plateau_kwargs = dict(
ec='none',
lw=0,
zorder=1.5,
@@ -225,13 +255,13 @@ if compute_ratios:
ref_data = load_data('../data/processed/white_noise_sd-1.npz', files=stages)[0]
ref_measures = {k: v.std() for k, v in ref_data.items() if not k.endswith('rate')}
species_measures = []
for species_path in species_paths:
species_measure = load_data(species_path, **load_kwargs)[0]['measure_inv']
species_measures = {}
for species in target_species:
path = search_files(species, incl='noise', dir='../data/inv/log_hp/')[0]
measure = load_data(path, **load_kwargs)[0]['measure_inv']
if compute_ratios:
species_measure /= ref_measures['inv']
species_measures.append(species_measure)
species_measures = np.array(species_measures).T
measure /= ref_measures['inv']
species_measures[species] = measure
# EXECUTION:
for data_path in data_paths:
@@ -291,14 +321,10 @@ for data_path in data_paths:
ax.set_xscale('symlog', linthresh=scales[1], linscale=0.5)
ax.set_yscale('symlog', linthresh=scales[1], linscale=0.5)
ax.set_aspect(**anchor_kwargs)
ylabel(ax, ylabels['big'], transform=big_subfig.transSubfigure, **ylab_big_kwargs)
if i == 0:
hide_ticks(ax, 'bottom')
letter_subplot(ax, 'c', **letter_big_kwargs)
else:
xlabel(ax, xlabels['big'], transform=big_subfig.transSubfigure, **xlab_big_kwargs)
letter_subplot(ax, 'd', **letter_big_kwargs)
big_axes[i] = ax
ylabel(big_axes[0], ylabels['big'], transform=big_subfig.transSubfigure, **ylab_big_kwargs)
super_xlabel(xlabels['big'], big_subfig, big_axes[0], big_axes[-1], **xlab_big_kwargs)
letter_subplots(big_axes, 'cde', **letter_big_kwargs)
# Plot pure-song envelope snippets:
handle = plot_snippets(pure_axes[0, :], t_full, pure_data['snip_env'],
@@ -352,25 +378,26 @@ for data_path in data_paths:
big_axes[1].plot(noise_scales, noise_data['measure_log'], c=colors['log'], lw=lw_big)
big_axes[1].plot(noise_scales, noise_data['measure_inv'], c=colors['inv'], lw=lw_big)
# Plot species measures:
big_axes[2].plot(noise_scales, species_measures, 'k', lw=lw_big)
if show_diag:
# Indicate diagonal:
big_axes[0].plot(pure_scales, pure_scales, **diag_kwargs)
big_axes[1].plot(noise_scales, noise_scales, **diag_kwargs)
if show_noise:
# Indicate noise floor:
if compute_ratios:
span_measure = noise_data['measure_inv'][-1] - ref_measures['inv']
thresh_measure = ref_measures['inv'] + noise_rel_thresh * span_measure
else:
span_measure = noise_data['measure_inv'][-1] - noise_data['measure_inv'][0]
thresh_measure = noise_data['measure_inv'][0] + noise_rel_thresh * span_measure
thresh_ind = np.nonzero(noise_data['measure_inv'] < thresh_measure)[0][-1]
thresh_scale = noise_scales[thresh_ind]
big_axes[1].axvspan(noise_scales[0], thresh_scale, **noise_kwargs)
if show_plateaus:
# Indicate low and high plateaus of noise invariance curve:
low_ind, high_ind = get_saturation(noise_data['measure_inv'], **plateau_settings)
big_axes[1].axvspan(noise_scales[0], noise_scales[low_ind],
fc=noise_colors[0], **plateau_kwargs)
big_axes[1].axvspan(noise_scales[low_ind], noise_scales[high_ind],
fc=noise_colors[1], **plateau_kwargs)
# Plot species-specific noise-song measures:
for species, measure in species_measures.items():
label = shorten_species(species)
big_axes[2].plot(noise_scales, measure, label=label,
c=species_colors[species], lw=lw_big)
big_axes[2].legend(**leg_kwargs)
if save_path is not None:
fig.savefig(save_path, bbox_inches='tight')

View File

@@ -7,11 +7,11 @@ 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 shorten_species, get_saturation
from color_functions import load_colors, shade_colors, create_listed_cmap
from plot_functions import hide_axis, title_subplot, ylimits, xlabel, ylabel, super_ylabel,\
plot_line, plot_barcode, strip_zeros, time_bar,\
letter_subplot, letter_subplots, hide_ticks,\
super_xlabel, super_ylabel, assign_colors
from plot_functions import hide_axis, title_subplot, ylimits, xlabel, ylabel,\
plot_line, time_bar,letter_subplot, letter_subplots,\
hide_ticks, super_xlabel, reorder_by_norm
from IPython import embed
def force_sequence(*vars, skip_None=False, equal_size=False):
@@ -126,10 +126,6 @@ def split_subplot(ax, side='right', size=10, pad=10):
inputs = zip(*force_sequence(side, size, pad, equal_size=True))
return [div.append_axes(s, f'{n}%', f'{p}%') for s, n, p in inputs]
def shorten_species(name):
genus, species = name.split('_')
return genus[0] + '. ' + species
def add_cross_axes(fig, n, long='col', fill='row', **grid_kwargs):
n_axes = n * (n - 1) // 2
nrows = grid_kwargs.get('nrows', None)
@@ -179,7 +175,7 @@ load_kwargs = dict(
)
save_path = '../figures/fig_invariance_thresh_lp_species.pdf'
exclude_zero = True
show_noise = True
show_floor = True
# SUBSET SETTINGS:
thresh_rel = np.array([0.5, 1, 3])[0]
@@ -214,7 +210,7 @@ subfig_specs = dict(
feat_grid_kwargs = dict(
nrows=2,
ncols=n_species,
wspace=0.25,
wspace=0.35,
hspace=0.1,
left=0.06,
right=0.985,
@@ -234,17 +230,16 @@ song_grid_kwargs = dict(
space_grid_kwargs = dict(
nrows=None,
ncols=None,
wspace=0.1,
hspace=0.3,
left=0.05,
right=1,
bottom=0.1,
wspace=0,
hspace=0.4,
left=0.15,
right=0.9,
bottom=0.13,
top=0.95
)
anchor_kwargs = dict(
aspect='equal',
adjustable='box',
anchor=(0.5, 0.5)
)
inset_kwargs = dict(
y0=0.7,
@@ -264,10 +259,12 @@ fs = dict(
species_colors = load_colors('../data/species_colors.npz')
kernel_shades = [0, 0.75]
scale_shades = [1, 0]
noise_colors = [(0.5, 0.5, 0.5), (0.7, 0.7, 0.7)]
lw = dict(
song=0.5,
feat=3,
kern=3
kern=2.5,
plateau=3,
)
zorder = dict(
Omocestus_rufipes=2,
@@ -285,7 +282,7 @@ xlabels = dict(
space=[f'$\\mu_{{f_{i}}}$' for i in range(1, n_kernels + 1)],
)
ylabels = dict(
feat='$\\mu_f$',
feat='$\\mu_{f_i}$',
space=[f'$\\mu_{{f_{i}}}$' for i in range(1, n_kernels + 1)],
bar='scale $\\alpha$',
)
@@ -296,10 +293,10 @@ xlab_feat_kwargs = dict(
va='bottom',
)
xlab_space_kwargs = dict(
y=-0.3,
y=-0.2,
fontsize=fs['lab_tex'],
ha='center',
va='bottom',
va='top',
)
ylab_feat_kwargs = dict(
x=0,
@@ -308,13 +305,14 @@ ylab_feat_kwargs = dict(
va='top',
)
ylab_space_kwargs = dict(
x=-0.2,
x=-0.3,
rotation=0,
fontsize=fs['lab_tex'],
ha='center',
va='bottom',
ha='right',
va='center',
)
ylab_cbar_kwargs = dict(
x=1,
x=-2,
fontsize=fs['lab_norm'],
ha='center',
va='bottom',
@@ -368,30 +366,43 @@ song_bar_kwargs = dict(
color='k',
lw=0,
clip_on=False,
# text_pos=(-0.1, 0.5),
# text_str=f'${int(1000 * song_bar_time)}\\,\\text{{ms}}$',
# text_kwargs=dict(
# fontsize=fs['bar'],
# ha='right',
# va='center',
# )
text_pos=(1.25, 0.5),
text_str=f'${int(song_bar_time)}\\,\\text{{s}}$',
text_kwargs=dict(
fontsize=fs['bar'],
ha='left',
va='center',
)
)
kern_bar_time = 0.05
kern_bar_kwargs = dict(
dur=kern_bar_time,
y0=inset_kwargs['y0'] - 0.03,
y1=inset_kwargs['y0'],
y0=0.1,
y1=0.2,
color='k',
lw=0
lw=0,
clip_on=False,
text_pos=(0.6, -1),
text_str=f'${int(kern_bar_time * 1000)}\\,\\text{{ms}}$',
text_kwargs=dict(
fontsize=fs['bar'],
ha='center',
va='top',
)
)
noise_kwargs = dict(
floor_kwargs = dict(
fc=(0.9, 0.9, 0.9),
ec='none',
lw=0,
zorder=0.5,
)
low_rel_thresh = 0.05
high_rel_thresh = 0.95
plateau_settings = dict(
low=0.05,
high=0.95,
first=True,
last=True,
condense='norm',
)
# EXECUTION:
@@ -450,14 +461,17 @@ letter_subplot(noise_subfig, 'e', ref=noise_axes[0], **letter_space_kwargs)
# Format feature space axes:
for ind, axes in enumerate(zip(pure_axes, noise_axes)):
irow, icol = row_inds[ind], col_inds[ind]
for ax in axes:
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.xaxis.set_major_locator(plt.MultipleLocator(xloc['space']))
ax.yaxis.set_major_locator(plt.MultipleLocator(yloc['space']))
ax.set_aspect(**anchor_kwargs)
xlabel(ax, xlabels['space'][col_inds[ind]], **xlab_space_kwargs)
ylabel(ax, ylabels['space'][row_inds[ind]], **ylab_space_kwargs)
anchor = space_pos[ind] / space_pos.max(axis=0)
anchor[0] = 1 - anchor[0]
ax.set_aspect(anchor=tuple(anchor[::-1]), **anchor_kwargs)
xlabel(ax, xlabels['space'][icol], **xlab_space_kwargs)
ylabel(ax, ylabels['space'][irow], **ylab_space_kwargs)
# Determine area to place colorbars:
rightmost = pure_axes[np.argmax(space_pos[:, 1])].get_position()
@@ -479,7 +493,11 @@ kern_factors = np.linspace(*kernel_shades, n_kernels)
kern_colors_bw = shade_colors((0., 0., 0.), kern_factors)
# Plot results per species:
noise_feat = np.zeros((n_species, n_kernels), dtype=float)
min_noise_feat = np.zeros((n_species, n_kernels), dtype=float)
max_pure_feat = np.zeros((n_species, n_kernels), dtype=float)
max_noise_feat = np.zeros((n_species, n_kernels), dtype=float)
pure_space_handles = {ax: [] for ax in pure_axes}
noise_space_handles = {ax: [] for ax in noise_axes}
for i, species in enumerate(target_species):
print(f'Processing {species}')
@@ -496,6 +514,7 @@ for i, species in enumerate(target_species):
plot_line(song_ax, time, song, ypad=0.05, c='k', lw=lw['song'])
title_subplot(song_ax, shorten_species(species), ref=song_subfig, **title_kwargs)
time_bar(song_ax, **song_bar_kwargs)
song_bar_kwargs['text_pos'] = None
# Fetch species-specific invariance files:
pure_path = search_files(species, incl='pure', dir='../data/inv/thresh_lp/')[0]
@@ -545,7 +564,8 @@ for i, species in enumerate(target_species):
inset.plot(config['k_times'], kern, c=c, lw=lw['kern'])
inset.set_xlim(xlims)
inset.set_ylim(ylims)
time_bar(insets[0], parent=feat_axes[0, 0], **kern_bar_kwargs)
# time_bar(insets[0], parent=feat_axes[0, 0], **kern_bar_kwargs)
time_bar(insets[0], **kern_bar_kwargs)
# Plot invariance curves in feature space:
norm = LogNorm(vmin=scales[scales > 0][0], vmax=scales[-1])
@@ -554,60 +574,56 @@ for i, species in enumerate(target_species):
pure_handle = pure_ax.scatter(pure_measure[:, icol], pure_measure[:, irow],
c=scales, cmap=scale_cmap, norm=norm,
zorder=zorder[species], **space_kwargs)
pure_space_handles[pure_ax].append(pure_handle)
noise_handle = noise_ax.scatter(noise_measure[:, icol], noise_measure[:, irow],
c=scales, cmap=scale_cmap, norm=norm,
zorder=zorder[species], **space_kwargs)
noise_space_handles[noise_ax].append(noise_handle)
# Indicate scale color code in pure subfigure:
pure_subfig.colorbar(pure_handle, cax=pure_bars[i])
pure_bars[i].set_yscale('symlog', **symlog_kwargs)
if i < n_species - 1:
hide_ticks(pure_bars[i], 'right', ticks=False)
else:
ylabel(pure_bars[i], ylabels['bar'], transform=pure_subfig.transSubfigure, **ylab_cbar_kwargs)
hide_ticks(pure_bars[i], 'right', ticks=False)
if i == 0:
pure_bars[0].tick_params(axis='y', which='both', left=True, labelleft=True)
ylabel(pure_bars[0], ylabels['bar'], **ylab_cbar_kwargs)
# Indicate scale color code in noise subfigure:
noise_subfig.colorbar(noise_handle, cax=noise_bars[i])
noise_bars[i].set_yscale('symlog', **symlog_kwargs)
if i < n_species - 1:
hide_ticks(noise_bars[i], 'right', ticks=False)
else:
ylabel(noise_bars[i], ylabels['bar'], transform=noise_subfig.transSubfigure, **ylab_cbar_kwargs)
hide_ticks(noise_bars[i], 'right', ticks=False)
if i == 0:
noise_bars[0].tick_params(axis='y', which='both', left=True, labelleft=True)
ylabel(noise_bars[0], ylabels['bar'], **ylab_cbar_kwargs)
# Log feature noise floor:
noise_feat[i, :] = noise_measure.min(axis=0)
# Indicate plateaus of pure invariance curves:
low_ind, high_ind = get_saturation(pure_measure, **plateau_settings)
pure_bars[i].axhline(scales[low_ind], c=noise_colors[0], lw=lw['plateau'])
pure_bars[i].axhline(scales[high_ind], c=noise_colors[1], lw=lw['plateau'])
# Indicate low and high plateaus:
min_feat = pure_measure.min(axis=0)
span_feat = pure_measure.max(axis=0) - min_feat
# Indicate plateaus of noise invariance curves:
low_ind, high_ind = get_saturation(noise_measure, **plateau_settings)
noise_bars[i].axhline(scales[low_ind], c=noise_colors[0], lw=lw['plateau'])
noise_bars[i].axhline(scales[high_ind], c=noise_colors[1], lw=lw['plateau'])
# Log start and end of invariance curve:
min_noise_feat[i, :] = noise_measure.min(axis=0)
max_pure_feat[i, :] = pure_measure.max(axis=0)
max_noise_feat[i, :] = noise_measure.max(axis=0)
low_thresh = min_feat + low_rel_thresh * span_feat
low_ind = np.nonzero((pure_measure >= low_thresh).all(axis=1))[0][0]
pure_bars[i].axhline(scales[low_ind], c='k', lw=3)
# Sort feature space traces by distance of endpoint to origin:
for ind, (pure_ax, noise_ax) in enumerate(zip(pure_axes, noise_axes)):
irow, icol = row_inds[ind], col_inds[ind]
reorder_by_norm(pure_space_handles[pure_ax], max_pure_feat[:, [icol, irow]])
reorder_by_norm(noise_space_handles[noise_ax], max_noise_feat[:, [icol, irow]])
high_thresh = min_feat + high_rel_thresh * span_feat
high_ind = np.nonzero((pure_measure >= high_thresh).any(axis=1))[0][0]
pure_bars[i].axhline(scales[high_ind], c='w', lw=3)
# Indicate low and high plateaus:
min_feat = noise_measure.min(axis=0)
span_feat = noise_measure.max(axis=0) - min_feat
low_thresh = min_feat + low_rel_thresh * span_feat
low_ind = np.nonzero((noise_measure >= low_thresh).all(axis=1))[0][0]
noise_bars[i].axhline(scales[low_ind], c='k', lw=3)
high_thresh = min_feat + high_rel_thresh * span_feat
high_ind = np.nonzero((noise_measure >= high_thresh).any(axis=1))[0][0]
noise_bars[i].axhline(scales[high_ind], c='w', lw=3)
if show_noise:
if show_floor:
# Indicate feature noise floor:
noise_feat = noise_feat.mean(axis=0)
noise_feat = min_noise_feat.mean(axis=0)
for ind, ax in enumerate(noise_axes):
irow, icol = row_inds[ind], col_inds[ind]
ax.add_patch(plt.Rectangle((0, 0), noise_feat[icol], noise_feat[irow], **noise_kwargs))
ax.add_patch(plt.Rectangle((0, 0), noise_feat[icol], noise_feat[irow], **floor_kwargs))
if save_path is not None:
fig.savefig(save_path)

View File

@@ -6,7 +6,7 @@ from thunderhopper.modeltools import load_data
from color_functions import load_colors
from plot_functions import hide_axis, letter_subplots,\
ylabel, super_xlabel, plot_line, plot_barcode,\
indicate_zoom, assign_colors, reorder_traces
indicate_zoom, assign_colors, reorder_by_sd
from IPython import embed
# GENERAL SETTINGS:
@@ -215,10 +215,10 @@ for data_path in data_paths:
signal = data['conv'][:, kern_inds]
handles = plot_line(ax_full, t_full, signal, lw=lw_full['conv'], yloc=yloc_full['conv'])
assign_colors(handles, kern_specs[:, 0], conv_colors)
reorder_traces(handles, signal)
reorder_by_sd(handles, signal)
handles = plot_line(ax_zoom, t_zoom, signal[zoom_mask, :], lw=lw_zoom['conv'], yloc=yloc_zoom['conv'])
assign_colors(handles, kern_specs[:, 0], conv_colors)
reorder_traces(handles, signal[zoom_mask, :])
reorder_by_sd(handles, signal[zoom_mask, :])
hide_axis(ax_full, 'bottom')
hide_axis(ax_zoom, 'bottom')

32
python/misc_functions.py Normal file
View File

@@ -0,0 +1,32 @@
import numpy as np
def shorten_species(name):
genus, species = name.split('_')
return genus[0] + '. ' + species
def get_saturation(sigmoid, low=0.05, high=0.95, first=True, last=True,
condense=None):
if condense == 'norm' and sigmoid.ndim == 2:
sigmoid = np.linalg.norm(sigmoid, axis=1)
min_value = sigmoid[0] if first else sigmoid.min(axis=0)
max_value = sigmoid[-1] if last else sigmoid.max(axis=0)
span = max_value - min_value
low_value = min_value + low * span
high_value = min_value + high * span
low_mask = sigmoid >= low_value
high_mask = sigmoid >= high_value
if sigmoid.ndim == 1:
low_ind = np.nonzero(low_mask)[0][0]
high_ind = np.nonzero(high_mask)[0][0]
elif condense == 'all':
low_ind = np.nonzero(low_mask.all(axis=1))[0][0]
high_ind = np.nonzero(high_mask.all(axis=1))[0][0]
else:
low_ind, high_ind = [], []
for i in range(sigmoid.shape[1]):
low_ind.append(np.nonzero(low_mask[:, i])[0][0])
high_ind.append(np.nonzero(high_mask[:, i])[0][0])
return low_ind, high_ind

View File

@@ -202,8 +202,15 @@ def assign_colors(handles, types, colors):
handle.set_color(colors[str(int(type_id))])
return None
def reorder_traces(handles, signal, zlow=2, zhigh=2.5):
inds = np.argsort(signal.std(axis=0))
def reorder_by_sd(handles, data, zlow=2, zhigh=2.5):
inds = np.argsort(data.std(axis=0))
zorders = np.linspace(zlow, zhigh, len(inds))[::-1]
for ind, z in zip(inds, zorders):
handles[ind].set_zorder(z)
return None
def reorder_by_norm(handles, data, zlow=2, zhigh=2.5):
inds = np.argsort(np.linalg.norm(data, axis=1))
zorders = np.linspace(zlow, zhigh, len(inds))[::-1]
for ind, z in zip(inds, zorders):
handles[ind].set_zorder(z)