Skip to content

Commit 75235e0

Browse files
committed
Merge branch 'dev/normalization' into dev/general
2 parents b4a793e + 64865ce commit 75235e0

13 files changed

+799
-266
lines changed

docs/normalization/explore_FLODOG_parameters.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
# -*- coding: utf-8 -*-
2+
# ---
3+
# jupyter:
4+
# jupytext:
5+
# cell_metadata_filter: sdmix,incorrectly_encoded_metadata,spatial_window_scalar,size,title,-all
6+
# custom_cell_magics: kql
7+
# text_representation:
8+
# extension: .py
9+
# format_name: percent
10+
# format_version: '1.3'
11+
# jupytext_version: 1.11.2
12+
# kernelspec:
13+
# display_name: multyscale
14+
# language: python
15+
# name: python3
16+
# ---
17+
118
# %% [markdown]
219
# # Exploring FLODOG normalization parameter
320
# This guide describes how to change the normalization parameters
@@ -86,7 +103,7 @@
86103
plt.show()
87104

88105
# %% Output default model
89-
normalized_4_05 = FLODOG.normalize_outputs(filters_output)
106+
normalized_4_05 = FLODOG.normalize_outputs(filters_output, eps=1e-6)
90107
output_4_05 = np.sum(normalized_4_05, axis=(0, 1))
91108

92109
# %% [markdown]
@@ -127,7 +144,7 @@
127144
# FLODOG.normalization_weights = normalization_weights
128145

129146
# %% Determine normalizing coefficients
130-
normalizing_coefficients_3 = multyscale.normalization.normalizers(
147+
normalizing_coefficients_3 = multyscale.normalization.norm_coeffs(
131148
filters_output, normalization_weights
132149
)
133150

@@ -153,11 +170,11 @@
153170
# (the $\sigma$ of the 2D Gaussian)
154171
# to the spatial scale of the filter being normalized.
155172
# This is controlled by a parameter `spatial_window_scalar`:
156-
# $\sigma = \mathrm{spatial_window_scalar} \times s$.
173+
# $\sigma = \texttt{spatial window scalar} \times s$.
157174
# To explore how, here we apply various parameterizations of this normalization
158175
# to the same stimulus image and corresponding (weighted) filter outputs.
159176

160-
# %% FLODOG with spatial_window_scalar = 2
177+
# %% FLODOG with spatial_window_scalar=2
161178
# Set parameter
162179
spatial_window_scalar = 2
163180
# FLODOG.spatial_window_scalar = spaial_window_scalar
@@ -169,7 +186,7 @@
169186
FLODOG.window_sigmas = window_sigmas
170187

171188
# Apply spatial averaging windows to normalizing coefficients
172-
energies_2_3 = FLODOG.normalizers_to_RMS(normalizing_coefficients_3)
189+
energies_2_3 = FLODOG.norm_energies(normalizing_coefficients_3, eps=1e-6)
173190

174191
# Visualize each energy estimate
175192
fig, axs = plt.subplots(*energies_2_3.shape[:2], sharex="all", sharey="all")
@@ -186,7 +203,7 @@
186203
plt.show()
187204

188205
# %% Output
189-
normalized_2_3 = filters_output / energies_2_3
206+
normalized_2_3 = filters_output / (energies_2_3 + 1e-6)
190207
output_2_3 = np.sum(normalized_2_3, axis=(0, 1))
191208

192209
# %% Comparing FLODOG outputs
@@ -233,7 +250,7 @@
233250
# to normalize the filter outputs accordingly.
234251

235252
# %% Normalize
236-
normalized_4_3 = FLODOG_4_3.normalize_outputs(filters_output)
253+
normalized_4_3 = FLODOG_4_3.normalize_outputs(filters_output, eps=1e-6)
237254

238255
# %% [markdown]
239256
# To then readout the final model prediction,
@@ -291,11 +308,11 @@
291308
# )
292309

293310
# %% Outputs
294-
output_2_05 = FLODOG_2_05.normalize_outputs(filters_output).sum((0, 1))
311+
output_2_05 = FLODOG_2_05.normalize_outputs(filters_output, eps=1e-6).sum((0, 1))
295312

296313
# %% LODOG, $\sigma=4$
297314
LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent, window_sigma=4)
298-
output_LODOG_4 = LODOG.normalize_outputs(filters_output).sum((0, 1))
315+
output_LODOG_4 = LODOG.normalize_outputs(filters_output, eps=1e-6).sum((0, 1))
299316

