Finished a good part of analysis and figure for Thresh-LP invariance (WIP).

This commit is contained in:
j-hartling
2026-03-06 14:47:22 +01:00
parent 933d28b5f8
commit 0407053c20
15 changed files with 774 additions and 338 deletions

View File

@@ -1,224 +1,281 @@
import plotstyle_plt
import glob
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import BboxTransformTo
from itertools import product
from thunderhopper.filetools import search_files
from thunderhopper.modeltools import load_data
from color_functions import load_colors
from plot_functions import hide_axis, letter_subplots, ylimits, ylabel, super_xlabel, plot_line
from plot_functions import hide_axis, ylimits, xlabel, ylabel, plot_line, strip_zeros
from IPython import embed
def strip_zeros(num, right_digits=5):
if isinstance(num, int):
return num
num = f'{num:.{right_digits}f}'
left, right = num.split('.')
right = right.rstrip('0')
if right:
return f'{left}.{right}'
return left
def plot_snippets(axes, time, snippets, label, scales=None,
ymin=None, ymax=None, **kwargs):
ymin, ymax = ylimits(snippets, minval=ymin, maxval=ymax, pad=0.05)
ylabel(axes[0], label, x=-0.7, rotation=0, ha='left', va='center')
for i, (ax, snippet) in enumerate(zip(axes, snippets.T)):
plot_line(ax, time, snippet, ymin=ymin, ymax=ymax, **kwargs)
if scales is not None:
ax.set_title(f'$\\alpha={strip_zeros(scales[i])}$')
def time_bar(ax, dur, y0=0.9, y1=0.95, xshift=0.5, parent=None, transform=None, **kwargs):
t0, t1 = ax.get_xlim()
offset = (t1 - t0 - dur) * xshift
x0 = t0 + offset
x1 = x0 + dur
if parent is None:
parent = ax
if transform is None:
transform = BboxTransformTo(parent.bbox)
if transform is not ax.transData:
trans = ax.transData + transform.inverted()
x0 = trans.transform((x0, 0))[0]
x1 = trans.transform((x1, 0))[0]
parent.add_artist(plt.Rectangle((x0, y0), x1 - x0, y1 - y0,
transform=transform, **kwargs))
return None
def add_snip_axes(fig, grid_kwargs):
grid = fig.add_gridspec(**grid_kwargs)
axes = np.zeros((grid.nrows, grid.ncols), dtype=object)
for i, j in product(range(grid.nrows), range(grid.ncols)):
axes[i, j] = fig.add_subplot(grid[i, j])
[hide_axis(ax, 'left') for ax in axes.flatten()]
[hide_axis(ax, 'bottom') for ax in axes.flatten()]
return axes
def plot_snippets(axes, time, snippets, ymin=None, ymax=None, **kwargs):
ymin, ymax = ylimits(snippets, minval=ymin, maxval=ymax, pad=0.05)
for ax, snippet in zip(axes, snippets.T):
plot_line(ax, time, snippet, ymin=ymin, ymax=ymax, **kwargs)
return None
# GENERAL SETTINGS:
target = 'Omocestus_rufipes'
data_paths = glob.glob(f'../data/inv/log_hp/{target}*.npz')
data_paths = search_files(target, excl='noise', dir='../data/inv/log_hp/')
stages = ['env', 'log', 'inv']
files = stages + ['scales', 'plot_scales', 'snr_env', 'snr_log', 'snr_inv']
files = stages + ['scales', 'example_scales', 'limit',
'measure_env', 'measure_log', 'measure_inv']
save_path = '../figures/fig_invariance_log_hp.pdf'
# PLOT SETTINGS:
# GRAPH SETTINGS:
fig_kwargs = dict(
figsize=(32/2.54, 16/2.54),
)
grid1_kwargs = dict(
super_grid_kwargs = dict(
nrows=2,
ncols=2,
wspace=0,
hspace=0,
left=0,
right=1,
bottom=0,
top=1
)
subfig_specs = dict(
pure=(0, 0),
noise=(1, 0),
analysis=(slice(None), 1)
)
pure_grid_kwargs = dict(
nrows=len(stages),
ncols=None,
wspace=0.05,
hspace=0.3,
left=0.1,
right=0.985,
bottom=0.17,
hspace=0.1,
left=0.13,
right=0.95,
bottom=0.15,
top=0.9
)
grid2_kwargs = dict(
noise_grid_kwargs = dict(
nrows=len(stages),
ncols=None,
wspace=0.05,
hspace=0.1,
left=0.13,
right=0.95,
bottom=0.15,
top=0.9
)
analysis_grid_kwargs = dict(
nrows=1,
ncols=3,
wspace=0.35,
ncols=1,
wspace=0,
hspace=0,
left=0.1,
right=0.985,
bottom=0.18,
left=0.15,
right=0.96,
bottom=0.1,
top=0.95
)
snip_specs = dict(
env=(0, slice(None)),
log=(1, slice(None)),
inv=(2, slice(None))
)
# PLOT SETTINGS:
colors = load_colors('../data/stage_colors.npz')
lw_snippets = 0.25
lw_snippets = 0.5
lw_analysis = 3
xlabels = dict(
analysis='scale $\\alpha$',
)
xlab_analysis_kwargs = dict(
y=0.01,
fontsize=16,
ha='center',
va='bottom',
)
ylabels = dict(
env=r'$x_{\text{env}}$',
log=r'$x_{\text{dB}}$',
inv=r'$x_{\text{adapt}}$',
abs='abs. SNR',#'abs. ' + r'$\text{SNR}$',
norm='norm. SNR',#'norm. ' + r'$\text{SNR}$',
gain='rel. SNR gain'
env='$x_{\\text{env}}$',
log='$x_{\\text{dB}}$',
inv='$x_{\\text{adapt}}$',
analysis='ratio $\\text{SD}_{\\alpha}\\,/\\,\\text{SD}_{\\min[\\alpha]}$',
# analysis='ratio $\\sigma_{\\alpha}\\,/\\,\\sigma_{\\min[\\alpha]}$',
)
ylab_snip_kwargs = dict(
x=0.01,
fontsize=20,
rotation=0,
ha='left',
va='center',
)
ylab_analysis_kwargs = dict(
x=0.02,
fontsize=16,
ha='center',
va='top',
)
xloc = dict(
abs=5,
analysis=200,
)
yloc = dict(
env=100,
log=10,
inv=5,
abs=50,
)
letter_kwargs = dict(
x=0.03,
y=0.99,
letter_snip_kwargs = dict(
x=0.02,
y=1,
ha='left',
va='top'
va='top',
fontsize=22,
fontweight='bold'
)
fit_kwargs = dict(
c='darkred',
ls='--',
zorder=1.9
letter_analysis_kwargs = dict(
x=0,
y=1,
ha='left',
va='top',
fontsize=22,
fontweight='bold'
)
fit_lw = dict(
abs=6,
norm=3,
gain=3
)
text_kwargs = dict(
abs=dict(s='$\\alpha^2+1$', fontsize=16, c=fit_kwargs['c'],
x=0.85, y=0.9, ha='right', va='center'),
norm=dict(s='$\\alpha^2+1$', fontsize=16, c=fit_kwargs['c'],
x=0.85, y=0.9, ha='right', va='center'),
gain=dict(s='$\\frac{1}{\\alpha}$', fontsize=24, c=fit_kwargs['c'],
x=0.75, y=0.8, ha='left', va='center')
)
calculated_floor = False
floor_kwargs = dict(
xmin=0,
indicate_unsaturated = False
unsaturated_proportion = 0.85
unsaturated_kwargs = dict(
color=3 * (0.85,),
zorder=0,
lw=0
)
bar_time = 5
bar_kwargs = dict(
y0=0.5,
y1=0.6,
color='k',
lw = 0,
)
# EXECUTION:
for data_path in data_paths:
print(f'Processing {data_path}')
# Load invariance data:
data, config = load_data(data_path, files)
t_full = np.arange(data['env'].shape[0]) / config['env_rate']
nonzero_scales = data['scales'][data['scales'] > 0]
floor_kwargs['xmax'] = data['floor_scale'] if calculated_floor else 1
pure_data, config = load_data(data_path, files)
noise_data, _ = load_data(data_path.replace('.npz', '_noise.npz'), files)
t_full = np.arange(pure_data['env'].shape[0]) / config['env_rate']
# Prepare overall graph:
fig = plt.figure(**fig_kwargs)
super_grid = fig.add_gridspec(2, 1, wspace=0, hspace=1,
left=0, right=1, bottom=0, top=1)
super_grid = fig.add_gridspec(**super_grid_kwargs)
# Prepare snippet axes:
subfig1 = fig.add_subfigure(super_grid[0, :])
grid1_kwargs['ncols'] = data['plot_scales'].size
grid1 = subfig1.add_gridspec(**grid1_kwargs)
axes = np.zeros((grid1.nrows, grid1.ncols), dtype=object)
for i, j in product(range(grid1.nrows), range(grid1.ncols)):
axes[i, j] = subfig1.add_subplot(grid1[i, j])
[hide_axis(ax, 'bottom') for ax in axes[:-1, :].flatten()]
[hide_axis(ax, 'left') for ax in axes[:, 1:].flatten()]
super_xlabel('time [s]', subfig1, axes[-1, 0], axes[-1, -1], y=0)
letter_subplots(axes[:, 0], labels='abc', **letter_kwargs)
# Prepare pure-song snippet axes:
pure_subfig = fig.add_subfigure(super_grid[subfig_specs['pure']])
pure_grid_kwargs['nrows' if pure_grid_kwargs['nrows'] is None else 'ncols'] = pure_data['example_scales'].size
pure_axes = add_snip_axes(pure_subfig, pure_grid_kwargs)
for ax, stage in zip(pure_axes[:, 0], stages):
ylabel(ax, ylabels[stage], **ylab_snip_kwargs,
transform=pure_subfig.transSubfigure)
for ax, scale in zip(pure_axes[snip_specs['env']], pure_data['example_scales']):
ax.set_title(f'$\\alpha={strip_zeros(scale)}$')
pure_subfig.text(s='a', **letter_snip_kwargs)
# Prepare analysis axes:
subfig2 = fig.add_subfigure(super_grid[1, :])
grid2 = subfig2.add_gridspec(**grid2_kwargs)
symlog_kwargs = dict(linthresh=nonzero_scales.min(), linscale=0.2)
# Prepare noise-song snippet axes:
noise_subfig = fig.add_subfigure(super_grid[subfig_specs['noise']])
noise_grid_kwargs['nrows' if noise_grid_kwargs['nrows'] is None else 'ncols'] = noise_data['example_scales'].size
noise_grid = noise_subfig.add_gridspec(**noise_grid_kwargs)
noise_axes = add_snip_axes(noise_subfig, noise_grid_kwargs)
for ax, stage in zip(noise_axes[:, 0], stages):
ylabel(ax, ylabels[stage], **ylab_snip_kwargs,
transform=noise_subfig.transSubfigure)
for ax, scale in zip(noise_axes[snip_specs['env']], noise_data['example_scales']):
ax.set_title(f'$\\alpha={strip_zeros(scale)}$')
noise_subfig.text(s='b', **letter_snip_kwargs)
ax_abs_snr = subfig2.add_subplot(grid2[:, 0])
ax_abs_snr.set_ylabel(ylabels['abs'])
# Prepare analysis axis:
analysis_subfig = fig.add_subfigure(super_grid[subfig_specs['analysis']])
analysis_grid = analysis_subfig.add_gridspec(**analysis_grid_kwargs)
analysis_ax = analysis_subfig.add_subplot(analysis_grid[0, 0])
analysis_ax.set_xlim(noise_data['scales'].min(), noise_data['scales'].max())
analysis_ax.xaxis.set_major_locator(plt.MultipleLocator(xloc['analysis']))
xlabel(analysis_ax, xlabels['analysis'], **xlab_analysis_kwargs,
transform=analysis_subfig.transSubfigure)
analysis_ax.set_yscale('log')
ylabel(analysis_ax, ylabels['analysis'], **ylab_analysis_kwargs,
transform=analysis_subfig.transSubfigure)
analysis_subfig.text(s='c', **letter_analysis_kwargs)
ax_norm_snr = subfig2.add_subplot(grid2[:, 1])
ax_norm_snr.set_ylabel(ylabels['norm'])
ax_norm_snr.set_xscale('symlog', **symlog_kwargs)
ax_norm_snr.set_yscale('log')
# Plot pure-song envelope snippets:
plot_snippets(pure_axes[snip_specs['env']], t_full, pure_data['env'],
ymin=0, c=colors['env'], lw=lw_snippets)
ax_gain = subfig2.add_subplot(grid2[:, 2])
ax_gain.set_ylabel(ylabels['gain'])
ax_gain.set_xscale('symlog', **symlog_kwargs)
ax_gain.set_yscale('log')
super_xlabel('song scale $\\alpha$', subfig2, ax_abs_snr, ax_gain)
letter_subplots([ax_abs_snr, ax_norm_snr, ax_gain], labels='def', **letter_kwargs)
# Plot pure-song logarithmic snippets:
plot_snippets(pure_axes[snip_specs['log']], t_full, pure_data['log'],
ymax=None, c=colors['log'], lw=lw_snippets)
# Plot envelope snippets:
plot_snippets(axes[0, :], t_full, data['env'], ylabels['env'], ymin=0,
scales=data['plot_scales'], c=colors['env'], lw=lw_snippets)
axes[0, 0].yaxis.set_major_locator(plt.MultipleLocator(yloc['env']))
# Plot logarithmic snippets:
plot_snippets(axes[1, :], t_full, data['log'], ylabels['log'], ymax=0,
c=colors['log'], lw=lw_snippets)
axes[1, 0].yaxis.set_major_locator(plt.MultipleLocator(yloc['log']))
# Plot invariant snippets:
plot_snippets(axes[2, :], t_full, data['inv'], ylabels['inv'],
# Plot pure-song invariant snippets:
plot_snippets(pure_axes[snip_specs['inv']], t_full, pure_data['inv'],
c=colors['inv'], lw=lw_snippets)
axes[2, 0].yaxis.set_major_locator(plt.MultipleLocator(yloc['inv']))
# Plot in-representation SNRs (absolute):
ax_abs_snr.plot(data['scales'], data['snr_env'], c=colors['env'], lw=lw_analysis)
ax_abs_snr.plot(data['scales'], data['snr_log'], c=colors['log'], lw=lw_analysis)
ax_abs_snr.plot(data['scales'], data['snr_inv'], c=colors['inv'], lw=lw_analysis)
ax_abs_snr.axvspan(**floor_kwargs)
ax_abs_snr.xaxis.set_major_locator(plt.MultipleLocator(xloc['abs']))
ax_abs_snr.yaxis.set_major_locator(plt.MultipleLocator(yloc['abs']))
ax_abs_snr.set_ylim(0, data['snr_env'].max())
# Indicate time scale:
time_bar(pure_axes[snip_specs['env']][0], bar_time, **bar_kwargs)
# Plot envelope SNR fit:
ax_abs_snr.plot(data['scales'], data['scales'] ** 2 + 1,
lw=fit_lw['abs'], **fit_kwargs)
ax_abs_snr.text(transform=ax_abs_snr.transAxes, **text_kwargs['abs'])
# Plot noise-song envelope snippets:
plot_snippets(noise_axes[snip_specs['env']], t_full, noise_data['env'],
ymin=0, c=colors['env'], lw=lw_snippets)
# Get normalized SNRs:
norm_snr_env = data['snr_env'] / data['snr_env'].max()
norm_snr_log = data['snr_log'] / data['snr_log'].max()
norm_snr_inv = data['snr_inv'] / data['snr_inv'].max()
# Plot noise-song logarithmic snippets:
plot_snippets(noise_axes[snip_specs['log']], t_full, noise_data['log'],
ymax=None, c=colors['log'], lw=lw_snippets)
# Plot in-representation SNRs (normalized):
ax_norm_snr.plot(data['scales'], norm_snr_env, c=colors['env'], lw=lw_analysis)
ax_norm_snr.plot(data['scales'], norm_snr_log, c=colors['log'], lw=lw_analysis)
ax_norm_snr.plot(data['scales'], norm_snr_inv, c=colors['inv'], lw=lw_analysis)
ax_norm_snr.axvspan(**floor_kwargs)
ax_norm_snr.set_ylim(norm_snr_env.min(), 1)
# Plot noise-song invariant snippets:
plot_snippets(noise_axes[snip_specs['inv']], t_full, noise_data['inv'],
c=colors['inv'], lw=lw_snippets)
# # Plot envelope SNR fit:
# ax_norm_snr.plot(nonzero_scales, nonzero_scales / nonzero_scales.max(),
# lw=fit_lw['norm'], **fit_kwargs)
# ax_norm_snr.text(transform=ax_norm_snr.transAxes, **text_kwargs['norm'])
# Indicate time scale:
time_bar(noise_axes[snip_specs['env']][0], bar_time, **bar_kwargs)
# Get relative SNR gain:
gain_log = norm_snr_log / norm_snr_env
gain_inv = norm_snr_inv / norm_snr_env
# Plot pure-song SD ratios (ideal):
base_ind = np.argmin(pure_data['scales'])
# measure_env = pure_data['measure_env'] / pure_data['measure_env'][base_ind]
# measure_log = pure_data['measure_log'] / pure_data['measure_log'][base_ind]
measure_inv = pure_data['measure_inv'] / pure_data['measure_inv'][base_ind]
# analysis_ax.plot(pure_data['scales'], measure_env, c=colors['env'], lw=lw_analysis, ls='--')
# analysis_ax.plot(pure_data['scales'], measure_log, c=colors['log'], lw=lw_analysis, ls='--')
analysis_ax.plot(pure_data['scales'], measure_inv, c=colors['inv'], lw=lw_analysis, ls='--')
# Plot across-representation gain:
ax_gain.plot(data['scales'], gain_log, c=colors['log'], lw=lw_analysis)
ax_gain.plot(data['scales'], gain_inv, c=colors['inv'], lw=lw_analysis)
ax_gain.axvspan(**floor_kwargs)
ax_gain.set_ylim(1, 10)
if indicate_unsaturated:
# Indicate influence of noise floor:
limit = noise_data['limit'] * unsaturated_proportion
thresh_ind = np.nonzero(noise_data['measure_inv'] <= limit)[0][-1]
analysis_ax.axvspan(0, noise_data['scales'][thresh_ind], **unsaturated_kwargs)
# Plot noise-song SD ratios (limited):
base_ind = np.argmin(noise_data['scales'])
measure_env = noise_data['measure_env'] / noise_data['measure_env'][base_ind]
measure_log = noise_data['measure_log'] / noise_data['measure_log'][base_ind]
measure_inv = noise_data['measure_inv'] / noise_data['measure_inv'][base_ind]
analysis_ax.plot(noise_data['scales'], measure_env, c=colors['env'], lw=lw_analysis)
analysis_ax.plot(noise_data['scales'], measure_log, c=colors['log'], lw=lw_analysis)
analysis_ax.plot(noise_data['scales'], measure_inv, c=colors['inv'], lw=lw_analysis)
analysis_ax.set_ylim(0.1, measure_env.max())
# Plot amplification fit:
ax_gain.plot(nonzero_scales, nonzero_scales.max() / nonzero_scales,
lw=fit_lw['gain'], **fit_kwargs)
ax_gain.text(transform=ax_gain.transAxes, **text_kwargs['gain'])
if save_path is not None:
fig.savefig(save_path)
plt.show()

View File

@@ -0,0 +1,285 @@
from pyparsing import alphanums
import plotstyle_plt
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import BboxTransformTo
from itertools import product
from thunderhopper.filetools import search_files
from thunderhopper.modeltools import load_data
from color_functions import load_colors
from plot_functions import hide_axis, ylimits, xlabel, ylabel, plot_line, plot_barcode, strip_zeros
from IPython import embed
def time_bar(ax, dur, y0=0.9, y1=0.95, xshift=0.5, parent=None, transform=None, **kwargs):
t0, t1 = ax.get_xlim()
offset = (t1 - t0 - dur) * xshift
x0 = t0 + offset
x1 = x0 + dur
if parent is None:
parent = ax
if transform is None:
transform = BboxTransformTo(parent.bbox)
if transform is not ax.transData:
trans = ax.transData + transform.inverted()
x0 = trans.transform((x0, 0))[0]
x1 = trans.transform((x1, 0))[0]
parent.add_artist(plt.Rectangle((x0, y0), x1 - x0, y1 - y0,
transform=transform, **kwargs))
return None
def add_snip_axes(fig, grid_kwargs):
grid = fig.add_gridspec(**grid_kwargs)
axes = np.zeros((grid.nrows, grid.ncols), dtype=object)
for i, j in product(range(grid.nrows), range(grid.ncols)):
axes[i, j] = fig.add_subplot(grid[i, j])
[hide_axis(ax, 'left') for ax in axes.flatten()]
[hide_axis(ax, 'bottom') for ax in axes.flatten()]
return axes
def plot_snippets(axes, time, snippets, ymin=None, ymax=None, **kwargs):
ymin, ymax = ylimits(snippets, minval=ymin, maxval=ymax, pad=0.05)
for ax, snippet in zip(axes, snippets.T):
plot_line(ax, time, snippet, ymin=ymin, ymax=ymax, **kwargs)
return None
def plot_bi_snippets(axes, time, binary, **kwargs):
for ax, binary in zip(axes, binary.T):
plot_barcode(ax, time, binary[:, None], **kwargs)
return None
# GENERAL SETTINGS:
target = 'Omocestus_rufipes'
data_paths = search_files(target, excl='noise', dir='../data/inv/thresh_lp/')
stages = ['conv', 'bi', 'feat']
files = stages + ['scales', 'example_scales', 'measure_conv', 'spread_conv',
'measure_feat', 'spread_feat']
save_path = '../figures/fig_invariance_thresh_lp.pdf'
# GRAPH SETTINGS:
fig_kwargs = dict(
figsize=(32/2.54, 16/2.54),
)
super_grid_kwargs = dict(
nrows=2,
ncols=2,
wspace=0,
hspace=0,
left=0,
right=1,
bottom=0,
top=1
)
subfig_specs = dict(
pure=(0, 0),
noise=(1, 0),
analysis=(slice(None), 1)
)
pure_grid_kwargs = dict(
nrows=len(stages),
ncols=None,
wspace=0.05,
hspace=0.1,
left=0.13,
right=0.95,
bottom=0.15,
top=0.9
)
noise_grid_kwargs = dict(
nrows=len(stages),
ncols=None,
wspace=0.05,
hspace=0.1,
left=0.13,
right=0.95,
bottom=0.15,
top=0.9
)
analysis_grid_kwargs = dict(
nrows=1,
ncols=1,
wspace=0,
hspace=0,
left=0.15,
right=0.96,
bottom=0.1,
top=0.95
)
snip_specs = dict(
conv=(0, slice(None)),
bi=(1, slice(None)),
feat=(2, slice(None))
)
# PLOT SETTINGS:
colors = load_colors('../data/stage_colors.npz')
lw_snippets = 0.5
lw_analysis = 3
xlabels = dict(
analysis='scale $\\alpha$',
)
xlab_analysis_kwargs = dict(
y=0.01,
fontsize=16,
ha='center',
va='bottom',
)
ylabels = dict(
conv='$c_i$',
bi='$b_i$',
feat='$f_i$',
analysis='ratio $\\text{SD}_{\\alpha}\\,/\\,\\text{SD}_{\\min[\\alpha]}$',
# analysis='ratio $\\sigma_{\\alpha}\\,/\\,\\sigma_{\\min[\\alpha]}$',
)
ylab_snip_kwargs = dict(
x=0.01,
fontsize=20,
rotation=0,
ha='left',
va='center',
)
ylab_analysis_kwargs = dict(
x=0.02,
fontsize=16,
ha='center',
va='top',
)
xloc = dict(
analysis=10,
)
letter_snip_kwargs = dict(
x=0.02,
y=1,
ha='left',
va='top',
fontsize=22,
fontweight='bold'
)
letter_analysis_kwargs = dict(
x=0,
y=1,
ha='left',
va='top',
fontsize=22,
fontweight='bold'
)
bar_time = 5
bar_kwargs = dict(
y0=0.5,
y1=0.6,
color='k',
lw = 0,
)
spread_kwargs = dict(
alpha=0.3,
lw=0,
zorder=0
)
kernel_ind = 0
# EXECUTION:
for data_path in data_paths:
print(f'Processing {data_path}')
# Load invariance data:
pure_data, config = load_data(data_path, files)
noise_data, _ = load_data(data_path.replace('.npz', '_noise.npz'), files)
t_full = np.arange(pure_data['conv'].shape[0]) / config['env_rate']
# Reduce snippet data to kernel subset:
pure_data['conv'] = pure_data['conv'][:, kernel_ind]
pure_data['bi'] = pure_data['bi'][:, kernel_ind]
pure_data['feat'] = pure_data['feat'][:, kernel_ind]
noise_data['conv'] = noise_data['conv'][:, kernel_ind]
noise_data['bi'] = noise_data['bi'][:, kernel_ind]
noise_data['feat'] = noise_data['feat'][:, kernel_ind]
# Prepare overall graph:
fig = plt.figure(**fig_kwargs)
super_grid = fig.add_gridspec(**super_grid_kwargs)
# Prepare pure-song snippet axes:
pure_subfig = fig.add_subfigure(super_grid[subfig_specs['pure']])
pure_grid_kwargs['nrows' if pure_grid_kwargs['nrows'] is None else 'ncols'] = pure_data['example_scales'].size
pure_axes = add_snip_axes(pure_subfig, pure_grid_kwargs)
for ax, stage in zip(pure_axes[:, 0], stages):
ylabel(ax, ylabels[stage], **ylab_snip_kwargs,
transform=pure_subfig.transSubfigure)
for ax, scale in zip(pure_axes[snip_specs['conv']], pure_data['example_scales']):
ax.set_title(f'$\\alpha={strip_zeros(scale)}$')
pure_subfig.text(s='a', **letter_snip_kwargs)
# Prepare noise-song snippet axes:
noise_subfig = fig.add_subfigure(super_grid[subfig_specs['noise']])
noise_grid_kwargs['nrows' if noise_grid_kwargs['nrows'] is None else 'ncols'] = noise_data['example_scales'].size
noise_grid = noise_subfig.add_gridspec(**noise_grid_kwargs)
noise_axes = add_snip_axes(noise_subfig, noise_grid_kwargs)
for ax, stage in zip(noise_axes[:, 0], stages):
ylabel(ax, ylabels[stage], **ylab_snip_kwargs,
transform=noise_subfig.transSubfigure)
for ax, scale in zip(noise_axes[snip_specs['conv']], noise_data['example_scales']):
ax.set_title(f'$\\alpha={strip_zeros(scale)}$')
noise_subfig.text(s='b', **letter_snip_kwargs)
# Prepare analysis axis:
analysis_subfig = fig.add_subfigure(super_grid[subfig_specs['analysis']])
analysis_grid = analysis_subfig.add_gridspec(**analysis_grid_kwargs)
analysis_ax = analysis_subfig.add_subplot(analysis_grid[0, 0])
analysis_ax.set_xlim(noise_data['scales'].min(), noise_data['scales'].max())
analysis_ax.xaxis.set_major_locator(plt.MultipleLocator(xloc['analysis']))
xlabel(analysis_ax, xlabels['analysis'], **xlab_analysis_kwargs,
transform=analysis_subfig.transSubfigure)
# analysis_ax.set_yscale('log')
ylabel(analysis_ax, ylabels['analysis'], **ylab_analysis_kwargs,
transform=analysis_subfig.transSubfigure)
analysis_subfig.text(s='c', **letter_analysis_kwargs)
# Plot pure-song kernel response snippets:
plot_snippets(pure_axes[snip_specs['conv']], t_full, pure_data['conv'],
ymin=0, c=colors['conv'], lw=lw_snippets)
# Plot pure-song binary snippets:
plot_bi_snippets(pure_axes[snip_specs['bi']], t_full, pure_data['bi'],
color=colors['bi'], lw=0)
# Plot pure-song feature snippets:
plot_snippets(pure_axes[snip_specs['feat']], t_full, pure_data['feat'],
c=colors['feat'], lw=lw_snippets)
# Indicate time scale:
time_bar(pure_axes[snip_specs['conv']][0], bar_time, **bar_kwargs)
# Plot noise-song kernel response snippets:
plot_snippets(noise_axes[snip_specs['conv']], t_full, noise_data['conv'],
ymin=0, c=colors['conv'], lw=lw_snippets)
# Plot noise-song binary snippets:
plot_bi_snippets(noise_axes[snip_specs['bi']], t_full, noise_data['bi'],
color=colors['bi'], lw=0)
# Plot noise-song feature snippets:
plot_snippets(noise_axes[snip_specs['feat']], t_full, noise_data['feat'],
c=colors['feat'], lw=lw_snippets)
# Indicate time scale:
time_bar(noise_axes[snip_specs['conv']][0], bar_time, **bar_kwargs)
# Plot noise-song SD ratios (limited):
analysis_ax.plot(noise_data['scales'], noise_data['measure_conv'],
c=colors['conv'], lw=lw_analysis)
lower, upper = noise_data['spread_conv']
analysis_ax.fill_between(noise_data['scales'], lower, upper,
color=colors['conv'], **spread_kwargs)
analysis_ax.plot(noise_data['scales'], noise_data['measure_feat'],
c=colors['feat'], lw=lw_analysis)
lower, upper = noise_data['spread_feat']
analysis_ax.fill_between(noise_data['scales'], lower, upper,
color=colors['feat'], **spread_kwargs)
if save_path is not None:
fig.savefig(save_path)
plt.show()
print('Done.')
embed()

View File

@@ -69,14 +69,22 @@ def ylimits(signal, ax=None, minval=None, maxval=None, pad=0.05):
return ax.set_ylim(limits)
return limits
def xlabel(ax, label, y=-0.1, fontsize=20, **kwargs):
def xlabel(ax, label, x=None, y=-0.1, fontsize=20, transform=None, **kwargs):
ax.set_xlabel(label, fontsize=fontsize, **kwargs)
ax.xaxis.set_label_coords(0.5, y)
if x is None:
x = 0.5
if transform is not None:
x = (ax.transAxes + transform.inverted()).transform((x, 0))[0]
ax.xaxis.set_label_coords(x, y, transform=transform)
return None
def ylabel(ax, label, x=-0.2, fontsize=20, **kwargs):
def ylabel(ax, label, x=-0.2, y=None, fontsize=20, transform=None, **kwargs):
ax.set_ylabel(label, fontsize=fontsize, **kwargs)
ax.yaxis.set_label_coords(x, 0.5)
if y is None:
y = 0.5
if transform is not None:
y = (ax.transAxes + transform.inverted()).transform((0, y))[1]
ax.yaxis.set_label_coords(x, y, transform=transform)
return None
def super_xlabel(label, fig, high_ax, low_ax, y=0.005, **kwargs):
@@ -136,3 +144,13 @@ def reorder_traces(handles, signal, zlow=2, zhigh=2.5):
handles[ind].set_zorder(z)
return None
def strip_zeros(num, right_digits=5):
if isinstance(num, int):
return num
num = f'{num:.{right_digits}f}'
left, right = num.split('.')
right = right.rstrip('0')
if right:
return f'{left}.{right}'
return left

View File

@@ -13,7 +13,7 @@ mpl.rcParams['font.size'] = 14
mpl.rcParams['figure.titlesize'] = 15
mpl.rcParams['figure.labelsize'] = 14
mpl.rcParams['axes.labelsize'] = 14
mpl.rcParams['axes.titlesize'] = 14
mpl.rcParams['axes.titlesize'] = 16
mpl.rcParams['xtick.labelsize'] = 13
mpl.rcParams['ytick.labelsize'] = 13
mpl.rcParams['legend.fontsize'] = 14

View File

@@ -1,7 +1,5 @@
import plotstyle_plt
import glob
import numpy as np
import matplotlib.pyplot as plt
from thunderhopper.modeltools import load_data, save_data
from thunderhopper.filetools import crop_paths
from thunderhopper.filters import decibel, sosfilter
@@ -14,111 +12,73 @@ data_paths = glob.glob(f'../data/processed/{target}*.npz')
save_path = '../data/inv/log_hp/'
# ANALYSIS SETTINGS:
scales = np.arange(0, 10.1, 0.1)
save_scales = np.array([0, 0.5, 1, 2, 5, 10])
floor_percents = dict(song=100, noise=99)
add_noise = False
single_db_ref = True
plot_redundant = False
# find_saturation = add_noise and False
example_scales = np.array([0, 0.1, 1, 10, 100, 200])
scales = np.geomspace(0.1, 1000, 100)
if not add_noise:
example_scales = example_scales[example_scales > 0]
scales = np.unique(np.concatenate((scales, example_scales)))
# if find_saturation:
# scales = np.append(scales, 10e10)
# EXECUTION:
for data_path, name in zip(data_paths, crop_paths(data_paths)):
print(f'Processing {name}')
# Load song envelope:
# Get normalized song envelope:
data, config = load_data(data_path, files='env')
song, rate = data['env'], config['env_rate']
t_full = np.arange(song.shape[0]) / rate
# Generate noise envelope:
rng = np.random.default_rng()
noise = rng.normal(size=song.shape)
noise = extract_env(noise, rate, config=config)
# Normalize each:
song /= song.std()
noise /= noise.std()
# Mix song component and noise component:
scaled = song[:, None] * scales[None, :]
mix = scaled + noise[:, None]
# Rescale song component:
mix = song[:, None] * scales[None, :]
# Find noise floor (experimental):
song_percent = np.percentile(scaled, floor_percents['song'], axis=0)
noise_percent = np.percentile(noise, floor_percents['noise'])
floor_scale = scales[np.nonzero(song_percent <= noise_percent)[0][-1]]
if add_noise:
# Add normalized noise envelope:
rng = np.random.default_rng()
noise = rng.normal(size=song.shape)
noise = extract_env(noise, rate, config=config)
noise /= noise.std()
mix += noise[:, None]
# Process mixture:
mix_log = decibel(mix, axis=None if single_db_ref else 0)
mix_inv = sosfilter(mix_log, rate, config['inv_fcut'], 'hp',
padtype='constant', padlen=config['padlen'])
# Get variances per stage:
var_env = mix.var(axis=0)
var_log = mix_log.var(axis=0)
var_inv = mix_inv.var(axis=0)
# Get "intensity measure" per stage:
measure_env = mix.std(axis=0)
measure_log = mix_log.std(axis=0)
measure_inv = mix_inv.std(axis=0)
# Get SNRs against pure noise per stage:
base_ind = np.nonzero(scales == 0)[0][0]
snr_env = var_env / var_env[base_ind]
snr_log = var_log / var_log[base_ind]
snr_inv = var_inv / var_inv[base_ind]
if plot_redundant:
# Normalize SNRs:
norm_snr_env = snr_env / snr_env.max()
norm_snr_log = snr_log / snr_log.max()
norm_snr_inv = snr_inv / snr_inv.max()
# Get SNR gain against env:
gain_log = snr_log / snr_env
gain_inv = snr_inv / snr_env
# Plot results:
fig, axes = plt.subplots(6, 1, sharex=True, layout='constrained')
axes[0].set_title('variance')
axes[0].plot(scales, var_env)
axes[0].plot(scales, var_log)
axes[0].plot(scales, var_inv)
axes[1].set_title('normalized variance')
axes[1].plot(scales, var_env / var_env.max())
axes[1].plot(scales, var_log / var_log.max())
axes[1].plot(scales, var_inv / var_inv.max())
axes[2].set_title('SNR')
axes[2].plot(scales, snr_env)
axes[2].plot(scales, snr_log)
axes[2].plot(scales, snr_inv)
axes[3].set_title('normalized SNR')
axes[3].plot(scales, norm_snr_env)
axes[3].plot(scales, norm_snr_log)
axes[3].plot(scales, norm_snr_inv)
axes[4].set_title('gain (absolute SNR)')
axes[4].plot(scales, gain_log)
axes[4].plot(scales, gain_inv)
axes[5].set_title('gain (normalized SNR)')
axes[5].plot(scales, norm_snr_log / norm_snr_env)
axes[5].plot(scales, norm_snr_inv / norm_snr_env)
plt.show()
# # Find saturation level:
# if find_saturation:
# limit = measure_inv[-1]
# scales = scales[:-1]
# measure_env = measure_env[:-1]
# measure_log = measure_log[:-1]
# measure_inv = measure_inv[:-1]
# Save analysis results:
save_inds = np.isin(scales, save_scales)
save_inds = np.nonzero(np.isin(scales, example_scales))[0]
if save_path is not None:
data = dict(
scales=scales,
plot_scales=scales[save_inds],
floor_scale=floor_scale,
example_scales=example_scales,
env=mix[:, save_inds],
log=mix_log[:, save_inds],
inv=mix_inv[:, save_inds],
snr_env=snr_env,
snr_log=snr_log,
snr_inv=snr_inv,
measure_env=measure_env,
measure_log=measure_log,
measure_inv=measure_inv,
)
save_data(save_path + name, data, config, overwrite=True)
# if find_saturation:
# data['limit'] = limit
file_name = save_path + name
if add_noise:
file_name += '_noise'
save_data(file_name, data, config, overwrite=True)
print('Done.')
embed()

View File

@@ -0,0 +1,110 @@
import glob
import numpy as np
from thunderhopper.modeltools import load_data, save_data
from thunderhopper.filetools import crop_paths
from thunderhopper.filters import sosfilter
from IPython import embed
# GENERAL SETTINGS:
target = 'Omocestus_rufipes'
data_paths = glob.glob(f'../data/processed/{target}*.npz')
save_path = '../data/inv/thresh_lp/'
# ANALYSIS SETTINGS:
add_noise = False
threshold = 0.5
example_scales = np.array([threshold, 0.6, 1, 10, 50, 100])
scales = np.linspace(threshold + 0.1, 100, 100)
if not add_noise:
example_scales = example_scales[example_scales > threshold]
scales = np.unique(np.concatenate((scales, example_scales)))
# EXECUTION:
for data_path, name in zip(data_paths, crop_paths(data_paths)):
print(f'Processing {name}')
# Get normalized pure-song kernel responses:
data, config = load_data(data_path, files='conv')
song, rate = data['conv'], data['conv_rate']
song /= song.std(axis=0)
# Prepare kernel-specific thresholds:
threshold *= song.max(axis=0, keepdims=True)
if add_noise:
# Get normalized noise:
rng = np.random.default_rng()
noise = rng.normal(size=(song.shape[0], 1))
noise /= noise.std()
# Prepare snippet storage:
shape = song.shape + (example_scales.size,)
conv = np.zeros(shape, dtype=float)
bi = np.zeros(shape, dtype=float)
feat = np.zeros(shape, dtype=float)
# Prepare measure storage:
shape = (scales.size, song.shape[1])
measure_conv = np.zeros(shape, dtype=float)
measure_feat = np.zeros(shape, dtype=float)
# Execute piecewise:
for i, scale in enumerate(scales):
print('Simulating scale ', scale)
# Rescale song component:
scaled_conv = song * scale
if add_noise:
# Add noise:
scaled_conv += noise
# Process mixture:
scaled_bi = (scaled_conv > threshold).astype(float)
scaled_feat = sosfilter(scaled_bi, rate, config['feat_fcut'], 'lp',
padtype='fixed', padlen=config['padlen'])
# Log snippet data:
if scale in example_scales:
scale_ind = np.nonzero(example_scales == scale)[0][0]
conv[:, :, scale_ind] = scaled_conv
bi[:, :, scale_ind] = scaled_bi
feat[:, :, scale_ind] = scaled_feat
# Get "intensity measure" per stage:
measure_conv[i] = scaled_conv.std(axis=0)
measure_feat[i] = scaled_feat.mean(axis=0)
# Relate to smallest scale:
base_ind = np.argmin(scales)
measure_conv /= measure_conv[base_ind, :]
measure_feat /= measure_feat[base_ind, :]
# Condense measures across kernels:
spread_conv = np.zeros((2, scales.size))
spread_conv[0] = np.percentile(measure_conv, 25, axis=1)
spread_conv[1] = np.percentile(measure_conv, 75, axis=1)
measure_conv = np.median(measure_conv, axis=1)
spread_feat = np.zeros((2, scales.size))
spread_feat[0] = np.percentile(measure_feat, 25, axis=1)
spread_feat[1] = np.percentile(measure_feat, 75, axis=1)
measure_feat = np.median(measure_feat, axis=1)
# Save analysis results:
if save_path is not None:
data = dict(
scales=scales,
example_scales=example_scales,
conv=conv,
bi=bi,
feat=feat,
measure_conv=measure_conv,
spread_conv=spread_conv,
measure_feat=measure_feat,
spread_feat=spread_feat,
)
file_name = save_path + name
if add_noise:
file_name += '_noise'
save_data(file_name, data, config, overwrite=True)
print('Done.')
embed()