300317

301318
# %% [markdown]
@@ -312,7 +329,7 @@
312329
# - subtract the two means
313330
# - divide the whole model output by this difference
314331

315-
# %% Standardize such that effect size = 1
332+
# %% Standardize such that effect size=1
316333
mask = np.load("example_stimulus_mask.npy")
317334

318335

docs/normalization/explore_LODOG_parameter.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109
# and thus also only need to be calculated once for all the parameterizations explored here.
110110

111111
# %%
112-
normalizing_coefficients = LODOG.normalizers(filters_output)
112+
normalizing_coefficients = LODOG.norm_coeffs(filters_output)
113113

114114
# %% Gaussian spatial averaging window [markdown]
115115
# The spatial averaging window in the LODOG model is
@@ -128,9 +128,11 @@
128128

129129
assert np.all(window_sigmas == window_sigma)
130130

131-
spatial_filters = multyscale.normalization.spatial_avg_windows_gaussian(
132-
LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas
133-
)
131+
spatial_filters = np.ndarray(filters_output.shape)
132+
for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]):
133+
spatial_filters[o, s, :] = multyscale.normalization.spatial_kernel_gaussian(
134+
LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s, :]
135+
)
134136
plt.subplot(1, 2, 1)
135137
plt.imshow(spatial_filters[0, 0, ...], cmap="coolwarm", extent=visextent)
136138
plt.colorbar()
@@ -147,13 +149,13 @@
147149
plt.show()
148150

149151
# %% [markdown]
150-
# The model method `.normalizers_to_RMS()` automatically generates and applies
152+
# The model method `.norm_energies()` automatically generates and applies
151153
# these spatial averaging windows to the proved normalizing coeffiencts.
152154
# This produces an $O \times S$ set of locally ($Y \times X$) calculated energies,
153155
# one for each normalizaing coefficient.
154156

155157
# %%
156-
energies_4 = LODOG.normalizers_to_RMS(normalizing_coefficients)
158+
energies_4 = LODOG.norm_energies(normalizing_coefficients, eps=1e-6)
157159

158160
# Visualize each local energy
159161
fig, axs = plt.subplots(*energies_4.shape[:2], sharex="all", sharey="all")
@@ -203,10 +205,14 @@
203205
assert np.all(LODOG.window_sigmas == LODOG.window_sigma)
204206

205207
# %%
206-
spatial_filters = multyscale.normalization.spatial_avg_windows_gaussian(
207-
LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas
208-
)
209-
plt.subplot(1, 2, 1)
208+
spatial_filters = np.ndarray(filters_output.shape)
209+
for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]):
210+
spatial_filters[o, s, :] = multyscale.normalization.spatial_kernel_gaussian(
211+
LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s, :]
212+
)
213+
214+
# %%
215+
plt.subplot(1, 2, 1)
210216
plt.imshow(spatial_filters[0, 0, ...], cmap="coolwarm", extent=visextent)
211217
plt.colorbar()
212218
plt.subplot(1, 2, 2)
@@ -226,7 +232,7 @@
226232
# more locally restricted (smaller spatial averaging window) energies
227233

228234
# %%
229-
energies_1 = LODOG.normalizers_to_RMS(normalizing_coefficients)
235+
energies_1 = LODOG.norm_energies(normalizing_coefficients, eps=1e-6)
230236

231237
# Visualize each local energy
232238
fig, axs = plt.subplots(*energies_4.shape[:2], sharex="all", sharey="all")
@@ -242,7 +248,7 @@
242248
# as would be expected.
243249

244250
# %%
245-
normalized_outputs_1 = filters_output / energies_1
251+
normalized_outputs_1 = filters_output / (energies_1 + 1e-6)
246252

247253
# Visualize each normalized output
248254
vmin = min(np.min(normalized_outputs_4), np.min(normalized_outputs_1))

docs/normalization/normalization_FLODOG.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@
165165
# the filter outputs at these spatial scales.
166166

167167
# %% Normalizing coefficients
168-
normalizing_coefficients_LODOG = LODOG.normalizers(filters_output)
169-
normalizing_coefficients_FLODOG = FLODOG.normalizers(filters_output)
168+
normalizing_coefficients_LODOG = LODOG.norm_coeffs(filters_output)
169+
normalizing_coefficients_FLODOG = FLODOG.norm_coeffs(filters_output)
170170

171171
# Visualize each norm. coeff.
172172
vmin = min(np.min(normalizing_coefficients_LODOG), np.min(normalizing_coefficients_FLODOG))
@@ -215,12 +215,17 @@
215215
# both in space, and in _spatial scale_ / _**F**requency_.
216216

217217
# %% Spatial averaging window
218-
spatial_windows_LODOG = multyscale.normalization.spatial_avg_windows_gaussian(
219-
LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas
220-
)
221-
spatial_windows_FLODOG = multyscale.normalization.spatial_avg_windows_gaussian(
222-
FLODOG.bank.x, FLODOG.bank.y, FLODOG.window_sigmas
223-
)
218+
spatial_windows_LODOG = np.ndarray(filters_output.shape)
219+
for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]):
220+
spatial_windows_LODOG[o, s, :] = multyscale.normalization.spatial_kernel_gaussian(
221+
LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s, :]
222+
)
223+
224+
spatial_windows_FLODOG = np.ndarray(filters_output.shape)
225+
for o, s in np.ndindex(FLODOG.window_sigmas.shape[:2]):
226+
spatial_windows_FLODOG[o, s, :] = multyscale.normalization.spatial_kernel_gaussian(
227+
FLODOG.bank.x, FLODOG.bank.y, FLODOG.window_sigmas[o, s, :]
228+
)
224229

225230
# Visualize each spatial avg. window
226231
fig, axs = plt.subplots(2, spatial_windows_LODOG.shape[1], sharex="all", sharey="all")
@@ -244,8 +249,8 @@
244249
# that form the denominators of the normalization
245250

246251
# %% Energy estimates
247-
energies_LODOG = LODOG.normalizers_to_RMS(normalizing_coefficients_LODOG)
248-
energies_FLODOG = FLODOG.normalizers_to_RMS(normalizing_coefficients_FLODOG)
252+
energies_LODOG = LODOG.norm_energies(normalizing_coefficients_LODOG, eps=1e-6)
253+
energies_FLODOG = FLODOG.norm_energies(normalizing_coefficients_FLODOG, eps=1e-6)
249254

250255
# Visualize
251256
vmin = min(np.min(energies_LODOG), np.min(energies_FLODOG))

docs/normalization/normalization_LODOG.py

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@
172172
filters_output = ODOG.bank.apply(stimulus)
173173
weighted_outputs = ODOG.weight_outputs(filters_output)
174174

175-
norm_outputs = LODOG.normalize_outputs(weighted_outputs)
175+
norm_outputs = LODOG.normalize_outputs(weighted_outputs, eps=1e-6)
176176
output_LODOG = norm_outputs.sum(axis=(0, 1))
177177

178178
# %% Extract target prediction
@@ -261,10 +261,10 @@
261261
assert np.array_equal(norm_weights, LODOG.normalization_weights)
262262

263263
# %% Normalizing images as weighted combination (tensor dot-product) of filter outputs
264-
normalizing_coefficients = multyscale.normalization.normalizers(weighted_outputs, norm_weights)
264+
normalizing_coefficients = multyscale.normalization.norm_coeffs(weighted_outputs, norm_weights)
265+
assert np.array_equal(normalizing_coefficients, LODOG.norm_coeffs(weighted_outputs))
265266

266-
# Visualize each normalizing coefficient n_{o,s}, i.e.
267-
# the normalizer image for each individual filter f_{o,s}
267+
# Visualize each normalizing coefficient n_{o,s}, i.e. for each individual filter f_{o,s}
268268
fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all")
269269
for o, s in np.ndindex(normalizing_coefficients.shape[:2]):
270270
axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent)
@@ -346,46 +346,53 @@
346346
plt.show()
347347

348348
# %% [markdown]
349-
# The function `multyscale.normalization.spatial_avg_windows_gaussian()`
349+
# The function `multyscale.normalization.spatial_kernel_gaussian()`
350350
# generates a ( $O\times S$ set of) Gaussian filters $G$,
351-
# where each Gaussian $G_{o',s'}$ is used to locally average filter $f_{o',s'}$.
351+
# where each Gaussian spatial averaging window
352+
# $G_{o',s'}$ is used to locally average filter $f_{o',s'}$.
352353
# In the LODOG model, all $G$ are identical.
353354
#
354355
# The parameter $\sigma$ controls the spatial size of each $G(\sigma)$ Gaussian filter;
355356
# thus this function takes in $O \times S$ $\sigma_{o', s'}$.
356357
# In the LODOG model, all $\sigma_{o', s'}$ are identical.
357358

358-
# %% Spatial Gaussian
359-
sigmas = LODOG.window_sigmas
360-
G = multyscale.normalization.spatial_avg_windows_gaussian(LODOG.bank.x, LODOG.bank.y, sigmas)
359+
# %% All sigmas identical
360+
print(LODOG.window_sigmas)
361361

362-
assert np.array_equal(G[0, 0, :], window)
362+
# %% Generate Gaussian filters
363+
spatial_windows = np.ndarray(filters_output.shape)
364+
for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]):
365+
spatial_windows[o, s, :] = multyscale.normalization.spatial_kernel_gaussian(
366+
LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s, :]
367+
)
368+
369+
assert np.array_equal(spatial_windows[0, 0, :], window)
370+
assert np.array_equal(spatial_windows, LODOG.spatial_kernels())
363371

364372
idx = (2, 3)
365373

366-
plt.imshow(G[*idx], cmap="coolwarm", extent=visextent)
374+
plt.imshow(spatial_windows[*idx], cmap="coolwarm", extent=visextent)
367375
plt.show()
368376

369377
# %% [markdown]
370378
# Applying this Gaussian window gives the _local_ (estimate of) of energy
371379

372-
# %% Local RMS
373-
normalization_local_RMS = np.square(normalizing_coefficients.copy())
380+
# %% Local energy estimates
381+
normalization_local_energies = np.ndarray(normalizing_coefficients.shape)
374382
for o, s in np.ndindex(normalizing_coefficients.shape[:2]):
375-
normalization_local_RMS[o, s] = multyscale.filters.apply(
376-
window, normalization_local_RMS[o, s], padval=0
383+
coeff = normalizing_coefficients[o, s] ** 2
384+
energy = multyscale.filters.apply(
385+
spatial_windows[o, s], coeff, padval=0
377386
)
387+
energy = np.sqrt(energy + 1e-6) # minor offset to avoid negatives/0's
388+
normalization_local_energies[o, s, :] = energy
378389

379-
normalization_local_RMS = (
380-
np.sqrt(normalization_local_RMS + 1e-6) + 1e-6
381-
) # minor offset to avoid negatives/0's
382-
383-
assert np.array_equal(normalization_local_RMS, LODOG.normalizers_to_RMS(normalizing_coefficients))
390+
assert np.allclose(normalization_local_energies, LODOG.norm_energies(normalizing_coefficients, eps=1e-6))
384391

385392
# Visualize each local RMS
386-
fig, axs = plt.subplots(*normalization_local_RMS.shape[:2], sharex="all", sharey="all")
387-
for o, s in np.ndindex(normalization_local_RMS.shape[:2]):
388-
axs[o, s].imshow(normalization_local_RMS[o, s], cmap="coolwarm", extent=visextent)
393+
fig, axs = plt.subplots(*normalization_local_energies.shape[:2], sharex="all", sharey="all")
394+
for o, s in np.ndindex(normalization_local_energies.shape[:2]):
395+
axs[o, s].imshow(normalization_local_energies[o, s], cmap="coolwarm", extent=visextent)
389396
fig.supxlabel("Spatial scale/freq. $s'$")
390397
fig.supylabel("Orientation $o'$")
391398
plt.show()
@@ -420,14 +427,14 @@
420427
# get normalized quite differently.
421428
#
422429
# We now divide each filter(output) $f_{o',s'}$
423-
# by the spatial RMS of the normalizer coefficient $n_{o',s'}$:
430+
# by the spatial RMS of the normalizing coefficient $n_{o',s'}$:
424431
# $$f'_{o',s'} = \frac{f_{o',s'}}{RMS(n_{o',s'})}$$
425432

426433
# %% Divisive normalization
427434
# Since the local RMSs tensor is the same (O, S, X, Y) shape as the filter outputs
428435
# we can simply divide
429-
normalized_outputs = weighted_outputs / normalization_local_RMS
430-
assert np.array_equal(normalized_outputs, norm_outputs)
436+
normalized_outputs = weighted_outputs / (normalization_local_energies + 1e-6)
437+
assert np.allclose(normalized_outputs, norm_outputs)
431438

432439
# Visualize each normalized f'_{o',s'}
433440
fig, axs = plt.subplots(*normalized_outputs.shape[:2], sharex="all", sharey="all")
@@ -456,7 +463,7 @@
456463

457464
# %% Recombine
458465
recombined_outputs = np.sum(normalized_outputs, axis=(0, 1))
459-
assert np.array_equal(recombined_outputs, output_LODOG)
466+
assert np.allclose(recombined_outputs, output_LODOG)
460467

461468
plt.subplot(2, 2, 1)
462469
plt.imshow(recombined_outputs, cmap="coolwarm", extent=visextent)

docs/normalization/normalization_ODOG.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@
244244
] # add this filter to normalizing coefficient
245245

246246
# Plot each normalizing coefficient n_{o,s},
247-
# i.e., the normalizer image for each individual filter f_{o,s}
247+
# i.e., for each individual filter f_{o,s}
248248
fig, axs = plt.subplots(*norm_coeffs.shape[:2], sharex="all", sharey="all")
249249
for o, s in np.ndindex(norm_coeffs.shape[:2]):
250250
axs[o, s].imshow(
@@ -314,8 +314,8 @@
314314
normalization_weights, weighted_outputs, axes=([0, 1], [0, 1])
315315
)
316316

317-
# Visualize each normalizing coefficient n_{o,s}, i.e.
318-
# the normalizer image for each individual filter f_{o,s}
317+
# Visualize each normalizing coefficient n_{o,s},
318+
# i.e., for each individual filter f_{o,s}
319319
fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all")
320320
for o, s in np.ndindex(normalizing_coefficients.shape[:2]):
321321
axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent)
@@ -404,15 +404,15 @@
404404
# for constructing the normalizing coefficients
405405
# from such weights, and the filter outputs to be normalized.
406406
#
407-
# The function `multyscale.normalization.normalizers()` creates these coefficients,
407+
# The function `multyscale.normalization.norm_coeffs()` creates these coefficients,
408408
# and note that these are identical to the $N$ constructed above.
409409

410410
# %% Identical normalizing coefficients
411-
norm_coeffs = multyscale.normalization.normalizers(weighted_outputs, normalization_weights)
411+
norm_coeffs = multyscale.normalization.norm_coeffs(weighted_outputs, normalization_weights)
412412
assert np.allclose(norm_coeffs, normalizing_coefficients)
413413

414-
# Visualize each normalizing coefficient n_{o,s}, i.e.
415-
# the normalizer image for each individual filter f_{o,s}
414+
# Visualize each normalizing coefficient n_{o,s},
415+
# i.e. for each individual filter f_{o,s}
416416
fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all")
417417
for o, s in np.ndindex(normalizing_coefficients.shape[:2]):
418418
axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent)
@@ -459,7 +459,7 @@
459459
#
460460
# Thus, the normalized filter output $f'_{o', s'}$
461461
# is calculating by dividing each filter(output) $f_{o',s'}$
462-
# by the energy of the normalizer coefficient $n_{o',s'}$:
462+
# by the energy of the normalizing coefficient $n_{o',s'}$:
463463
# $$f'_{o',s'} = \frac{f_{o',s'}}{RMS(n_{o',s'})}$$
464464

465465
# %% Divisive normalization

0 commit comments

Comments
 (0)