From fc3d67c5f6afffb063c55a3ea154c70718fe253a Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 17:42:21 +0200 Subject: [PATCH 01/79] test(transplementation): rename --- test/{RHS_filters.py => RHS_implementation.py} | 0 test/conftest.py | 4 ++-- test/test_RHS_matlab.py | 12 +++++++----- test/test_apply.py | 4 ++-- test/test_filterbanks.py | 3 ++- test/test_filters.py | 12 +++++++----- 6 files changed, 20 insertions(+), 15 deletions(-) rename test/{RHS_filters.py => RHS_implementation.py} (100%) diff --git a/test/RHS_filters.py b/test/RHS_implementation.py similarity index 100% rename from test/RHS_filters.py rename to test/RHS_implementation.py diff --git a/test/conftest.py b/test/conftest.py index c23ed1e..95ba21a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,7 +2,7 @@ import numpy as np import pytest -import RHS_filters +import RHS_implementation from scipy import io filepath_matlab_output = os.path.abspath(__file__ + "../../odog_matlab.mat") @@ -11,7 +11,7 @@ @pytest.fixture() def rhs_bank(): # Create RHS filterbank from Python transplation - return RHS_filters.filterbank() + return RHS_implementation.filterbank() @pytest.fixture() diff --git a/test/test_RHS_matlab.py b/test/test_RHS_matlab.py index fe96729..85bdd33 100644 --- a/test/test_RHS_matlab.py +++ b/test/test_RHS_matlab.py @@ -1,7 +1,7 @@ # %% import matplotlib.pyplot as plt import numpy as np -import RHS_filters +import RHS_implementation def test_filterbank(rhs_bank, matlab_bank): @@ -20,7 +20,9 @@ def test_RHSconv_matlab(matlab_filteroutput, matlab_bank, stimulus): filters_output = np.empty(matlab_bank.shape) for i in range(matlab_bank.shape[0]): for j in range(matlab_bank.shape[1]): - filters_output[i, j, ...] = RHS_filters.ourconv(stimulus, matlab_bank[i, j, ...]) + filters_output[i, j, ...] = RHS_implementation.ourconv( + stimulus, matlab_bank[i, j, ...] + ) assert np.allclose(matlab_filteroutput, filters_output) @@ -30,7 +32,7 @@ def test_RHSconv_RHS(matlab_filteroutput, stimulus, rhs_bank): filters_output = np.empty(rhs_bank.shape) for i in range(rhs_bank.shape[0]): for j in range(rhs_bank.shape[1]): - filters_output[i, j, ...] = RHS_filters.ourconv(stimulus, rhs_bank[i, j, ...]) + filters_output[i, j, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[i, j, ...]) assert np.allclose(matlab_filteroutput, filters_output) @@ -40,7 +42,7 @@ def test_ODOG(stimulus, rhs_bank, output_odog_matlab): filters_output = np.empty(rhs_bank.shape) for i in range(rhs_bank.shape[0]): for j in range(rhs_bank.shape[1]): - filters_output[i, j, ...] = RHS_filters.ourconv(stimulus, rhs_bank[i, j, ...]) + filters_output[i, j, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[i, j, ...]) - output = RHS_filters.odog_normalize(filters_output) + output = RHS_implementation.odog_normalize(filters_output) assert np.allclose(output, output_odog_matlab) diff --git a/test/test_apply.py b/test/test_apply.py index 19c5b70..7075f6b 100644 --- a/test/test_apply.py +++ b/test/test_apply.py @@ -1,5 +1,5 @@ import numpy as np -import RHS_filters +import RHS_implementation from multyscale import filters @@ -30,7 +30,7 @@ def test_conv_apply(rhs_bank, stimulus): for i in range(rhs_bank.shape[0]): for j in range(rhs_bank.shape[1]): f = rhs_bank[i, j] - o_conv[i, j, ...] = RHS_filters.ourconv(stimulus, f) + o_conv[i, j, ...] = RHS_implementation.ourconv(stimulus, f) o_apply[i, j, ...] = filters.apply(stimulus, f, pad=True) assert np.allclose(o_conv, o_apply) diff --git a/test/test_filterbanks.py b/test/test_filterbanks.py index 8dbdfa1..25ae00a 100644 --- a/test/test_filterbanks.py +++ b/test/test_filterbanks.py @@ -1,6 +1,7 @@ import matplotlib.pyplot as plt import numpy as np import RHS_filters +import RHS_implementation from multyscale import filterbank @@ -35,4 +36,4 @@ def test_scale_weights(): bank = filterbank.RHS2007(shape, visextent) center_sigmas = np.array(bank.sigmas)[:, 0, 0] scale_weights = filterbank.scale_weights(center_sigmas, 0.1) - assert np.allclose(scale_weights, RHS_filters.w_val) + assert np.allclose(scale_weights, RHS_implementation.w_val) diff --git a/test/test_filters.py b/test/test_filters.py index d6e5a51..c101cdc 100644 --- a/test/test_filters.py +++ b/test/test_filters.py @@ -1,12 +1,12 @@ # %% import matplotlib.pyplot as plt import numpy as np -import RHS_filters +import RHS_implementation import multyscale # %% RHS bank -rhs_bank = RHS_filters.filterbank() +rhs_bank = RHS_implementation.filterbank() # %% Parameters of image shape = (1024, 1024) # filtershape in pixels @@ -26,7 +26,7 @@ def test_circular_Gaussian(): sigmas = np.array([1, 1]) * sigma1 f = multyscale.filters.gaussian2d(x, y, (sigmas[0], sigmas[1])) f = f / f.sum() - f_2 = RHS_filters.d2gauss(shape[0], sigmas[0] * 32, shape[1], sigmas[0] * 32, 0) + f_2 = RHS_implementation.d2gauss(shape[0], sigmas[0] * 32, shape[1], sigmas[0] * 32, 0) plt.subplot(2, 2, 1) plt.imshow(f) @@ -48,7 +48,9 @@ def test_elliptical_Gaussian(): sigmas = np.array([1, 1]) * np.array([sigma1, sigma2]) f = multyscale.filters.gaussian2d(x, y, (sigmas[0], sigmas[1]), orientation=orientation) f = f / f.sum() - f_2 = RHS_filters.d2gauss(shape[0], sigmas[0] * 32, shape[1], sigmas[1] * 32, orientation) + f_2 = RHS_implementation.d2gauss( + shape[0], sigmas[0] * 32, shape[1], sigmas[1] * 32, orientation + ) plt.subplot(2, 2, 1) plt.imshow(f) @@ -67,7 +69,7 @@ def test_ODOG(): orientation = 150 sigma3 = 2 sigmas = np.array([[1, 1], [1, 2]]) * sigma3 - rhs_odog = RHS_filters.odog(shape[0], shape[1], sigma3 * 32, orientation=orientation) + rhs_odog = RHS_implementation.odog(shape[0], shape[1], sigma3 * 32, orientation=orientation) multy_odog = multyscale.filters.odog(x, y, sigmas, orientation=(orientation, orientation)) plt.subplot(2, 2, 1) From 86c1866d6e5c9851db63a43348997d203dd7a75b Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 17:51:30 +0200 Subject: [PATCH 02/79] test(transplementation): explicit testing of transplementation --- ...S_matlab.py => test_RHS_implementation.py} | 32 ++++++++++++------- test/test_filterbanks.py | 1 - 2 files changed, 20 insertions(+), 13 deletions(-) rename test/{test_RHS_matlab.py => test_RHS_implementation.py} (56%) diff --git a/test/test_RHS_matlab.py b/test/test_RHS_implementation.py similarity index 56% rename from test/test_RHS_matlab.py rename to test/test_RHS_implementation.py index 85bdd33..e0e90f0 100644 --- a/test/test_RHS_matlab.py +++ b/test/test_RHS_implementation.py @@ -1,22 +1,30 @@ -# %% -import matplotlib.pyplot as plt +""" Test that Python transplementation matches original MATLAB output + +The original Robinson, Hammon, de Sa (2007) implementation of (F)(L)ODOG is in MATLAB. +The multyscale testsuite provides a Python "transplementation" of the algorithms of this +original MATLAB code. + +This module tests that the model output (and some in between steps) produced by this +Python transplementation matches numerically with the comparable output from original +MATLAB implementation + +The "ground-truth" MATLAB output should be provided in file output_MATLAB.mat, +which is accessed by the pytest fixtures in MATLAB_comparison.py. + +Note that this is selected output, for just a single stimulus. +""" + import numpy as np import RHS_implementation def test_filterbank(rhs_bank, matlab_bank): - # %% Visualise - for i in range(rhs_bank.shape[0]): - plt.subplot(rhs_bank.shape[0], 2, i * 2 + 1) - plt.imshow(rhs_bank[i, 6, ...]) - plt.subplot(rhs_bank.shape[0], 2, i * 2 + 2) - plt.imshow(rhs_bank[i, 6, ...]) - + """Python filterbank matches RHS MATLAB filterbank""" assert np.allclose(rhs_bank, matlab_bank) def test_RHSconv_matlab(matlab_filteroutput, matlab_bank, stimulus): - # RHS convolution with matlab filters matches matlab output + """Python convolution with RHS MATLAB filters matches RHS MATLAB filters output""" filters_output = np.empty(matlab_bank.shape) for i in range(matlab_bank.shape[0]): for j in range(matlab_bank.shape[1]): @@ -28,7 +36,7 @@ def test_RHSconv_matlab(matlab_filteroutput, matlab_bank, stimulus): def test_RHSconv_RHS(matlab_filteroutput, stimulus, rhs_bank): - # RHS convolution with python RHS filters matches matlab output + """Python convolution with Python filters matches RHS MATLAB filters output""" filters_output = np.empty(rhs_bank.shape) for i in range(rhs_bank.shape[0]): for j in range(rhs_bank.shape[1]): @@ -38,7 +46,7 @@ def test_RHSconv_RHS(matlab_filteroutput, stimulus, rhs_bank): def test_ODOG(stimulus, rhs_bank, output_odog_matlab): - # RHS convolution with python RHS filters matches matlab output + """Python ODOG normalization & output matches RHS MATLAB ODOG output""" filters_output = np.empty(rhs_bank.shape) for i in range(rhs_bank.shape[0]): for j in range(rhs_bank.shape[1]): diff --git a/test/test_filterbanks.py b/test/test_filterbanks.py index 25ae00a..837ae72 100644 --- a/test/test_filterbanks.py +++ b/test/test_filterbanks.py @@ -1,6 +1,5 @@ import matplotlib.pyplot as plt import numpy as np -import RHS_filters import RHS_implementation from multyscale import filterbank From 098921070ce7bed49b3a7a7a1ed5f3fbd03caf33 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 18:14:42 +0200 Subject: [PATCH 03/79] test(transplementation): lightly refactor --- test/test_RHS_implementation.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index e0e90f0..06df7b8 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -26,11 +26,8 @@ def test_filterbank(rhs_bank, matlab_bank): def test_RHSconv_matlab(matlab_filteroutput, matlab_bank, stimulus): """Python convolution with RHS MATLAB filters matches RHS MATLAB filters output""" filters_output = np.empty(matlab_bank.shape) - for i in range(matlab_bank.shape[0]): - for j in range(matlab_bank.shape[1]): - filters_output[i, j, ...] = RHS_implementation.ourconv( - stimulus, matlab_bank[i, j, ...] - ) + for o, s in np.ndindex(matlab_bank.shape[:2]): + filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, matlab_bank[o, s, ...]) assert np.allclose(matlab_filteroutput, filters_output) @@ -38,9 +35,8 @@ def test_RHSconv_matlab(matlab_filteroutput, matlab_bank, stimulus): def test_RHSconv_RHS(matlab_filteroutput, stimulus, rhs_bank): """Python convolution with Python filters matches RHS MATLAB filters output""" filters_output = np.empty(rhs_bank.shape) - for i in range(rhs_bank.shape[0]): - for j in range(rhs_bank.shape[1]): - filters_output[i, j, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[i, j, ...]) + for o, s in np.ndindex(rhs_bank.shape[:2]): + filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[o, s, ...]) assert np.allclose(matlab_filteroutput, filters_output) @@ -48,9 +44,8 @@ def test_RHSconv_RHS(matlab_filteroutput, stimulus, rhs_bank): def test_ODOG(stimulus, rhs_bank, output_odog_matlab): """Python ODOG normalization & output matches RHS MATLAB ODOG output""" filters_output = np.empty(rhs_bank.shape) - for i in range(rhs_bank.shape[0]): - for j in range(rhs_bank.shape[1]): - filters_output[i, j, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[i, j, ...]) + for o, s in np.ndindex(rhs_bank.shape[:2]): + filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[o, s, ...]) output = RHS_implementation.odog_normalize(filters_output) assert np.allclose(output, output_odog_matlab) From 8e1e1934fe23179df26ad6964377187e43b2cdf4 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 17:04:17 +0200 Subject: [PATCH 04/79] test: remove plotting from tests --- test/test_filterbanks.py | 6 ------ test/test_filters.py | 29 ----------------------------- 2 files changed, 35 deletions(-) diff --git a/test/test_filterbanks.py b/test/test_filterbanks.py index 837ae72..a5f392f 100644 --- a/test/test_filterbanks.py +++ b/test/test_filterbanks.py @@ -14,12 +14,6 @@ # %% Filterbank def test_filterbank(matlab_bank): multy_bank = filterbank.RHS2007((1024, 1024), visextent) - # Visualise filterbank - for i in range(multy_bank.filters.shape[0]): - plt.subplot(multy_bank.filters.shape[0], 2, i * 2 + 1) - plt.imshow(multy_bank.filters[i, 6, ...], extent=visextent) - plt.subplot(multy_bank.filters.shape[0], 2, i * 2 + 2) - plt.imshow(matlab_bank[i, 6, ...]) assert np.allclose(matlab_bank, multy_bank.filters) diff --git a/test/test_filters.py b/test/test_filters.py index c101cdc..79c84a3 100644 --- a/test/test_filters.py +++ b/test/test_filters.py @@ -1,5 +1,3 @@ -# %% -import matplotlib.pyplot as plt import numpy as np import RHS_implementation @@ -28,15 +26,6 @@ def test_circular_Gaussian(): f = f / f.sum() f_2 = RHS_implementation.d2gauss(shape[0], sigmas[0] * 32, shape[1], sigmas[0] * 32, 0) - plt.subplot(2, 2, 1) - plt.imshow(f) - plt.subplot(2, 2, 2) - plt.imshow(f_2) - plt.subplot(2, 2, 3) - plt.plot(f[512, :]) - plt.subplot(2, 2, 4) - plt.plot(f_2[512, :]) - assert np.allclose(f, f_2) @@ -52,15 +41,6 @@ def test_elliptical_Gaussian(): shape[0], sigmas[0] * 32, shape[1], sigmas[1] * 32, orientation ) - plt.subplot(2, 2, 1) - plt.imshow(f) - plt.subplot(2, 2, 2) - plt.imshow(f_2) - plt.subplot(2, 2, 3) - plt.plot(f[512, :]) - plt.subplot(2, 2, 4) - plt.plot(f_2[512, :]) - assert np.allclose(f, f_2) @@ -72,13 +52,4 @@ def test_ODOG(): rhs_odog = RHS_implementation.odog(shape[0], shape[1], sigma3 * 32, orientation=orientation) multy_odog = multyscale.filters.odog(x, y, sigmas, orientation=(orientation, orientation)) - plt.subplot(2, 2, 1) - plt.imshow(rhs_odog) - plt.subplot(2, 2, 2) - plt.imshow(multy_odog) - plt.subplot(2, 2, 3) - plt.plot(rhs_odog[512, :]) - plt.subplot(2, 2, 4) - plt.plot(multy_odog[512, :]) - assert np.allclose(rhs_odog, multy_odog) From c130c51bfcdae0cac915612d6feb36773f7be0c2 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Wed, 7 Jul 2021 18:05:12 +0200 Subject: [PATCH 05/79] test((F)LODOG): Fixtures for MATLAB output --- test/conftest.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/conftest.py b/test/conftest.py index 95ba21a..ec70cdd 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -34,3 +34,13 @@ def matlab_filteroutput(): @pytest.fixture() def output_odog_matlab(): return io.loadmat(filepath_matlab_output)["odog_output"] + + +@pytest.fixture() +def output_lodog_matlab(): + return io.loadmat(filepath_matlab_output)["lodog_output"] + + +@pytest.fixture() +def output_flodog_matlab(): + return io.loadmat(filepath_matlab_output)["flodog_output"] From 45b6f5bf40dbfcf297913852be143e08afb77e95 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 16:52:32 +0200 Subject: [PATCH 06/79] test: separate out fixtures for comparing to MATLAB output --- .gitignore | 2 +- test/MATLAB_comparison.py | 53 +++++++++++++++++++++++++++++++++++++++ test/conftest.py | 39 +--------------------------- test/test_apply.py | 20 +++++++-------- test/test_filterbanks.py | 9 +++---- test/test_models.py | 12 ++++----- 6 files changed, 75 insertions(+), 60 deletions(-) create mode 100644 test/MATLAB_comparison.py diff --git a/.gitignore b/.gitignore index ea72868..67434ef 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,4 @@ __pycache__/ multyscale.egg-info/ docs/_build/ -test/odog_matlab.mat +test/odog_MATLAB.mat diff --git a/test/MATLAB_comparison.py b/test/MATLAB_comparison.py new file mode 100644 index 0000000..1149daa --- /dev/null +++ b/test/MATLAB_comparison.py @@ -0,0 +1,53 @@ +import os + +import numpy as np +import pytest +from scipy import io + +filepath_MATLAB_output = os.path.abspath(__file__ + "../../odog_MATLAB.mat") + + +@pytest.fixture() +def MATLAB_visextent(): + # Visual extent, same convention as pyplot (Left, Right, Bottom, Top): + visextent = np.array([-0.5, 0.5, -0.5, 0.5]) * (1023 / 32) + # NOTE: RHS implementation doesn't actually use (-16,16,-16,16) + return visextent + + +@pytest.fixture() +def MATLAB_shape(): + # Shape (resolution) of image, filters (Y, X) + return (1024, 1024) + + +@pytest.fixture() +def MATLAB_bank(): + # Load RHS bank from MATLAB implementation + return np.array(io.loadmat(filepath_MATLAB_output)["filters"].tolist()) + + +@pytest.fixture() +def stimulus(): + return io.loadmat(filepath_MATLAB_output)["illusion"] + + +@pytest.fixture() +def MATLAB_filteroutput(): + # Load RHS bank from MATLAB implementation + return np.array(io.loadmat(filepath_MATLAB_output)["filter_response"].tolist()) + + +@pytest.fixture() +def output_odog_MATLAB(): + return io.loadmat(filepath_MATLAB_output)["odog_output"] + + +@pytest.fixture() +def output_lodog_MATLAB(): + return io.loadmat(filepath_MATLAB_output)["lodog_output"] + + +@pytest.fixture() +def output_flodog_MATLAB(): + return io.loadmat(filepath_MATLAB_output)["flodog_output"] diff --git a/test/conftest.py b/test/conftest.py index ec70cdd..f6f15e9 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,46 +1,9 @@ -import os - -import numpy as np import pytest import RHS_implementation -from scipy import io - -filepath_matlab_output = os.path.abspath(__file__ + "../../odog_matlab.mat") +from MATLAB_comparison import * @pytest.fixture() def rhs_bank(): # Create RHS filterbank from Python transplation return RHS_implementation.filterbank() - - -@pytest.fixture() -def matlab_bank(): - # Load RHS bank from MATLAB implementation - return np.array(io.loadmat(filepath_matlab_output)["filters"].tolist()) - - -@pytest.fixture() -def stimulus(): - return io.loadmat(filepath_matlab_output)["illusion"] - - -@pytest.fixture() -def matlab_filteroutput(): - # Load RHS bank from MATLAB implementation - return np.array(io.loadmat(filepath_matlab_output)["filter_response"].tolist()) - - -@pytest.fixture() -def output_odog_matlab(): - return io.loadmat(filepath_matlab_output)["odog_output"] - - -@pytest.fixture() -def output_lodog_matlab(): - return io.loadmat(filepath_matlab_output)["lodog_output"] - - -@pytest.fixture() -def output_flodog_matlab(): - return io.loadmat(filepath_matlab_output)["flodog_output"] diff --git a/test/test_apply.py b/test/test_apply.py index 7075f6b..b0ddc83 100644 --- a/test/test_apply.py +++ b/test/test_apply.py @@ -4,24 +4,24 @@ from multyscale import filters -def test_apply_matlab(matlab_filteroutput, matlab_bank, stimulus): - # multyscale apply with matlab filters matches matlab output - filters_output = np.empty(matlab_bank.shape) - for i in range(matlab_bank.shape[0]): - for j in range(matlab_bank.shape[1]): - filters_output[i, j, ...] = filters.apply(stimulus, matlab_bank[i, j, ...], pad=True) +def test_apply_MATLAB(MATLAB_filteroutput, MATLAB_bank, stimulus): + # multyscale apply with MATLAB filters matches MATLAB output + filters_output = np.empty(MATLAB_bank.shape) + for i in range(MATLAB_bank.shape[0]): + for j in range(MATLAB_bank.shape[1]): + filters_output[i, j, ...] = filters.apply(stimulus, MATLAB_bank[i, j, ...], pad=True) - assert np.allclose(matlab_filteroutput, filters_output) + assert np.allclose(MATLAB_filteroutput, filters_output) -def test_apply_RHS(matlab_filteroutput, rhs_bank, stimulus): - # multyscale apply with RHS filters matches matlab output +def test_apply_RHS(MATLAB_filteroutput, rhs_bank, stimulus): + # multyscale apply with RHS filters matches MATLAB output filters_output = np.empty(rhs_bank.shape) for i in range(rhs_bank.shape[0]): for j in range(rhs_bank.shape[1]): filters_output[i, j, ...] = filters.apply(stimulus, rhs_bank[i, j, ...], pad=True) - assert np.allclose(matlab_filteroutput, filters_output) + assert np.allclose(MATLAB_filteroutput, filters_output) def test_conv_apply(rhs_bank, stimulus): diff --git a/test/test_filterbanks.py b/test/test_filterbanks.py index a5f392f..13a091b 100644 --- a/test/test_filterbanks.py +++ b/test/test_filterbanks.py @@ -11,18 +11,17 @@ # NOTE: RHS implementation doesn't actually use (-16,16,-16,16) -# %% Filterbank -def test_filterbank(matlab_bank): +def test_filterbank(MATLAB_bank): multy_bank = filterbank.RHS2007((1024, 1024), visextent) - assert np.allclose(matlab_bank, multy_bank.filters) + assert np.allclose(MATLAB_bank, multy_bank.filters) -def test_filterbank_apply(stimulus, matlab_filteroutput): +def test_filterbank_apply(stimulus, MATLAB_filteroutput): multy_bank = filterbank.RHS2007((1024, 1024), visextent) multy_output = multy_bank.apply(stimulus) - assert np.allclose(matlab_filteroutput, multy_output) + assert np.allclose(MATLAB_filteroutput, multy_output) def test_scale_weights(): diff --git a/test/test_models.py b/test/test_models.py index abbcb0b..5f7ee1a 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -10,24 +10,24 @@ # %% Tests -def test_odog_output(output_odog_matlab, stimulus): +def test_odog_output(output_odog_MATLAB, stimulus): model = models.ODOG_RHS2007(stimulus.shape, visextent) output = model.apply(stimulus) - assert np.allclose(output, output_odog_matlab) + assert np.allclose(output, output_odog_MATLAB) -def test_lodog_output(stimulus, output_lodog_matlab): +def test_lodog_output(stimulus, output_lodog_MATLAB): window_sigma = 128 / 32 model = models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) output = model.apply(stimulus) - assert np.allclose(output, output_lodog_matlab) + assert np.allclose(output, output_lodog_MATLAB) -def test_flodog_output(stimulus, output_flodog_matlab): +def test_flodog_output(stimulus, output_flodog_MATLAB): windowSizeScalar = 4 m = 0.5 model = models.FLODOG_RHS2007(stimulus.shape, visextent) output = model.apply(stimulus) - assert np.allclose(output, output_flodog_matlab) + assert np.allclose(output, output_flodog_MATLAB) From b9d7c96256becf7283f9e52d75d1a7ec17d581a3 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 17:56:48 +0200 Subject: [PATCH 07/79] test(transplementation): filterbank, apply, use extracted fixtures --- test/test_RHS_implementation.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index 06df7b8..5e9ed6a 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -18,34 +18,34 @@ import RHS_implementation -def test_filterbank(rhs_bank, matlab_bank): +def test_filterbank(rhs_bank, MATLAB_bank): """Python filterbank matches RHS MATLAB filterbank""" - assert np.allclose(rhs_bank, matlab_bank) + assert np.allclose(rhs_bank, MATLAB_bank) -def test_RHSconv_matlab(matlab_filteroutput, matlab_bank, stimulus): +def test_RHSconv_MATLAB(MATLAB_filteroutput, MATLAB_bank, stimulus): """Python convolution with RHS MATLAB filters matches RHS MATLAB filters output""" - filters_output = np.empty(matlab_bank.shape) - for o, s in np.ndindex(matlab_bank.shape[:2]): - filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, matlab_bank[o, s, ...]) + filters_output = np.empty(MATLAB_bank.shape) + for o, s in np.ndindex(MATLAB_bank.shape[:2]): + filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, MATLAB_bank[o, s, ...]) - assert np.allclose(matlab_filteroutput, filters_output) + assert np.allclose(MATLAB_filteroutput, filters_output) -def test_RHSconv_RHS(matlab_filteroutput, stimulus, rhs_bank): +def test_RHSconv_RHS(MATLAB_filteroutput, stimulus, rhs_bank): """Python convolution with Python filters matches RHS MATLAB filters output""" filters_output = np.empty(rhs_bank.shape) for o, s in np.ndindex(rhs_bank.shape[:2]): filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[o, s, ...]) - assert np.allclose(matlab_filteroutput, filters_output) + assert np.allclose(MATLAB_filteroutput, filters_output) -def test_ODOG(stimulus, rhs_bank, output_odog_matlab): +def test_ODOG(stimulus, rhs_bank, output_odog_MATLAB): """Python ODOG normalization & output matches RHS MATLAB ODOG output""" filters_output = np.empty(rhs_bank.shape) for o, s in np.ndindex(rhs_bank.shape[:2]): filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[o, s, ...]) output = RHS_implementation.odog_normalize(filters_output) - assert np.allclose(output, output_odog_matlab) + assert np.allclose(output, output_odog_MATLAB) From ef0968393561cfb7229b7f06e325a1c87592870f Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 17:58:03 +0200 Subject: [PATCH 08/79] test(transplementation): models use extracted fixtures --- test/test_RHS_implementation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index 5e9ed6a..dd768bf 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -41,11 +41,11 @@ def test_RHSconv_RHS(MATLAB_filteroutput, stimulus, rhs_bank): assert np.allclose(MATLAB_filteroutput, filters_output) -def test_ODOG(stimulus, rhs_bank, output_odog_MATLAB): +def test_ODOG(output_odog_MATLAB, MATLAB_filteroutput): """Python ODOG normalization & output matches RHS MATLAB ODOG output""" - filters_output = np.empty(rhs_bank.shape) - for o, s in np.ndindex(rhs_bank.shape[:2]): - filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[o, s, ...]) - output = RHS_implementation.odog_normalize(filters_output) + # Normalize and read out + output = RHS_implementation.odog_normalize(MATLAB_filteroutput) + + # Compare assert np.allclose(output, output_odog_MATLAB) From f98655e2233e2c2449afa32d854a81be9f8a4d0c Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 21 Sep 2023 16:21:49 +0200 Subject: [PATCH 09/79] feat(filters,filterbank): `.apply()`-methods take `padval`-arg Also properly document `padval` default --- multyscale/filterbank.py | 20 ++++++++++++++++---- multyscale/filters.py | 14 +++++++------- multyscale/models.py | 4 +++- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/multyscale/filterbank.py b/multyscale/filterbank.py index 96f1e52..fe70a52 100644 --- a/multyscale/filterbank.py +++ b/multyscale/filterbank.py @@ -75,13 +75,18 @@ def __init__(self, sigmas: Sequence[Sequence[float]], x: np.ndarray, y: np.ndarr dog = filters.dog(x, y, sigma) self.filters[i, :, :] = dog - def apply(self, image: np.ndarray) -> np.ndarray: + def apply(self, image: np.ndarray, padval=0.5) -> np.ndarray: """Apply filterbank to given image Parameters ---------- image : numpy.ndarray Image matrix to be filtered + padval : float, optional + whether to pad the input image, by default 0.5. + If truthy, image will be padded equally around all borders + with the constant value in here + to be the size of the filter Returns ------- @@ -92,7 +97,7 @@ def apply(self, image: np.ndarray) -> np.ndarray: """ filters_output = np.empty(self.filters.shape) for i in range(self.filters.shape[0]): - filters_output[i, ...] = filters.apply(image, self.filters[i, ...], pad=True) + filters_output[i, ...] = filters.apply(image, self.filters[i, ...], padval=padval) return filters_output @@ -171,13 +176,18 @@ def __init__( odog = filters.odog(x, y, sigma, (angle, angle)) self.filters[i, j, :, :] = odog - def apply(self, image: np.ndarray) -> np.ndarray: + def apply(self, image: np.ndarray, padval=0.5) -> np.ndarray: """Apply filterbank to given image Parameters ---------- image : numpy.ndarray Image matrix to be filtered + padval : float, optional + whether to pad the input image, by default 0.5. + If truthy, image will be padded equally around all borders + with the constant value in here + to be the size of the filter Returns ------- @@ -190,7 +200,9 @@ def apply(self, image: np.ndarray) -> np.ndarray: filters_output = np.empty(self.filters.shape) for i in range(self.filters.shape[0]): for j in range(self.filters.shape[1]): - filters_output[i, j, ...] = filters.apply(image, self.filters[i, j, ...], pad=True) + filters_output[i, j, ...] = filters.apply( + image, self.filters[i, j, ...], padval=padval + ) return filters_output diff --git a/multyscale/filters.py b/multyscale/filters.py index 74c2128..d508504 100644 --- a/multyscale/filters.py +++ b/multyscale/filters.py @@ -10,7 +10,7 @@ # TODO: (Abstract) base class Filter with apply-method,... -def apply(image: np.ndarray, filt: np.ndarray, pad: bool = False) -> np.ndarray: +def apply(image: np.ndarray, filt: np.ndarray, padval: float = 0.5) -> np.ndarray: """Apply filter to image, optionally pad input Parameters @@ -19,10 +19,10 @@ def apply(image: np.ndarray, filt: np.ndarray, pad: bool = False) -> np.ndarray: image to filter filt : numpy.ndarray filter to use - pad : bool, optional - whether to pad the input image, by default False. - If true, input will be padded equally around all borders - with the constant value 0.5 + padval : float, optional + whether to pad the input image, by default 0.5. + If truthy, input will be padded equally around all borders + with the constant value in here to be the size of the filter, Returns @@ -32,7 +32,7 @@ def apply(image: np.ndarray, filt: np.ndarray, pad: bool = False) -> np.ndarray: """ # TODO: make method - if pad: + if padval: pad_vertical, pad_horizontal = np.array(filt.shape) padding = np.array( [ @@ -41,7 +41,7 @@ def apply(image: np.ndarray, filt: np.ndarray, pad: bool = False) -> np.ndarray: ], dtype="int", ) - pad_image = np.pad(image, padding, "constant", constant_values=0.5) + pad_image = np.pad(image, padding, "constant", constant_values=padval) filtered_image = signal.fftconvolve(pad_image, filt, mode="valid") else: filtered_image = signal.fftconvolve(image, filt, mode="same") diff --git a/multyscale/models.py b/multyscale/models.py index c5a2309..92a63c6 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -115,7 +115,9 @@ def normalizers_to_RMS(self, normalizers): normalizers_RMS = normalizers.copy() normalizers_RMS = np.square(normalizers_RMS) for o, s in np.ndindex(normalizers_RMS.shape[:2]): - normalizers_RMS[o, s] = filters.apply(spatial_avg_filters[o, s], normalizers_RMS[o, s]) + normalizers_RMS[o, s] = filters.apply( + spatial_avg_filters[o, s], normalizers_RMS[o, s], padval=0 + ) normalizers_RMS += 1e-6 normalizers_RMS = np.sqrt(normalizers_RMS) return normalizers_RMS From 456ea766e8a2d05d377576d72d708f0a7aa608d4 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 18:46:19 +0200 Subject: [PATCH 10/79] test(filters): test `padval` --- test/test_apply.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_apply.py b/test/test_apply.py index b0ddc83..88bcef4 100644 --- a/test/test_apply.py +++ b/test/test_apply.py @@ -9,7 +9,7 @@ def test_apply_MATLAB(MATLAB_filteroutput, MATLAB_bank, stimulus): filters_output = np.empty(MATLAB_bank.shape) for i in range(MATLAB_bank.shape[0]): for j in range(MATLAB_bank.shape[1]): - filters_output[i, j, ...] = filters.apply(stimulus, MATLAB_bank[i, j, ...], pad=True) + filters_output[i, j, ...] = filters.apply(stimulus, MATLAB_bank[i, j, ...], padval=0.5) assert np.allclose(MATLAB_filteroutput, filters_output) @@ -19,7 +19,7 @@ def test_apply_RHS(MATLAB_filteroutput, rhs_bank, stimulus): filters_output = np.empty(rhs_bank.shape) for i in range(rhs_bank.shape[0]): for j in range(rhs_bank.shape[1]): - filters_output[i, j, ...] = filters.apply(stimulus, rhs_bank[i, j, ...], pad=True) + filters_output[i, j, ...] = filters.apply(stimulus, rhs_bank[i, j, ...], padval=0.5) assert np.allclose(MATLAB_filteroutput, filters_output) @@ -31,6 +31,6 @@ def test_conv_apply(rhs_bank, stimulus): for j in range(rhs_bank.shape[1]): f = rhs_bank[i, j] o_conv[i, j, ...] = RHS_implementation.ourconv(stimulus, f) - o_apply[i, j, ...] = filters.apply(stimulus, f, pad=True) + o_apply[i, j, ...] = filters.apply(stimulus, f, padval=0.5) assert np.allclose(o_conv, o_apply) From 67ef44ef9cfe1b4a9a667cfda118647375afaedc Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 18:46:37 +0200 Subject: [PATCH 11/79] test(transplementation): `padval` --- test/RHS_implementation.py | 4 ++-- test/test_RHS_implementation.py | 8 ++++++-- test/test_apply.py | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/test/RHS_implementation.py b/test/RHS_implementation.py index b99c4d2..862881d 100644 --- a/test/RHS_implementation.py +++ b/test/RHS_implementation.py @@ -90,10 +90,10 @@ def filterbank(): # %% Convolution -def ourconv(image, filt): +def ourconv(image, filt, pad=0.5): # pad padded_size = np.array(image.shape) + np.array(filt.shape) - pad_img = pad_RHS(image, padded_size, padval=0.5) + pad_img = pad_RHS(image, padded_size, padval=pad) pad_filt = pad_RHS(filt, padded_size, padval=0) # Paul's slightly corrected version diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index dd768bf..1965bd8 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -27,7 +27,9 @@ def test_RHSconv_MATLAB(MATLAB_filteroutput, MATLAB_bank, stimulus): """Python convolution with RHS MATLAB filters matches RHS MATLAB filters output""" filters_output = np.empty(MATLAB_bank.shape) for o, s in np.ndindex(MATLAB_bank.shape[:2]): - filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, MATLAB_bank[o, s, ...]) + filters_output[o, s, ...] = RHS_implementation.ourconv( + stimulus, MATLAB_bank[o, s, ...], pad=0.5 + ) assert np.allclose(MATLAB_filteroutput, filters_output) @@ -36,7 +38,9 @@ def test_RHSconv_RHS(MATLAB_filteroutput, stimulus, rhs_bank): """Python convolution with Python filters matches RHS MATLAB filters output""" filters_output = np.empty(rhs_bank.shape) for o, s in np.ndindex(rhs_bank.shape[:2]): - filters_output[o, s, ...] = RHS_implementation.ourconv(stimulus, rhs_bank[o, s, ...]) + filters_output[o, s, ...] = RHS_implementation.ourconv( + stimulus, rhs_bank[o, s, ...], pad=0.5 + ) assert np.allclose(MATLAB_filteroutput, filters_output) diff --git a/test/test_apply.py b/test/test_apply.py index 88bcef4..29f4d0f 100644 --- a/test/test_apply.py +++ b/test/test_apply.py @@ -30,7 +30,7 @@ def test_conv_apply(rhs_bank, stimulus): for i in range(rhs_bank.shape[0]): for j in range(rhs_bank.shape[1]): f = rhs_bank[i, j] - o_conv[i, j, ...] = RHS_implementation.ourconv(stimulus, f) + o_conv[i, j, ...] = RHS_implementation.ourconv(stimulus, f, pad=0.5) o_apply[i, j, ...] = filters.apply(stimulus, f, padval=0.5) assert np.allclose(o_conv, o_apply) From 182b3e976094b8c81df47db5c34ad7d1d490b1d7 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 18:57:34 +0200 Subject: [PATCH 12/79] test(transplementation): `weight` --- test/RHS_implementation.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/RHS_implementation.py b/test/RHS_implementation.py index 862881d..45fb466 100644 --- a/test/RHS_implementation.py +++ b/test/RHS_implementation.py @@ -120,6 +120,17 @@ def unpad_RHS(pad_image, shape): return image +def weight(filter_responses): + weighted_responses = np.ndarray(filter_responses.shape) + + # loop over the orientations + for o in range(filter_responses.shape[0]): + # loop over spatial frequencies + for f in range(filter_responses.shape[1]): + weighted_responses[o, f] = filter_responses[o, f] * w_val[f] + return weighted_responses + + # %% Normalizations def odog_normalize(filter_responses): # to hold model output From 51412aa71956cf86027dc0a7895fb694297e922b Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 17 Feb 2022 18:17:42 +0100 Subject: [PATCH 13/79] test(ODOG): regression ODOG components Where possible compare components to MATLAB output; else, compare to RHS transplementation --- test/MATLAB_comparison.py | 2 +- test/test_ODOG.py | 40 +++++++++++++++++++++++++++++++++ test/test_RHS_implementation.py | 4 ++-- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 test/test_ODOG.py diff --git a/test/MATLAB_comparison.py b/test/MATLAB_comparison.py index 1149daa..84173b0 100644 --- a/test/MATLAB_comparison.py +++ b/test/MATLAB_comparison.py @@ -39,7 +39,7 @@ def MATLAB_filteroutput(): @pytest.fixture() -def output_odog_MATLAB(): +def output_ODOG_MATLAB(): return io.loadmat(filepath_MATLAB_output)["odog_output"] diff --git a/test/test_ODOG.py b/test/test_ODOG.py new file mode 100644 index 0000000..eb6c351 --- /dev/null +++ b/test/test_ODOG.py @@ -0,0 +1,40 @@ +# %% Imports +import numpy as np +import pytest +import RHS_implementation + +import multyscale + +# %% Parameters of image +# visual extent, same convention as pyplot: +visextent = np.array([-0.5, 0.5, -0.5, 0.5]) * (1023 / 32) +# NOTE: RHS implementation doesn't actually use (-16,16,-16,16) + + +# %% Model +@pytest.fixture +def model(stimulus): + return multyscale.models.ODOG_RHS2007(stimulus.shape, visextent) + + +# %% Tests +def test_filters(MATLAB_filteroutput, model, stimulus): + filter_output = model.bank.apply(stimulus) + + assert np.allclose(MATLAB_filteroutput, filter_output) + + +def test_weights(model): + assert np.allclose(model.scale_weights, RHS_implementation.w_val) + + +def test_normalize(output_ODOG_MATLAB, model, MATLAB_filteroutput): + filter_outputs = model.weight_outputs(MATLAB_filteroutput) + normed_outputs = model.normalize_outputs(filter_outputs) + output = np.sum(normed_outputs, (0, 1)) + assert np.allclose(output, output_ODOG_MATLAB) + + +def test_model_output(output_ODOG_MATLAB, model, stimulus): + output = model.apply(stimulus) + assert np.allclose(output, output_ODOG_MATLAB) diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index 1965bd8..669ffe4 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -45,11 +45,11 @@ def test_RHSconv_RHS(MATLAB_filteroutput, stimulus, rhs_bank): assert np.allclose(MATLAB_filteroutput, filters_output) -def test_ODOG(output_odog_MATLAB, MATLAB_filteroutput): +def test_ODOG(output_ODOG_MATLAB, MATLAB_filteroutput): """Python ODOG normalization & output matches RHS MATLAB ODOG output""" # Normalize and read out output = RHS_implementation.odog_normalize(MATLAB_filteroutput) # Compare - assert np.allclose(output, output_odog_MATLAB) + assert np.allclose(output, output_ODOG_MATLAB) From 25b807c4afb7e0e5c520f1d50e768f5b7696061e Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Wed, 25 Oct 2023 22:07:49 +0200 Subject: [PATCH 14/79] test(ODOG): fix use renamed fixtures --- test/test_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_models.py b/test/test_models.py index 5f7ee1a..6346117 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -10,10 +10,10 @@ # %% Tests -def test_odog_output(output_odog_MATLAB, stimulus): +def test_odog_output(output_ODOG_MATLAB, stimulus): model = models.ODOG_RHS2007(stimulus.shape, visextent) output = model.apply(stimulus) - assert np.allclose(output, output_odog_MATLAB) + assert np.allclose(output, output_ODOG_MATLAB) def test_lodog_output(stimulus, output_lodog_MATLAB): From e389437d0a9ae7f113e6e094e1310e356c151fed Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 16:29:37 +0200 Subject: [PATCH 15/79] test(LODOG, transplement): LODOG normalization Easier to test against subcomponents of LODOG in transplementation --- test/RHS_implementation.py | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/RHS_implementation.py b/test/RHS_implementation.py index 45fb466..50ad692 100644 --- a/test/RHS_implementation.py +++ b/test/RHS_implementation.py @@ -156,4 +156,83 @@ def odog_normalize(filter_responses): return modelOut +def lodog_RMS(this_norm, sig1, sr): + # square + img_sqr = this_norm**2 + + # create Gaussian mask + mask = lodog_mask(sig1, sr) + + # filter the image (using unit-sum mask --> mean) + filter_out = ourconv(img_sqr, mask, pad=0) + + # make sure there are no negative numbers due to fft inaccuracies. + filter_out = filter_out + 1e-6 + + # take the square root, last part of doing RMS + filter_out = np.sqrt(filter_out) + + filter_out += 1e-6 + return filter_out + + +def lodog_mask(sig1, sr=1, o=0): + # sig1= size of gaussian window in the direction of the filter + # sig2= size of gaussian window perpendicular to filter + sig2 = sig1 * sr + + # directed along main axis of filter + rot = orientations[o] * np.pi / 180 + + # create a unit volume gaussian for filtering + mask = d2gauss(model_x, sig1, model_y, sig2, rot) + mask = mask / mask.sum() + return mask + + # %% +def lodog_normalize(filter_responses, sig1, sr=1): + # normalizers + norms = lodog_normalizers(filter_responses) + + # lRMS + RMSs = lodog_RMSs(norms, sig1, sr) + + # loop over the orientations + normed_resps = np.ndarray(filter_responses.shape) + for o in range(filter_responses.shape[0]): + # loop over spatial frequencies + for f in range(filter_responses.shape[1]): + filter_out = RMSs[o, f] + normed_resps[o, f] = filter_responses[o, f] / filter_out + return normed_resps + + +def lodog_normalizers(filter_responses): + norms = np.zeros(filter_responses.shape) + + # loop over the orientations + for o in range(filter_responses.shape[0]): + this_norm = np.zeros(filter_responses.shape[-2:]) + + # loop over spatial frequencies to accumulate + for f in range(filter_responses.shape[1]): + this_norm += filter_responses[o, f] # * w_val[f] + + for f in range(filter_responses.shape[1]): + norms[o, f] = this_norm + return norms + + +def lodog_RMSs(norms, sig1, sr=1): + RMSs = np.ndarray(norms.shape) + # loop over the orientations + for o in range(norms.shape[0]): + # loop over spatial frequencies to accumulate + for f in range(norms.shape[1]): + RMSs[o, f] = lodog_RMS(norms[o, f], sig1, sr) + return RMSs + + +def flodog_normalize(filter_response, sigx, sr, sdmix): + pass From b8f2b2a46cb1931dbadd9d07a9a449456adf90f6 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 18:52:55 +0200 Subject: [PATCH 16/79] test(transplementation): test LODOG transplementation --- test/test_RHS_implementation.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index 669ffe4..137d148 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -53,3 +53,17 @@ def test_ODOG(output_ODOG_MATLAB, MATLAB_filteroutput): # Compare assert np.allclose(output, output_ODOG_MATLAB) + + +def test_LODOG(output_lodog_MATLAB, MATLAB_filteroutput): + """Python ODOG normalization & output matches RHS MATLAB LODOG output""" + + # Weight filteroutput by scale + filters_output = RHS_implementation.weight(MATLAB_filteroutput) + + # Normalize + normed_multi_responses = RHS_implementation.lodog_normalize(filters_output, sig1=128, sr=1) + + # Sum to accumulate model output + output = np.sum(normed_multi_responses, (0, 1)) + assert np.allclose(output, output_lodog_MATLAB) From 88a88c69bffd981a9ac79a9e6d175c9135c19421 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 20:01:11 +0200 Subject: [PATCH 17/79] test(LODOG): against transplement components --- test/test_LODOG.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/test_LODOG.py diff --git a/test/test_LODOG.py b/test/test_LODOG.py new file mode 100644 index 0000000..ed7e4d0 --- /dev/null +++ b/test/test_LODOG.py @@ -0,0 +1,74 @@ +# %% Imports +import numpy as np +import RHS_implementation + +import multyscale + +# %% Parameters of image +# visual extent, same convention as pyplot: +visextent = np.array([-0.5, 0.5, -0.5, 0.5]) * (1023 / 32) +# NOTE: RHS implementation doesn't actually use (-16,16,-16,16) +window_sigma = 128 / 32 + + +# %% +def test_lodog_filters(stimulus, MATLAB_filteroutput): + model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) + + lodog_filter_output = model.bank.apply(stimulus) + + assert np.allclose(MATLAB_filteroutput, lodog_filter_output) + + +def test_weights(stimulus): + model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) + assert np.allclose(model.scale_weights, RHS_implementation.w_val) + + +def test_lodog_normalizers(stimulus, MATLAB_filteroutput): + model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) + weighted_outputs = model.weight_outputs(MATLAB_filteroutput) + normalizers = model.normalizers(weighted_outputs) + RHS_norms = RHS_implementation.lodog_normalizers(weighted_outputs) + assert np.allclose(normalizers.shape, RHS_norms.shape) + assert np.allclose(normalizers, RHS_norms) + + +def test_lodog_mask(stimulus): + # Is the spatial (Gaussian) averaging window the same? + RHS_mask = RHS_implementation.lodog_mask(sig1=128) + model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) + spatial_avg_filters = multyscale.normalization.spatial_avg_windows_gaussian( + model.bank.x, model.bank.y, model.window_sigmas + ) + for o, s in np.ndindex(spatial_avg_filters.shape[:2]): + assert np.allclose(spatial_avg_filters[o, s], RHS_mask) + + +def test_lodog_RMS(stimulus): + model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) + filter_outputs = model.bank.apply(stimulus) + weighted_outputs = model.weight_outputs(filter_outputs) + normalizers = model.normalizers(weighted_outputs) + + RMSs = model.normalizers_to_RMS(normalizers) + RHS_RMSs = RHS_implementation.lodog_RMSs(normalizers, sig1=128) + assert np.allclose(RMSs, RHS_RMSs) + + +def test_normalized_outputs(stimulus): + model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) + filter_outputs = model.bank.apply(stimulus) + weighted_outputs = model.weight_outputs(filter_outputs) + + normed_outputs = model.normalize_outputs(weighted_outputs) + + RHS_normalized_outputs = RHS_implementation.lodog_normalize(weighted_outputs, sig1=128) + assert np.allclose(normed_outputs, RHS_normalized_outputs) + + +def test_lodog_output(stimulus, output_lodog_MATLAB): + model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) + + output = model.apply(stimulus) + assert np.allclose(output, output_lodog_MATLAB) From cba91225ed19851ccdb368d4d1cd11d3ace701bd Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 13:23:55 +0200 Subject: [PATCH 18/79] feat(LODOG): `window_sigma` as constructor param --- multyscale/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/multyscale/models.py b/multyscale/models.py index 92a63c6..d46b4f7 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -100,8 +100,8 @@ def apply(self, image): class LODOG_RHS2007(ODOG_RHS2007): # TODO: docstring - def __init__(self, shape, visextent): - self.window_sigma = 2 + def __init__(self, shape, visextent, window_sigma=2): + self.window_sigma = window_sigma self.window_sigmas = np.ones(shape=(6, 7, 2)) * self.window_sigma super().__init__(shape, visextent) From 80a51bde9f58b9b770b8a3992f1197a4d9addfb3 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 13:24:15 +0200 Subject: [PATCH 19/79] fix(LODOG) additional epsilon offset --- multyscale/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/multyscale/models.py b/multyscale/models.py index d46b4f7..762ed14 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -120,6 +120,7 @@ def normalizers_to_RMS(self, normalizers): ) normalizers_RMS += 1e-6 normalizers_RMS = np.sqrt(normalizers_RMS) + normalizers_RMS += 1e-6 return normalizers_RMS From bb2e1a7b91ffafd45788b378c3f2ba67c8761733 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 10 Feb 2022 12:46:23 +0100 Subject: [PATCH 20/79] fix(LODOG): pad normalizer filtering with `0`s (Rather than no padding) --- multyscale/filters.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/multyscale/filters.py b/multyscale/filters.py index d508504..1014fd1 100644 --- a/multyscale/filters.py +++ b/multyscale/filters.py @@ -32,19 +32,19 @@ def apply(image: np.ndarray, filt: np.ndarray, padval: float = 0.5) -> np.ndarra """ # TODO: make method - if padval: - pad_vertical, pad_horizontal = np.array(filt.shape) - padding = np.array( - [ - [pad_vertical / 2 - 1, pad_vertical / 2], - [pad_horizontal / 2 - 1, pad_horizontal / 2], - ], - dtype="int", - ) - pad_image = np.pad(image, padding, "constant", constant_values=padval) - filtered_image = signal.fftconvolve(pad_image, filt, mode="valid") - else: - filtered_image = signal.fftconvolve(image, filt, mode="same") + # if padval: + pad_vertical, pad_horizontal = np.array(filt.shape) + padding = np.array( + [ + [pad_vertical / 2 - 1, pad_vertical / 2], + [pad_horizontal / 2 - 1, pad_horizontal / 2], + ], + dtype="int", + ) + pad_image = np.pad(image, padding, "constant", constant_values=padval) + filtered_image = signal.fftconvolve(pad_image, filt, mode="valid") + # else: + # filtered_image = signal.fftconvolve(image, filt, mode="same") return filtered_image From e561811a9e00fcf4e5aa611a31580a61694cc29b Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Wed, 25 Oct 2023 22:12:22 +0200 Subject: [PATCH 21/79] test(LODOG): slightly rename fixtures --- test/MATLAB_comparison.py | 2 +- test/test_LODOG.py | 4 ++-- test/test_RHS_implementation.py | 4 ++-- test/test_models.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/MATLAB_comparison.py b/test/MATLAB_comparison.py index 84173b0..38968e9 100644 --- a/test/MATLAB_comparison.py +++ b/test/MATLAB_comparison.py @@ -44,7 +44,7 @@ def output_ODOG_MATLAB(): @pytest.fixture() -def output_lodog_MATLAB(): +def output_LODOG_MATLAB(): return io.loadmat(filepath_MATLAB_output)["lodog_output"] diff --git a/test/test_LODOG.py b/test/test_LODOG.py index ed7e4d0..478d437 100644 --- a/test/test_LODOG.py +++ b/test/test_LODOG.py @@ -67,8 +67,8 @@ def test_normalized_outputs(stimulus): assert np.allclose(normed_outputs, RHS_normalized_outputs) -def test_lodog_output(stimulus, output_lodog_MATLAB): +def test_lodog_output(stimulus, output_LODOG_MATLAB): model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) output = model.apply(stimulus) - assert np.allclose(output, output_lodog_MATLAB) + assert np.allclose(output, output_LODOG_MATLAB) diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index 137d148..ad47bc2 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -55,7 +55,7 @@ def test_ODOG(output_ODOG_MATLAB, MATLAB_filteroutput): assert np.allclose(output, output_ODOG_MATLAB) -def test_LODOG(output_lodog_MATLAB, MATLAB_filteroutput): +def test_LODOG(output_LODOG_MATLAB, MATLAB_filteroutput): """Python ODOG normalization & output matches RHS MATLAB LODOG output""" # Weight filteroutput by scale @@ -66,4 +66,4 @@ def test_LODOG(output_lodog_MATLAB, MATLAB_filteroutput): # Sum to accumulate model output output = np.sum(normed_multi_responses, (0, 1)) - assert np.allclose(output, output_lodog_MATLAB) + assert np.allclose(output, output_LODOG_MATLAB) diff --git a/test/test_models.py b/test/test_models.py index 6346117..fb4edf8 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -16,12 +16,12 @@ def test_odog_output(output_ODOG_MATLAB, stimulus): assert np.allclose(output, output_ODOG_MATLAB) -def test_lodog_output(stimulus, output_lodog_MATLAB): +def test_lodog_output(stimulus, output_LODOG_MATLAB): window_sigma = 128 / 32 model = models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) output = model.apply(stimulus) - assert np.allclose(output, output_lodog_MATLAB) + assert np.allclose(output, output_LODOG_MATLAB) def test_flodog_output(stimulus, output_flodog_MATLAB): From 3e5a3f5c2f21ccb66eaac8e970edf5c901bd204c Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 18 Feb 2022 10:28:34 +0100 Subject: [PATCH 22/79] feat(FLODOG): model params in object initialization --- multyscale/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/multyscale/models.py b/multyscale/models.py index 762ed14..ddfb697 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -127,10 +127,10 @@ def normalizers_to_RMS(self, normalizers): class FLODOG_RHS2007(LODOG_RHS2007): # TODO: docstring - def __init__(self, shape, visextent): + def __init__(self, shape, visextent, sdmix, spatial_window_scalar): super().__init__(shape, visextent) - self.sdmix = 0.5 # stdev of Gaussian weights for scale mixing + self.sdmix = sdmix # stdev of Gaussian weights for scale mixing self.scale_norm_weights = normalization.scale_norm_weights_gaussian( len(self.scale_weights), self.sdmix ) @@ -138,7 +138,7 @@ def __init__(self, shape, visextent): 6, 7, self.scale_norm_weights, self.orientation_norm_weights ) - self.spatial_window_scalar = 2 + self.spatial_window_scalar = spatial_window_scalar self.window_sigmas = np.broadcast_to( np.array(self.center_sigmas)[None, ..., None], (6, 7, 2) ) From 56958d855d6725fc4fb779169347ac6fd6c6a7be Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 26 Oct 2023 16:47:31 +0200 Subject: [PATCH 23/79] test(transplement): FLODOG normalization extract normalizers to subroutine reuse LODOG gaussian kernel separate spatial masks reuse LODOG RMS function extract local RMS subroutine separate read out from normalization default spatial averaging mask is circular rename fixture --- test/MATLAB_comparison.py | 2 +- test/RHS_implementation.py | 67 ++++++++++++++++++++++++++++++++- test/test_RHS_implementation.py | 21 +++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/test/MATLAB_comparison.py b/test/MATLAB_comparison.py index 38968e9..066fec4 100644 --- a/test/MATLAB_comparison.py +++ b/test/MATLAB_comparison.py @@ -49,5 +49,5 @@ def output_LODOG_MATLAB(): @pytest.fixture() -def output_flodog_MATLAB(): +def output_FLODOG_MATLAB(): return io.loadmat(filepath_MATLAB_output)["flodog_output"] diff --git a/test/RHS_implementation.py b/test/RHS_implementation.py index 50ad692..5374477 100644 --- a/test/RHS_implementation.py +++ b/test/RHS_implementation.py @@ -234,5 +234,68 @@ def lodog_RMSs(norms, sig1, sr=1): return RMSs -def flodog_normalize(filter_response, sigx, sr, sdmix): - pass +# %% FLODOG +def flodog_normalize(filter_responses, sigx, sdmix, sr=1): + # Normalizers are combination of filters, weighted by scale + norms = FLODOG_normalizers(filter_responses, sdmix) + + # local RMS of each normalizer through spatial filtering + RMSs = FLODOG_RMSs(norms, sigx, sr) + + # Divisively normalize + # loop over the orientations + normed_resps = np.ndarray(filter_responses.shape) + for o in range(filter_responses.shape[0]): + # loop over spatial frequencies + for f in range(filter_responses.shape[1]): + filter_out = RMSs[o, f] + normed_resps[o, f] = filter_responses[o, f] / filter_out + + return normed_resps + + +def FLODOG_normalizers(filter_responses, sdmix): + """Build weighted normalizers""" + + norms = np.zeros(filter_responses.shape) + + # loop over the orientations + for o in range(filter_responses.shape[0]): + # loop over spatial frequencies to accumulate + for f in range(filter_responses.shape[1]): + normalizer = 0 + area = 0 + for wf in range(len(stdev_pixels)): + gweight = gauss(f - wf, sdmix) + area = area + gweight + normalizer = normalizer + (filter_responses[o, wf] * gweight) + normalizer = normalizer / area + + norms[o, f, ...] = normalizer + + return norms + + +def FLODOG_masks(sigx, sr=1): + masks = np.ndarray((len(orientations), len(stdev_pixels), model_y, model_x)) + + for o in range(len(orientations)): + for f in range(len(stdev_pixels)): + sig1 = sigx * stdev_pixels[f] + + masks[o, f] = lodog_mask(sig1, sr) + + return masks + + +def FLODOG_RMSs(norms, sigx, sr=1): + RMSs = np.ndarray(norms.shape) + # loop over the orientations + for o in range(norms.shape[0]): + # loop over spatial frequencies to accumulate + for f in range(norms.shape[1]): + # localization extent along direction of filter - function of frequency + sig1 = sigx * stdev_pixels[f] + + RMSs[o, f] = lodog_RMS(norms[o, f], sig1, sr) + return RMSs diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index ad47bc2..35352e7 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -67,3 +67,24 @@ def test_LODOG(output_LODOG_MATLAB, MATLAB_filteroutput): # Sum to accumulate model output output = np.sum(normed_multi_responses, (0, 1)) assert np.allclose(output, output_LODOG_MATLAB) + + +def test_FLODOG(output_FLODOG_MATLAB, MATLAB_filteroutput): + """Python ODOG normalization & output matches RHS MATLAB FLODOG output""" + + # Weight filteroutput by scale + filters_output = RHS_implementation.weight(MATLAB_filteroutput) + + # Normalize + normed_outputs = RHS_implementation.flodog_normalize( + filters_output, + sigx=4, + sr=1, + sdmix=0.5, + ) + + # Sum normalized channel outputs, to read out model output + output = np.sum(normed_outputs, (0, 1)) + + # Compare + assert np.allclose(output, output_FLODOG_MATLAB) From d60a4a4a6090cc0d630e033cecbac52611b35484 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 26 Oct 2023 16:15:55 +0200 Subject: [PATCH 24/79] test((F)LODOG): normalization params (F)LODOG for MATLAB output as fixtures --- test/MATLAB_comparison.py | 14 ++++++++++++++ test/test_RHS_implementation.py | 27 +++++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/test/MATLAB_comparison.py b/test/MATLAB_comparison.py index 066fec4..2f1d3ab 100644 --- a/test/MATLAB_comparison.py +++ b/test/MATLAB_comparison.py @@ -51,3 +51,17 @@ def output_LODOG_MATLAB(): @pytest.fixture() def output_FLODOG_MATLAB(): return io.loadmat(filepath_MATLAB_output)["flodog_output"] + + +@pytest.fixture() +def MATLAB_LODOG_params(): + """Normalization parameters used to produce the MATLAB output for LODOG""" + LODOG_params = {"sig1": 128, "sr": 1} + return LODOG_params + + +@pytest.fixture() +def MATLAB_FLODOG_params(): + """Normalization parameters used to produce the MATLAB output for FLODOG""" + FLODOG_params = {"sigx": 4, "sr": 1, "sdmix": 0.5} + return FLODOG_params diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index 35352e7..8527c0c 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -55,36 +55,39 @@ def test_ODOG(output_ODOG_MATLAB, MATLAB_filteroutput): assert np.allclose(output, output_ODOG_MATLAB) -def test_LODOG(output_LODOG_MATLAB, MATLAB_filteroutput): - """Python ODOG normalization & output matches RHS MATLAB LODOG output""" +def test_LODOG(output_LODOG_MATLAB, MATLAB_filteroutput, MATLAB_LODOG_params): + """Python LODOG normalization & output matches RHS MATLAB LODOG output""" # Weight filteroutput by scale filters_output = RHS_implementation.weight(MATLAB_filteroutput) # Normalize - normed_multi_responses = RHS_implementation.lodog_normalize(filters_output, sig1=128, sr=1) + normed_multi_responses = RHS_implementation.lodog_normalize( + filters_output, + **MATLAB_LODOG_params, + ) - # Sum to accumulate model output + # Readout model output by summing normalized channel outputs output = np.sum(normed_multi_responses, (0, 1)) + + # Compare to MATLAB output assert np.allclose(output, output_LODOG_MATLAB) -def test_FLODOG(output_FLODOG_MATLAB, MATLAB_filteroutput): - """Python ODOG normalization & output matches RHS MATLAB FLODOG output""" +def test_FLODOG(output_FLODOG_MATLAB, MATLAB_filteroutput, MATLAB_FLODOG_params): + """Python FLODOG normalization & output matches RHS MATLAB FLODOG output""" # Weight filteroutput by scale filters_output = RHS_implementation.weight(MATLAB_filteroutput) # Normalize - normed_outputs = RHS_implementation.flodog_normalize( + normed_multi_responses = RHS_implementation.flodog_normalize( filters_output, - sigx=4, - sr=1, - sdmix=0.5, + **MATLAB_FLODOG_params, ) # Sum normalized channel outputs, to read out model output - output = np.sum(normed_outputs, (0, 1)) + output = np.sum(normed_multi_responses, (0, 1)) - # Compare + # Compare to MATLAB output assert np.allclose(output, output_FLODOG_MATLAB) From 24a1adc2129912e62666070c3a1c832adc86c203 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 18 Feb 2022 11:21:53 +0100 Subject: [PATCH 25/79] fix(FLODOG): local mean filters not correctly scaled --- multyscale/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multyscale/models.py b/multyscale/models.py index ddfb697..21cdab6 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -139,6 +139,6 @@ def __init__(self, shape, visextent, sdmix, spatial_window_scalar): ) self.spatial_window_scalar = spatial_window_scalar - self.window_sigmas = np.broadcast_to( + self.window_sigmas = self.spatial_window_scalar * np.broadcast_to( np.array(self.center_sigmas)[None, ..., None], (6, 7, 2) ) From 547245b6a9f79214df6de9f163e53b406cba921c Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 24 Oct 2023 14:25:58 +0200 Subject: [PATCH 26/79] fix(FLODOG): scale normalization weights should add up to 1 --- multyscale/normalization.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/multyscale/normalization.py b/multyscale/normalization.py index 46650c5..c9d8fd7 100644 --- a/multyscale/normalization.py +++ b/multyscale/normalization.py @@ -40,16 +40,16 @@ def scale_norm_weights_gaussian(n_scales, sdmix): rel_i = np.asarray(range(n_scales)) - s # Gaussian weights, based on relative index - scale_norm_weights[s, ...] = np.exp(-(rel_i**2) / (2 * sdmix**2)) / ( - sdmix * np.sqrt(2 * np.pi) - ) - # scale_norm_weights = scale_norm_weights / scale_norm_weights.sum((0)) + weights = np.exp(-(rel_i**2) / (2 * sdmix**2)) / (sdmix * np.sqrt(2 * np.pi)) + weights /= weights.sum() + scale_norm_weights[s, :] = weights + return scale_norm_weights def orientation_norm_weights(n_orientations): orientation_norm_weights = np.eye(n_orientations) - orientation_norm_weights = orientation_norm_weights / orientation_norm_weights.sum(0) + orientation_norm_weights /= orientation_norm_weights.sum(0) return orientation_norm_weights From 2553344e0cd87276ef172ef7737d75959dc1f64a Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 26 Oct 2023 17:01:03 +0200 Subject: [PATCH 27/79] test(FLODOG): test each component scale_norm_weights local averaging filters normalizers_to_RMS full normalization use renamed fixture --- test/RHS_implementation.py | 16 ++++--- test/test_FLODOG.py | 87 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 test/test_FLODOG.py diff --git a/test/RHS_implementation.py b/test/RHS_implementation.py index 5374477..62754ad 100644 --- a/test/RHS_implementation.py +++ b/test/RHS_implementation.py @@ -254,22 +254,28 @@ def flodog_normalize(filter_responses, sigx, sdmix, sr=1): return normed_resps +def FLODOG_normweights(sdmix): + weights = np.ndarray((len(stdev_pixels), len(stdev_pixels))) + for f in range(len(stdev_pixels)): + for wf in range(len(stdev_pixels)): + weights[f, wf] = gauss(f - wf, sdmix) + weights[f, :] /= weights[f, :].sum() + return weights + + def FLODOG_normalizers(filter_responses, sdmix): """Build weighted normalizers""" norms = np.zeros(filter_responses.shape) + weights = FLODOG_normweights(sdmix) # loop over the orientations for o in range(filter_responses.shape[0]): # loop over spatial frequencies to accumulate for f in range(filter_responses.shape[1]): normalizer = 0 - area = 0 for wf in range(len(stdev_pixels)): - gweight = gauss(f - wf, sdmix) - area = area + gweight - normalizer = normalizer + (filter_responses[o, wf] * gweight) - normalizer = normalizer / area + normalizer += filter_responses[o, wf] * weights[f, wf] norms[o, f, ...] = normalizer diff --git a/test/test_FLODOG.py b/test/test_FLODOG.py new file mode 100644 index 0000000..7b94904 --- /dev/null +++ b/test/test_FLODOG.py @@ -0,0 +1,87 @@ +# %% Imports +import numpy as np +import pytest +import RHS_implementation + +import multyscale + +# %% Parameters of image +# visual extent, same convention as pyplot: +visextent = np.array([-0.5, 0.5, -0.5, 0.5]) * (1023 / 32) +# NOTE: RHS implementation doesn't actually use (-16,16,-16,16) + + +# %% Model +@pytest.fixture +def model(stimulus, MATLAB_FLODOG_params): + return multyscale.models.FLODOG_RHS2007( + stimulus.shape, + visextent, + spatial_window_scalar=MATLAB_FLODOG_params["sigx"], + sdmix=MATLAB_FLODOG_params["sdmix"], + ) + + +# %% Tests +def test_filters(MATLAB_filteroutput, model, stimulus): + filter_output = model.bank.apply(stimulus) + + assert np.allclose(MATLAB_filteroutput, filter_output) + + +def test_weights(model): + assert np.allclose(model.scale_weights, RHS_implementation.w_val) + + +def test_scale_norm_weights(model, MATLAB_FLODOG_params): + RHS_weights = RHS_implementation.FLODOG_normweights(sdmix=MATLAB_FLODOG_params["sdmix"]) + assert np.allclose(RHS_weights.shape, model.scale_norm_weights.shape) + assert np.allclose(RHS_weights, model.scale_norm_weights) + + +def test_normalizers(model, MATLAB_filteroutput, MATLAB_FLODOG_params): + weighted_outputs = model.weight_outputs(MATLAB_filteroutput) + normalizers = model.normalizers(weighted_outputs) + RHS_norms = RHS_implementation.FLODOG_normalizers( + weighted_outputs, sdmix=MATLAB_FLODOG_params["sdmix"] + ) + assert np.allclose(normalizers.shape, RHS_norms.shape) + assert np.allclose(normalizers, RHS_norms) + + +def test_spatial_masks(model, MATLAB_FLODOG_params): + # Is the spatial (Gaussian) averaging window the same? + masks = RHS_implementation.FLODOG_masks(sigx=MATLAB_FLODOG_params["sigx"]) + spatial_avg_filters = multyscale.normalization.spatial_avg_windows_gaussian( + model.bank.x, model.bank.y, model.window_sigmas + ) + assert np.allclose(spatial_avg_filters, masks) + + +def test_FLODOG_RMS(model, MATLAB_filteroutput, MATLAB_FLODOG_params): + weighted_outputs = RHS_implementation.weight(MATLAB_filteroutput) + normalizers = RHS_implementation.FLODOG_normalizers( + weighted_outputs, sdmix=MATLAB_FLODOG_params["sdmix"] + ) + RHS_RMSs = RHS_implementation.FLODOG_RMSs(normalizers, sigx=MATLAB_FLODOG_params["sigx"]) + + RMSs = model.normalizers_to_RMS(normalizers) + assert np.allclose(RMSs, RHS_RMSs) + + +def test_normalized_outputs(model, MATLAB_filteroutput, MATLAB_FLODOG_params): + weighted_outputs = RHS_implementation.weight(MATLAB_filteroutput) + RHS_normalized_outputs = RHS_implementation.flodog_normalize( + weighted_outputs, + sigx=MATLAB_FLODOG_params["sigx"], + sdmix=MATLAB_FLODOG_params["sdmix"], + ) + + normed_outputs = model.normalize_outputs(weighted_outputs) + + assert np.allclose(normed_outputs, RHS_normalized_outputs) + + +def test_model_output(output_FLODOG_MATLAB, model, stimulus): + output = model.apply(stimulus) + assert np.allclose(output, output_FLODOG_MATLAB) From 53616388eb95b2de611343de5e6ff7ef2cdce550 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 26 Oct 2023 19:56:34 +0200 Subject: [PATCH 28/79] test(FLODOG): test full model output --- test/test_models.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/test_models.py b/test/test_models.py index fb4edf8..42b5bf2 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -24,10 +24,12 @@ def test_lodog_output(stimulus, output_LODOG_MATLAB): assert np.allclose(output, output_LODOG_MATLAB) -def test_flodog_output(stimulus, output_flodog_MATLAB): - windowSizeScalar = 4 - m = 0.5 - - model = models.FLODOG_RHS2007(stimulus.shape, visextent) +def test_flodog_output(stimulus, output_FLODOG_MATLAB, MATLAB_FLODOG_params): + model = models.FLODOG_RHS2007( + stimulus.shape, + visextent, + sdmix=MATLAB_FLODOG_params["sdmix"], + spatial_window_scalar=MATLAB_FLODOG_params["sigx"], + ) output = model.apply(stimulus) - assert np.allclose(output, output_flodog_MATLAB) + assert np.allclose(output, output_FLODOG_MATLAB) From c4830ac7fdf874988783b6a98922e2bbfae87bd2 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 26 Oct 2023 19:57:14 +0200 Subject: [PATCH 29/79] tests: unify (somewhat) test-suite structure across models use params fixture LODOG --- test/RHS_implementation.py | 26 ++++++++-------- test/test_FLODOG.py | 2 +- test/test_LODOG.py | 55 +++++++++++++++++---------------- test/test_RHS_implementation.py | 4 +-- test/test_models.py | 10 +++--- 5 files changed, 50 insertions(+), 47 deletions(-) diff --git a/test/RHS_implementation.py b/test/RHS_implementation.py index 62754ad..38281f5 100644 --- a/test/RHS_implementation.py +++ b/test/RHS_implementation.py @@ -156,12 +156,13 @@ def odog_normalize(filter_responses): return modelOut -def lodog_RMS(this_norm, sig1, sr): +# %% LODOG +def LODOG_RMS(this_norm, sig1, sr): # square img_sqr = this_norm**2 # create Gaussian mask - mask = lodog_mask(sig1, sr) + mask = LODOG_mask(sig1, sr) # filter the image (using unit-sum mask --> mean) filter_out = ourconv(img_sqr, mask, pad=0) @@ -176,7 +177,7 @@ def lodog_RMS(this_norm, sig1, sr): return filter_out -def lodog_mask(sig1, sr=1, o=0): +def LODOG_mask(sig1, sr=1, o=0): # sig1= size of gaussian window in the direction of the filter # sig2= size of gaussian window perpendicular to filter sig2 = sig1 * sr @@ -190,13 +191,12 @@ def lodog_mask(sig1, sr=1, o=0): return mask -# %% -def lodog_normalize(filter_responses, sig1, sr=1): +def LODOG_normalize(filter_responses, sig1, sr=1): # normalizers - norms = lodog_normalizers(filter_responses) + norms = LODOG_normalizers(filter_responses) # lRMS - RMSs = lodog_RMSs(norms, sig1, sr) + RMSs = LODOG_RMSs(norms, sig1, sr) # loop over the orientations normed_resps = np.ndarray(filter_responses.shape) @@ -208,7 +208,7 @@ def lodog_normalize(filter_responses, sig1, sr=1): return normed_resps -def lodog_normalizers(filter_responses): +def LODOG_normalizers(filter_responses): norms = np.zeros(filter_responses.shape) # loop over the orientations @@ -224,18 +224,18 @@ def lodog_normalizers(filter_responses): return norms -def lodog_RMSs(norms, sig1, sr=1): +def LODOG_RMSs(norms, sig1, sr=1): RMSs = np.ndarray(norms.shape) # loop over the orientations for o in range(norms.shape[0]): # loop over spatial frequencies to accumulate for f in range(norms.shape[1]): - RMSs[o, f] = lodog_RMS(norms[o, f], sig1, sr) + RMSs[o, f] = LODOG_RMS(norms[o, f], sig1, sr) return RMSs # %% FLODOG -def flodog_normalize(filter_responses, sigx, sdmix, sr=1): +def FLODOG_normalize(filter_responses, sigx, sdmix, sr=1): # Normalizers are combination of filters, weighted by scale norms = FLODOG_normalizers(filter_responses, sdmix) @@ -289,7 +289,7 @@ def FLODOG_masks(sigx, sr=1): for f in range(len(stdev_pixels)): sig1 = sigx * stdev_pixels[f] - masks[o, f] = lodog_mask(sig1, sr) + masks[o, f] = LODOG_mask(sig1, sr) return masks @@ -303,5 +303,5 @@ def FLODOG_RMSs(norms, sigx, sr=1): # localization extent along direction of filter - function of frequency sig1 = sigx * stdev_pixels[f] - RMSs[o, f] = lodog_RMS(norms[o, f], sig1, sr) + RMSs[o, f] = LODOG_RMS(norms[o, f], sig1, sr) return RMSs diff --git a/test/test_FLODOG.py b/test/test_FLODOG.py index 7b94904..2f71713 100644 --- a/test/test_FLODOG.py +++ b/test/test_FLODOG.py @@ -71,7 +71,7 @@ def test_FLODOG_RMS(model, MATLAB_filteroutput, MATLAB_FLODOG_params): def test_normalized_outputs(model, MATLAB_filteroutput, MATLAB_FLODOG_params): weighted_outputs = RHS_implementation.weight(MATLAB_filteroutput) - RHS_normalized_outputs = RHS_implementation.flodog_normalize( + RHS_normalized_outputs = RHS_implementation.FLODOG_normalize( weighted_outputs, sigx=MATLAB_FLODOG_params["sigx"], sdmix=MATLAB_FLODOG_params["sdmix"], diff --git a/test/test_LODOG.py b/test/test_LODOG.py index 478d437..c74547c 100644 --- a/test/test_LODOG.py +++ b/test/test_LODOG.py @@ -1,5 +1,6 @@ # %% Imports import numpy as np +import pytest import RHS_implementation import multyscale @@ -8,36 +9,40 @@ # visual extent, same convention as pyplot: visextent = np.array([-0.5, 0.5, -0.5, 0.5]) * (1023 / 32) # NOTE: RHS implementation doesn't actually use (-16,16,-16,16) -window_sigma = 128 / 32 -# %% -def test_lodog_filters(stimulus, MATLAB_filteroutput): - model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) +# %% Model +@pytest.fixture +def model(stimulus, MATLAB_LODOG_params): + return multyscale.models.LODOG_RHS2007( + stimulus.shape, + visextent, + window_sigma=MATLAB_LODOG_params["sig1"] / 32, + ) + - lodog_filter_output = model.bank.apply(stimulus) +# %% Tests +def test_filters(MATLAB_filteroutput, model, stimulus): + filter_output = model.bank.apply(stimulus) - assert np.allclose(MATLAB_filteroutput, lodog_filter_output) + assert np.allclose(MATLAB_filteroutput, filter_output) -def test_weights(stimulus): - model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) +def test_weights(model): assert np.allclose(model.scale_weights, RHS_implementation.w_val) -def test_lodog_normalizers(stimulus, MATLAB_filteroutput): - model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) +def test_normalizers(model, MATLAB_filteroutput): weighted_outputs = model.weight_outputs(MATLAB_filteroutput) normalizers = model.normalizers(weighted_outputs) - RHS_norms = RHS_implementation.lodog_normalizers(weighted_outputs) + RHS_norms = RHS_implementation.LODOG_normalizers(weighted_outputs) assert np.allclose(normalizers.shape, RHS_norms.shape) assert np.allclose(normalizers, RHS_norms) -def test_lodog_mask(stimulus): +def test_spatial_mask(model, MATLAB_LODOG_params): # Is the spatial (Gaussian) averaging window the same? - RHS_mask = RHS_implementation.lodog_mask(sig1=128) - model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) + RHS_mask = RHS_implementation.LODOG_mask(sig1=MATLAB_LODOG_params["sig1"]) spatial_avg_filters = multyscale.normalization.spatial_avg_windows_gaussian( model.bank.x, model.bank.y, model.window_sigmas ) @@ -45,30 +50,26 @@ def test_lodog_mask(stimulus): assert np.allclose(spatial_avg_filters[o, s], RHS_mask) -def test_lodog_RMS(stimulus): - model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) - filter_outputs = model.bank.apply(stimulus) - weighted_outputs = model.weight_outputs(filter_outputs) +def test_LODOG_RMS(model, MATLAB_filteroutput, MATLAB_LODOG_params): + weighted_outputs = model.weight_outputs(MATLAB_filteroutput) normalizers = model.normalizers(weighted_outputs) RMSs = model.normalizers_to_RMS(normalizers) - RHS_RMSs = RHS_implementation.lodog_RMSs(normalizers, sig1=128) + RHS_RMSs = RHS_implementation.LODOG_RMSs(normalizers, sig1=MATLAB_LODOG_params["sig1"]) assert np.allclose(RMSs, RHS_RMSs) -def test_normalized_outputs(stimulus): - model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) - filter_outputs = model.bank.apply(stimulus) - weighted_outputs = model.weight_outputs(filter_outputs) +def test_normalized_outputs(model, MATLAB_filteroutput, MATLAB_LODOG_params): + weighted_outputs = model.weight_outputs(MATLAB_filteroutput) normed_outputs = model.normalize_outputs(weighted_outputs) - RHS_normalized_outputs = RHS_implementation.lodog_normalize(weighted_outputs, sig1=128) + RHS_normalized_outputs = RHS_implementation.LODOG_normalize( + weighted_outputs, sig1=MATLAB_LODOG_params["sig1"] + ) assert np.allclose(normed_outputs, RHS_normalized_outputs) -def test_lodog_output(stimulus, output_LODOG_MATLAB): - model = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) - +def test_model_output(output_LODOG_MATLAB, model, stimulus): output = model.apply(stimulus) assert np.allclose(output, output_LODOG_MATLAB) diff --git a/test/test_RHS_implementation.py b/test/test_RHS_implementation.py index 8527c0c..cd97494 100644 --- a/test/test_RHS_implementation.py +++ b/test/test_RHS_implementation.py @@ -62,7 +62,7 @@ def test_LODOG(output_LODOG_MATLAB, MATLAB_filteroutput, MATLAB_LODOG_params): filters_output = RHS_implementation.weight(MATLAB_filteroutput) # Normalize - normed_multi_responses = RHS_implementation.lodog_normalize( + normed_multi_responses = RHS_implementation.LODOG_normalize( filters_output, **MATLAB_LODOG_params, ) @@ -81,7 +81,7 @@ def test_FLODOG(output_FLODOG_MATLAB, MATLAB_filteroutput, MATLAB_FLODOG_params) filters_output = RHS_implementation.weight(MATLAB_filteroutput) # Normalize - normed_multi_responses = RHS_implementation.flodog_normalize( + normed_multi_responses = RHS_implementation.FLODOG_normalize( filters_output, **MATLAB_FLODOG_params, ) diff --git a/test/test_models.py b/test/test_models.py index 42b5bf2..4d76ed1 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -16,10 +16,12 @@ def test_odog_output(output_ODOG_MATLAB, stimulus): assert np.allclose(output, output_ODOG_MATLAB) -def test_lodog_output(stimulus, output_LODOG_MATLAB): - window_sigma = 128 / 32 - - model = models.LODOG_RHS2007(stimulus.shape, visextent, window_sigma=window_sigma) +def test_lodog_output(stimulus, output_LODOG_MATLAB, MATLAB_LODOG_params): + model = models.LODOG_RHS2007( + stimulus.shape, + visextent, + window_sigma=MATLAB_LODOG_params["sig1"] / 32, + ) output = model.apply(stimulus) assert np.allclose(output, output_LODOG_MATLAB) From e0e1b097d67e5575133928a2ae09afd44896af72 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 18 Feb 2022 17:54:49 +0100 Subject: [PATCH 30/79] refactor((F)LODOG): Default parameters for LODOG, FLODOG As in Robinson, Hammon, & de Sa (2007) --- multyscale/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/multyscale/models.py b/multyscale/models.py index 21cdab6..a5eb806 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -100,7 +100,7 @@ def apply(self, image): class LODOG_RHS2007(ODOG_RHS2007): # TODO: docstring - def __init__(self, shape, visextent, window_sigma=2): + def __init__(self, shape, visextent, window_sigma=4): self.window_sigma = window_sigma self.window_sigmas = np.ones(shape=(6, 7, 2)) * self.window_sigma @@ -127,7 +127,7 @@ def normalizers_to_RMS(self, normalizers): class FLODOG_RHS2007(LODOG_RHS2007): # TODO: docstring - def __init__(self, shape, visextent, sdmix, spatial_window_scalar): + def __init__(self, shape, visextent, sdmix=0.5, spatial_window_scalar=4): super().__init__(shape, visextent) self.sdmix = sdmix # stdev of Gaussian weights for scale mixing From 126979d7c04040fe9bd0ef3f727b1531d17d419a Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 12 Dec 2023 18:10:32 +0100 Subject: [PATCH 31/79] feat(filterbank): `.shape` attribute --- multyscale/filterbank.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/multyscale/filterbank.py b/multyscale/filterbank.py index fe70a52..a32cd6e 100644 --- a/multyscale/filterbank.py +++ b/multyscale/filterbank.py @@ -69,6 +69,11 @@ def __init__(self, sigmas: Sequence[Sequence[float]], x: np.ndarray, y: np.ndarr self.sigmas = sigmas self.x = x self.y = y + self.shape = ( + len(self.sigmas), + x.shape[0], + x.shape[1], + ) self.filters = np.empty((len(sigmas), x.shape[0], x.shape[1])) for i, sigma in enumerate(sigmas): @@ -168,6 +173,12 @@ def __init__( self.sigmas = sigmas self.x = x self.y = y + self.shape = ( + len(self.orientations), + len(self.sigmas), + x.shape[0], + x.shape[1], + ) self.filters = np.empty((len(orientations), len(sigmas), x.shape[0], x.shape[1])) From bcb8f208dd61d36d1c4ba313be2a341c70ae765b Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 24 Feb 2022 14:24:42 +0100 Subject: [PATCH 32/79] refactor(models): initialization softcodes bank dimensions --- multyscale/models.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/multyscale/models.py b/multyscale/models.py index a5eb806..0268f99 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -47,9 +47,9 @@ def __init__(self, shape, visextent): self.scale_weights = filterbank.scale_weights(self.center_sigmas, self.weights_slope) self.scale_norm_weights = normalization.scale_norm_weights_equal(len(self.scale_weights)) - self.orientation_norm_weights = normalization.orientation_norm_weights(6) + self.orientation_norm_weights = normalization.orientation_norm_weights(self.bank.shape[0]) self.normalization_weights = normalization.create_normalization_weights( - 6, 7, self.scale_norm_weights, self.orientation_norm_weights + *self.bank.shape[:2], self.scale_norm_weights, self.orientation_norm_weights ) def weight_outputs(self, filters_output): @@ -101,11 +101,11 @@ class LODOG_RHS2007(ODOG_RHS2007): # TODO: docstring def __init__(self, shape, visextent, window_sigma=4): - self.window_sigma = window_sigma - self.window_sigmas = np.ones(shape=(6, 7, 2)) * self.window_sigma - super().__init__(shape, visextent) + self.window_sigma = window_sigma + self.window_sigmas = np.ones(shape=(*self.bank.shape[:2], 2)) * self.window_sigma + def normalizers_to_RMS(self, normalizers): # Expand sigmas # Get RMS from each normalizer @@ -135,10 +135,10 @@ def __init__(self, shape, visextent, sdmix=0.5, spatial_window_scalar=4): len(self.scale_weights), self.sdmix ) self.normalization_weights = normalization.create_normalization_weights( - 6, 7, self.scale_norm_weights, self.orientation_norm_weights + *self.bank.shape[:2], self.scale_norm_weights, self.orientation_norm_weights ) self.spatial_window_scalar = spatial_window_scalar self.window_sigmas = self.spatial_window_scalar * np.broadcast_to( - np.array(self.center_sigmas)[None, ..., None], (6, 7, 2) + np.array(self.center_sigmas)[None, ..., None], (*self.bank.shape[:2], 2) ) From 6187c193c64a23a99bfc9767cd2c32da79b5ed47 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 12 Dec 2023 23:57:33 +0100 Subject: [PATCH 33/79] refactor: rename filterbank -> ..s --- README.md | 2 +- demo/demo_dogbank.py | 4 ++-- demo/demo_filterbank.py | 4 ++-- multyscale/__init__.py | 4 ++-- multyscale/{filterbank.py => filterbanks.py} | 0 multyscale/models.py | 14 +++++++------- test/test_filterbanks.py | 10 +++++----- 7 files changed, 19 insertions(+), 19 deletions(-) rename multyscale/{filterbank.py => filterbanks.py} (100%) diff --git a/README.md b/README.md index e667aaf..5cc254f 100644 --- a/README.md +++ b/README.md @@ -37,5 +37,5 @@ import multyscale ``` - `multyscale.filters` contains functions to generate filters -- `multyscale.filterbank` contains classes defining specific sets (banks) of filters +- `multyscale.filterbanks` contains classes defining specific sets (banks) of filters - `multsycale.models` implements some common models from the literature diff --git a/demo/demo_dogbank.py b/demo/demo_dogbank.py index a0f576d..f8c2283 100644 --- a/demo/demo_dogbank.py +++ b/demo/demo_dogbank.py @@ -5,7 +5,7 @@ from PIL import Image # Import local module -from multyscale import filterbank, utils +from multyscale import filterbanks, utils # %% Load example stimulus stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) @@ -31,7 +31,7 @@ sigmas = [(s, cs_ratio * s) for s in center_sigmas] # %% Create filterbank -bank = filterbank.DOGBank(sigmas, x, y) +bank = filterbanks.DOGBank(sigmas, x, y) # %% Visualise filterbank for i in range(bank.filters.shape[0]): diff --git a/demo/demo_filterbank.py b/demo/demo_filterbank.py index 20a1fd0..09da30c 100644 --- a/demo/demo_filterbank.py +++ b/demo/demo_filterbank.py @@ -5,7 +5,7 @@ from PIL import Image # Import local module -from multyscale import filterbank, utils +from multyscale import filterbanks, utils # %% Load example stimulus stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) @@ -34,7 +34,7 @@ sigmas = [((s, s), (s, cs_ratio * s)) for s in center_sigmas] # %% Create filterbank -bank = filterbank.ODOGBank(orientations, sigmas, x, y) +bank = filterbanks.ODOGBank(orientations, sigmas, x, y) # %% Visualise filterbank for i in range(bank.filters.shape[0]): diff --git a/multyscale/__init__.py b/multyscale/__init__.py index 927d2b3..a31c0e3 100644 --- a/multyscale/__init__.py +++ b/multyscale/__init__.py @@ -1,5 +1,5 @@ __version__ = "0.2.0" -__all__ = ["filters", "filterbank", "models", "utils", "normalization"] +__all__ = ["filters", "filterbanks", "models", "utils", "normalization"] -from multyscale import filterbank, filters, models, normalization, utils +from multyscale import filterbanks, filters, models, normalization, utils diff --git a/multyscale/filterbank.py b/multyscale/filterbanks.py similarity index 100% rename from multyscale/filterbank.py rename to multyscale/filterbanks.py diff --git a/multyscale/models.py b/multyscale/models.py index 0268f99..e5f8afc 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -2,7 +2,7 @@ import numpy as np # Local application imports -from . import filterbank, filters, normalization +from . import filterbanks, filters, normalization # TODO: refactor filter-output datastructures @@ -12,14 +12,14 @@ def __init__(self, shape, visextent): self.shape = shape self.visextent = visextent - self.bank = filterbank.BM1997(shape, visextent) + self.bank = filterbanks.BM1997(shape, visextent) self.center_sigmas = [sigma[0] for sigma in self.bank.sigmas] self.weights_slope = 0.1 - self.scale_weights = filterbank.scale_weights(self.center_sigmas, self.weights_slope) + self.scale_weights = filterbanks.scale_weights(self.center_sigmas, self.weights_slope) def weight_outputs(self, filters_output): - return filterbank.weight_multiscale_outputs(filters_output, self.scale_weights) + return filterbanks.weight_multiscale_outputs(filters_output, self.scale_weights) def apply(self, image): # TODO: docstring @@ -40,11 +40,11 @@ def __init__(self, shape, visextent): self.shape = shape self.visextent = visextent - self.bank = filterbank.RHS2007(shape, visextent) + self.bank = filterbanks.RHS2007(shape, visextent) self.center_sigmas = [sigma[0][0] for sigma in self.bank.sigmas] self.weights_slope = 0.1 - self.scale_weights = filterbank.scale_weights(self.center_sigmas, self.weights_slope) + self.scale_weights = filterbanks.scale_weights(self.center_sigmas, self.weights_slope) self.scale_norm_weights = normalization.scale_norm_weights_equal(len(self.scale_weights)) self.orientation_norm_weights = normalization.orientation_norm_weights(self.bank.shape[0]) @@ -53,7 +53,7 @@ def __init__(self, shape, visextent): ) def weight_outputs(self, filters_output): - return filterbank.weight_oriented_multiscale_outputs(filters_output, self.scale_weights) + return filterbanks.weight_oriented_multiscale_outputs(filters_output, self.scale_weights) def normalizers(self, filters_output): # Get normalizers diff --git a/test/test_filterbanks.py b/test/test_filterbanks.py index 13a091b..50e18a0 100644 --- a/test/test_filterbanks.py +++ b/test/test_filterbanks.py @@ -2,7 +2,7 @@ import numpy as np import RHS_implementation -from multyscale import filterbank +from multyscale import filterbanks # %% Parameters of image shape = (1024, 1024) # filtershape in pixels @@ -12,20 +12,20 @@ def test_filterbank(MATLAB_bank): - multy_bank = filterbank.RHS2007((1024, 1024), visextent) + multy_bank = filterbanks.RHS2007((1024, 1024), visextent) assert np.allclose(MATLAB_bank, multy_bank.filters) def test_filterbank_apply(stimulus, MATLAB_filteroutput): - multy_bank = filterbank.RHS2007((1024, 1024), visextent) + multy_bank = filterbanks.RHS2007((1024, 1024), visextent) multy_output = multy_bank.apply(stimulus) assert np.allclose(MATLAB_filteroutput, multy_output) def test_scale_weights(): - bank = filterbank.RHS2007(shape, visextent) + bank = filterbanks.RHS2007(shape, visextent) center_sigmas = np.array(bank.sigmas)[:, 0, 0] - scale_weights = filterbank.scale_weights(center_sigmas, 0.1) + scale_weights = filterbanks.scale_weights(center_sigmas, 0.1) assert np.allclose(scale_weights, RHS_implementation.w_val) From d80f8afe080dc88e740fa92fd7679b09ff3d991c Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 24 Feb 2022 17:37:24 +0100 Subject: [PATCH 34/79] docs: example stimulus and corresponding mask as numpy files --- demo/example_stimulus.npy | Bin 0 -> 8388736 bytes demo/example_stimulus_mask.npy | Bin 0 -> 8388736 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 demo/example_stimulus.npy create mode 100644 demo/example_stimulus_mask.npy diff --git a/demo/example_stimulus.npy b/demo/example_stimulus.npy new file mode 100644 index 0000000000000000000000000000000000000000..a602b2dd23304fd9afacd943e686290f4f10005b GIT binary patch literal 8388736 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%0AqDKf4(z}kaDW3G-~b0WzyS_$ zfCC)hz-b41RbCZPKmi35P(T3%6i{G8fz!_uzFt?OgAOTR2X7>8w%Wh{_y>~G97eC0Xwh*J8%aa-~b0WzyS_$ zfCC)h00%g5+JRn`R|OPMKmi35P(T3%6xdMU^z($T*VX8tLkie|9oT_8-~b0WzyS_$ zfCC)h00%h0f!hxB3cV_zfC36Apnw7jD4@WG0=J((eE+UY2OUzt4(z}V+yMtTzyS_$ zfCC)h00%h00S=sYpjYKp0RxjfC36Apnw7jD6pZx?dK2Qzbn&0hZL{_JFo+HzyS_$ zfCC)h00%h00S<701E(G6Re4oF0R0tzUgfC36Apnw7!3fzAF@cp|o9dt+mJFo*g za0eXV00%h00S<70103K02RLxrfnJqY1r$&~0R1=MUe%E7L)T z6tDw3umg9%0S<70103K02ROh14sd`2ryb~3c~w9G1r$&~0RhU zI;4Od*nu6m0}gP2103K02ROh14sd`29JuX3uh6Rk3Mim}0tzUgfC36^C~*7v!}ssX zbkHFM?7$A}z#VXa103K02ROh14sd`29N@rd2YOXr6;MC{1r$&~0R z{kt+9bVva^umd}A2OQu42ROh14sd`29N+*4IB?p5UX@n`6i`3`1r$&~0Rvc6c=#T<-UK4miL84sd`29N+*4IKTl8aNxEBy+W@FD4>7>3Mim}0tzUw zp}_6u58uBl(?N$6umd}=19!jy4sd`29N+*4IKTl8aDW4+9q3hgRX_m+6i`3`1r$&~ zfei&tKTr62U5ySpq<|gRfgQL54sd`29N+*4IKTl8aDW3Gxa~l%(5nIpD4>7>3Mim} z0t##>aQpeg_wUMd&>;ovzz*!d9dLjH9N+*4IKTl8aDW3G;J|4IdR1N(P(T3%6i`3` z1r$(VLxI!J6TV(oqk|49U7> z3Mim}0vih4e*W7>3Min!h61;rKYahLOa~oOzz*!d4%`6;IKTl8aDW3G-~b0WzyS`NcA!_~RRIMQ zP(T3%6i`3`1vV5o{XF68bu~KZkOFpK2X^2NIKTl8aDW3G-~b0WzyS_$;I;$3Laz!a zpnw7jD4>7>3MjCl!0qP`-@hxGyaDW3G-~b0WzyS_$fCC)3?Le>4 zs{#rrpnw7jD4>7>3T!BF`}xE7@5*%0AqDKf4(z}kaDW3G-~b0WzyS_$fCC)hz-b41 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 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00;ixP8|yX0000C^0(eQh=Rxi9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> X9`Jw%Jm3Kjc)$Z5@PG$A;DJ97O#rsC literal 0 HcmV?d00001 diff --git a/demo/example_stimulus_mask.npy b/demo/example_stimulus_mask.npy new file mode 100644 index 0000000000000000000000000000000000000000..0ea9b61987c46f21a7f7d1b76c79a0043c1112fd GIT binary patch literal 8388736 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 zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7e?*A5+!000002>SoJ9f3~JfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8Fn4Aoqb< AO8@`> literal 0 HcmV?d00001 From 08d75e219022f0b38e31ec0f883f4dcbe6064c54 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 12 Dec 2023 22:12:03 +0100 Subject: [PATCH 35/79] docs: minimal working Jupyter Book build Reorganize into tutorials, how-tos, topic guides, references Update installation page Add contributing information Placeholder for API reference Placeholder(s) for Notebooks (tutorials, demos) --- docs/Makefile | 20 ---- docs/_config.yml | 96 ++++++++++++++++++ docs/_toc.yml | 24 +++++ docs/conf.py | 76 -------------- docs/contributing/contribute.md | 6 ++ docs/contributing/dev_environ.md | 21 ++++ docs/contributing/docs.md | 46 +++++++++ docs/contributing/get_in_touch.md | 5 + docs/cookbook/cookbook.rst | 5 - docs/cookbook/howto_run_flodog.nblink | 3 - .../example_stimulus.png | Bin docs/getting_started/getting_started.md | 14 +++ docs/getting_started/installation.md | 90 ++++++++++++++++ docs/howtos/howtos.md | 8 ++ docs/index.md | 61 +++++++++++ docs/index.rst | 33 ------ docs/install.rst | 70 ------------- docs/make.bat | 35 ------- docs/reference/api.md | 11 ++ docs/reference/multyscale.filterbank.rst | 7 -- docs/reference/multyscale.filters.rst | 7 -- docs/reference/multyscale.models.rst | 7 -- docs/reference/multyscale.normalization.rst | 7 -- docs/reference/multyscale.rst | 16 --- docs/reference/multyscale.utils.rst | 7 -- docs/{topics => topic_guides}/diagram.pdf | Bin docs/topic_guides/history.md | 9 ++ .../topic_guides.md} | 15 +-- docs/topics/brightness_modeling.rst | 2 - docs/tutorials/demo_filters.nblink | 3 - docs/tutorials/tutorials.rst | 5 - pyproject.toml | 33 +----- 32 files changed, 398 insertions(+), 344 deletions(-) delete mode 100644 docs/Makefile create mode 100644 docs/_config.yml create mode 100644 docs/_toc.yml delete mode 100644 docs/conf.py create mode 100644 docs/contributing/contribute.md create mode 100644 docs/contributing/dev_environ.md create mode 100644 docs/contributing/docs.md create mode 100644 docs/contributing/get_in_touch.md delete mode 100644 docs/cookbook/cookbook.rst delete mode 100644 docs/cookbook/howto_run_flodog.nblink rename docs/{tutorials => getting_started}/example_stimulus.png (100%) create mode 100644 docs/getting_started/getting_started.md create mode 100644 docs/getting_started/installation.md create mode 100644 docs/howtos/howtos.md create mode 100644 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/install.rst delete mode 100644 docs/make.bat create mode 100644 docs/reference/api.md delete mode 100644 docs/reference/multyscale.filterbank.rst delete mode 100644 docs/reference/multyscale.filters.rst delete mode 100644 docs/reference/multyscale.models.rst delete mode 100644 docs/reference/multyscale.normalization.rst delete mode 100644 docs/reference/multyscale.rst delete mode 100644 docs/reference/multyscale.utils.rst rename docs/{topics => topic_guides}/diagram.pdf (100%) create mode 100644 docs/topic_guides/history.md rename docs/{topics/topics.rst => topic_guides/topic_guides.md} (66%) delete mode 100644 docs/topics/brightness_modeling.rst delete mode 100644 docs/tutorials/demo_filters.nblink delete mode 100644 docs/tutorials/tutorials.rst diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cb..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..15bc6d5 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,96 @@ +# Book settings +# Learn more at https://jupyterbook.org/customize/config.html + +title: multyscale +author: Joris Vincent +logo: logo.png + +# Force re-execution of notebooks on each build. +# See https://jupyterbook.org/content/execute.html +execute: + execute_notebooks: force + +# Define the name of the latex output file for PDF builds +#latex: +# latex_documents: +# targetname: multyscale.tex + +# Add a bibtex file so that we can create citations +#bibtex_bibfiles: +# - references.bib + +# Information about where the book exists on the web +repository: + url: https://github.com/computational-psychology/multyscale # Online location of your book + path_to_book: docs # Optional path to your book, relative to the repository root + #branch: dev_docs # Which branch of the repository should be used when creating links (optional) + +# Add GitHub buttons to your book +# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository +html: + use_issues_button: true + use_repository_button: true + +#launch_buttons: +# notebook_interface: "jupyterlab" # The interface interactive links will activate ["classic", "jupyterlab"] +# binderhub_url: "https://mybinder.org" + +sphinx: + # extra_extensions: + # - sphinx.ext.intersphinx + # - sphinx.ext.autosummary # generate summary tables of functions in modules + # - sphinx.ext.napoleon # recognize NumPy style docstrings + # - sphinx.ext.viewcode # add links to source code in API reference + # - hoverxref.extension + + config: + exclude_patterns: ['_build', '_templates'] + suppress_warnings: ["etoc.toctree"] + #autosummary_generate: True # autosummary generates module-level .rst files? + add_module_names: False # Don't include module names in autosummary tables + autosummary_imported_members: True + autosummary_ignore_module_all: False + autodoc_default_options: { + "members": True, # Include module/class members. + "member-order": 'bysource', # Order members as in source file. + } + # templates_path: ['_templates'] # Path(s) that contain templates, relative to this config + exclude_patterns: ['_build', '_templates'] + # intersphinx_mapping: + # python: + # - 'https://docs.python.org/3/' + # - null + # numpy [stable]: + # - 'https://numpy.org/doc/stable/' + # - null + # matplotlib [stable]: + # - 'https://matplotlib.org/stable/' + # - null + # pandas [latest?]: + # - 'https://pandas.pydata.org/docs/' + # - null + # scipy [latest]: + # - 'https://docs.scipy.org/doc/scipy/' + # - null + # pillow [latest]: + # - 'https://pillow.readthedocs.io/en/latest/' + # - null + # suppress_warnings: ["etoc.toctree"] + + # # Hoverxref Extension + # hoverxref_auto_ref: True + # hoverxref_intersphinx: [ + # "python", + # "numpy", + # "matplotlib", + # "scipy", + # ] + # hoverxref_domains: ["py", "numpy", "matplotlib", "scipy"] + # hoverxref_role_types: { + # "hoverxref": "modal", + # "ref": "modal", # for hoverxref_auto_ref config + # "mod": "tooltip", + # "class": "tooltip", + # "func": "tooltip", + # "obj": "tooltip" + # } \ No newline at end of file diff --git a/docs/_toc.yml b/docs/_toc.yml new file mode 100644 index 0000000..35b1ba5 --- /dev/null +++ b/docs/_toc.yml @@ -0,0 +1,24 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: index +parts: + - caption: Getting started with multyscale + chapters: + - file: getting_started/installation + - file: getting_started/getting_started + - file: howtos/howtos + - caption: Topic guides + chapters: + - file: topic_guides/history + # - file: topic_guides/normalization_ODOG + # - file: topic_guides/normalization_LODOG + # - file: topic_guides/normalization_FLODOG + # - caption: Reference + # chapters: + # - file: reference/api.md + - caption: Get in touch + chapters: + - file: contributing/get_in_touch + - file: contributing/contribute \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index f3fe729..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,76 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - -sys.path.insert(0, os.path.abspath("../multyscale/")) - - -# -- Project information ----------------------------------------------------- - -project = "multyscale" -copyright = "2021, Joris Vincent" -author = "Joris Vincent" - -# The full version, including alpha/beta/rc tags -release = "0.1.0" - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.napoleon", - "sphinx.ext.intersphinx", - "sphinx_tabs.tabs", - "nbsphinx", - "nbsphinx_link", -] -autodoc_typehints = "description" -napoleon_google_docstring = False -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = True -napoleon_use_param = True -napoleon_use_keyword = True - -intersphinx_mapping = { - "python": ("https://docs.python.org/", None), - "numpy": ("http://docs.scipy.org/doc/numpy/", None), -} - -# Add any paths that contain templates here, relative to this directory. -# templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -import sphinx_rtd_theme - -html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ["_static"] diff --git a/docs/contributing/contribute.md b/docs/contributing/contribute.md new file mode 100644 index 0000000..8aa0b69 --- /dev/null +++ b/docs/contributing/contribute.md @@ -0,0 +1,6 @@ +# Contribute to `multyscale` + +```{toctree} +dev_environ +docs +``` diff --git a/docs/contributing/dev_environ.md b/docs/contributing/dev_environ.md new file mode 100644 index 0000000..67d1342 --- /dev/null +++ b/docs/contributing/dev_environ.md @@ -0,0 +1,21 @@ +# Setting up a development environment + +1. **Fork** the [GitHub repository](https://github.com/computational-psychology/multyscale/) +2. **Clone** the fork repository to your local machine +3. **Install**`multyscale` with the development requirements `pip install -e ".[dev]"` + - Recommended to create an *editable* installation using + - `".[dev,docs]"` to also edit and build the documentation + +The `multyscale` project uses a couple of development tools +to work towards more consistent code quality: + +- `pytest` for unit and integration tests +- `pyupgrade` for possible syntax improvements using newer language features +- `black` for consistent code formatting +- `flake8` for all kinds of linting + +These tools get installed as part of the `[dev]` extra dependencies. +Additionally, to run these more consistently, one can install: + +- `nox` for automatically running tests across different Python versions +- `precommit` for automatically running formatters and linters diff --git a/docs/contributing/docs.md b/docs/contributing/docs.md new file mode 100644 index 0000000..da209dc --- /dev/null +++ b/docs/contributing/docs.md @@ -0,0 +1,46 @@ +# Contribute documentation + +## How the documentation is organized +All documentation lives in the `docs` subdirectory, +and consists of a collection of Markdown (`.md`) files, +specifically in the [MyST Markdown](https://jupyterbook.org/en/stable/content/myst.html) flavor +(as well as some ReStructured Text). + +The documentation is generally organized along [The Documentation System](https://documentation.divio.com/), +consisting of the following categories: +- learning-oriented tutorials, under +- understanding-oriented +- information-oriented + + +## Building the documentation +`multyscale`'s documentation is build using [Jupyter Book](https://jupyterbook.org/en/stable/intro.html) +which is installed as part of the `[docs]` extra dependencies. +To compile the documentation locally (from the toplevel directory): +``` +jupyter-book build --all docs/ +``` +which will then provide an output message on how to view the locally-built documentation. + +## Executable content +Some pages are pure (MyST) Markdown files; +others (e.g. the tutorials) are executable notebooks, in the same MyST Markdown format. +These MyST Notebooks can contain both Markdown syntax, +as well as `code-cell` blocks which are executed during the build +and their output is "woven" into the resulting page. +These MyST Notebooks have a YAML frontmatter +directing [JupyText](https://jupytext.readthedocs.io/en/latest/) +how to convert them. + +## Contributing back to `multyscale` + +0. **Edit** documentation +1. **Commit & Push** changes to your fork + - We use [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) messages; + for documentation please start your commit message(s) with `docs: ...` +2. **Pull request** from your fork to our repository + - GitHub Actions will automatically run tests and linters + - If linters fail, run `black`, `pyupgrade` and `flake8` -- + either separately or all together through `pre-commit`: + `pre-commit run --all-files` +3. Changes will be reviewed by one of the maintainers \ No newline at end of file diff --git a/docs/contributing/get_in_touch.md b/docs/contributing/get_in_touch.md new file mode 100644 index 0000000..8c8cbc7 --- /dev/null +++ b/docs/contributing/get_in_touch.md @@ -0,0 +1,5 @@ +# Getting in touch + +```{toctree} +Ask a question +`````` \ No newline at end of file diff --git a/docs/cookbook/cookbook.rst b/docs/cookbook/cookbook.rst deleted file mode 100644 index 1ec6f35..0000000 --- a/docs/cookbook/cookbook.rst +++ /dev/null @@ -1,5 +0,0 @@ -How-To Guides (Cookbook) -========================= - -.. toctree:: - howto_run_flodog \ No newline at end of file diff --git a/docs/cookbook/howto_run_flodog.nblink b/docs/cookbook/howto_run_flodog.nblink deleted file mode 100644 index ddc0b57..0000000 --- a/docs/cookbook/howto_run_flodog.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../demo/run_flodog.ipynb" -} \ No newline at end of file diff --git a/docs/tutorials/example_stimulus.png b/docs/getting_started/example_stimulus.png similarity index 100% rename from docs/tutorials/example_stimulus.png rename to docs/getting_started/example_stimulus.png diff --git a/docs/getting_started/getting_started.md b/docs/getting_started/getting_started.md new file mode 100644 index 0000000..2b792fa --- /dev/null +++ b/docs/getting_started/getting_started.md @@ -0,0 +1,14 @@ +# Getting started (Tutorial) + +The following pages serve as **tutorial**, +walking you through the very basics of using `multyscale`. + +Along the way, they also refer to some [topic guides](../topic_guides/topic_guides), +which have more in-depth explanation on various concepts and design decisions. + +First, [install `multyscale`](./installation/) + + \ No newline at end of file diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md new file mode 100644 index 0000000..70cec56 --- /dev/null +++ b/docs/getting_started/installation.md @@ -0,0 +1,90 @@ +# Installing + +`multyscale` can be installed in several ways. +For most usecases, i.e., to _use_ the functions in `multyscale`, +we recommend installing from PyPI using `pip`. + +To adapt or contribute code, you will have to get the _source_ code from GitHub. + +:::::{tab-set} + +::::{tab-item} pip {fab}`python` +`pip` can install the latest version of `multyscale` directly from PyPI + +```python +pip install multyscale +``` + +:::{admonition} Install a different version + :class: dropdown + +`pip` by default install the latest version of package. +To install a different version, simply specify the version number, +either an exact version: +```python +pip install "multyscale==1.0.0" +``` +or a conditional version: +```python +pip install "multyscale<=1.0.0" +``` +(for any version before `1.0.0`). + +`multyscale` uses approximately [Semantic Versioning](https://semver.org/) / +[PEP440](https://peps.python.org/pep-0440/). +This means that versions with the same MAJOR version number are backwards compatible: +code written using version `1.N.x` will work under `1.N+1.x` +but is not guaranteed to work under version `2.x.x`. +Versions with higher MINOR version number (`x.N+1.x`) introduce new features. +Versions with higher PATCH number (`x.x.N+1`) fix bugs. + +::: +:::: + + +::::{tab-item} source {fab}`github` + +1. Clone the repository from GitHub: + + ```bash + git clone git@github.com:computational-psychology/multyscale.git + ``` + +2. `multyscale` can then be installed using pip. + From top-level directory run: + + ```python + pip install . + ``` + + to install to your local python library. + +:::{admonition} For developers + :class: dropdown + +```python +pip install -e ".[dev,docs]" +``` + +for an editable install (`-e`) which makes changes to files immediately usable, +rather than having to reinstall the package after every change; +and to install the development and documentation dependencies. +::: +:::: + +::::: + + +## Dependencies +Dependencies should be automatically installed (at least using `pip`). +`multyscale`s required dependencies are: +- [NumPy](numpy) +- [SciPy](scipy) + +This documentation contains interactive tutorials and demos, +in the form of Jupyter Notebooks. +To run this, additional dependencies are required: + - Jupyter Notebook (or JupyterLab) + - ipywidgets +These demo notebooks can also be opened on Binder, +which should install all the necessary dependencies automatically. \ No newline at end of file diff --git a/docs/howtos/howtos.md b/docs/howtos/howtos.md new file mode 100644 index 0000000..38b0b29 --- /dev/null +++ b/docs/howtos/howtos.md @@ -0,0 +1,8 @@ +# How-to... (recipes) + + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..7e9d859 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,61 @@ +# multyscale + +Multyscale aims to provide a modular and flexible, +yet intuitive and robust library +for implementing multiscale spatial filtering models of early vision +(initially and primarily for modeling brightness perception). + +--- + +::::{grid} 2 +:gutter: 3 + +:::{grid-item-card} Get started +:link: getting_started/getting_started +:link-type: doc +:text-align: center +🏃 +^^^ + +Install `multyscale` +and follow an introductory tutorial +::: + +:::{grid-item-card} Learn more +:link: topic_guides/topic_guides +:link-type: doc +:text-align: center +🧑‍🏫 +^^^ + +Learn more about design decisions +and particular conventions and topics within `multyscale` +::: + +:::{grid-item-card} Reference +:link: reference/api +:link-type: doc +:text-align: center +📑 +^^^ + +Look up components and functions, +and their specific parameters + +::: + +:::{grid-item-card} Get in touch, or contribute +:link: contributing/get_in_touch +:link-type: doc +:text-align: center +📨, 🎁 +^^^ + +Ask a question, +report a bug, +request a feature, +or contribute. +::: +:::: + +--- \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 2841bfc..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. multyscale documentation master file, created by - sphinx-quickstart on Fri May 21 00:51:39 2021. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to multyscale's documentation! -====================================== - -Multyscale aims to provide a modular and flexible, yet intuitive and robust library -for implementing multiscale spatial filtering models of early vision -(initially and primarily for modeling brightness perception). - -.. note:: - This documentation uses `The Documentation System`_'s four sections: - Explanations, Tutorials, a "Cookbook" of How-To guides, and the API reference. - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - install - topics/topics - tutorials/tutorials - cookbook/cookbook.rst - -.. toctree:: - :maxdepth: 1 - - reference/multyscale - -:ref:`genindex` - -.. _The Documentation System: https://documentation.divio.com/ \ No newline at end of file diff --git a/docs/install.rst b/docs/install.rst deleted file mode 100644 index a647c92..0000000 --- a/docs/install.rst +++ /dev/null @@ -1,70 +0,0 @@ -Installation -============= - -Multyscale is a true Python package, but not (yet) published on PyPI [#fn_PyPI]_. -For now, multyscale can be retrieved primarily from the `GitHub repository`_ - -.. code-block:: bash - - git clone git@github.com:computational-psychology/multyscale.git - -After downloading the repository, it can be installed (from the main directory): - -.. tabs:: - - .. tab:: pip - - .. code-block:: python - - pip install . - - This will install the package in your local Python packages, - after which the cloned repository can be removed - without removing the installed package. - - To remove the installed package completely, using pip - - .. code-block:: python - - pip uninstall multyscale - - .. tab:: pip, for developers - - .. code-block:: python - - pip install -e . - - This will only create a link to the local repository, - so that changes to the files in this repository are reflected in Python - without the necessity of reinstalling the package. - - As a result, removing the repository removes the packages, - but it's also still a good idea to run - - .. code-block:: python - - pip uninstall multyscale - - -.. _GitHub repository: https://github.com/computational-psychology/multyscale -.. [#fn_PyPI] This will be fixed in an upcoming update. - -.. TODO: add conda installation - -Requirements -------------- - -Multyscale requires: - -- numpy -- scipy - -and recommends working with: - -- pillow -- matplotlib - -as well as: - -- ipython -- jupyter diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 2119f51..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/reference/api.md b/docs/reference/api.md new file mode 100644 index 0000000..eb265d8 --- /dev/null +++ b/docs/reference/api.md @@ -0,0 +1,11 @@ +# `multyscale` API + \ No newline at end of file diff --git a/docs/reference/multyscale.filterbank.rst b/docs/reference/multyscale.filterbank.rst deleted file mode 100644 index e24992b..0000000 --- a/docs/reference/multyscale.filterbank.rst +++ /dev/null @@ -1,7 +0,0 @@ -multyscale.filterbank module -============================ - -.. automodule:: multyscale.filterbank - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/multyscale.filters.rst b/docs/reference/multyscale.filters.rst deleted file mode 100644 index 9dad7db..0000000 --- a/docs/reference/multyscale.filters.rst +++ /dev/null @@ -1,7 +0,0 @@ -multyscale.filters module -========================= - -.. automodule:: multyscale.filters - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/multyscale.models.rst b/docs/reference/multyscale.models.rst deleted file mode 100644 index 16581ec..0000000 --- a/docs/reference/multyscale.models.rst +++ /dev/null @@ -1,7 +0,0 @@ -multyscale.models module -======================== - -.. automodule:: multyscale.models - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/multyscale.normalization.rst b/docs/reference/multyscale.normalization.rst deleted file mode 100644 index 3e94ac4..0000000 --- a/docs/reference/multyscale.normalization.rst +++ /dev/null @@ -1,7 +0,0 @@ -multyscale.normalization module -=============================== - -.. automodule:: multyscale.normalization - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/multyscale.rst b/docs/reference/multyscale.rst deleted file mode 100644 index 220c47a..0000000 --- a/docs/reference/multyscale.rst +++ /dev/null @@ -1,16 +0,0 @@ -API reference -========================= - -.. automodule:: multyscale - :members: - :undoc-members: - :show-inheritance: - -.. toctree:: - :maxdepth: 4 - - multyscale.filterbank - multyscale.filters - multyscale.models - multyscale.normalization - multyscale.utils diff --git a/docs/reference/multyscale.utils.rst b/docs/reference/multyscale.utils.rst deleted file mode 100644 index accfea1..0000000 --- a/docs/reference/multyscale.utils.rst +++ /dev/null @@ -1,7 +0,0 @@ -multyscale.utils module -======================= - -.. automodule:: multyscale.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/topics/diagram.pdf b/docs/topic_guides/diagram.pdf similarity index 100% rename from docs/topics/diagram.pdf rename to docs/topic_guides/diagram.pdf diff --git a/docs/topic_guides/history.md b/docs/topic_guides/history.md new file mode 100644 index 0000000..2f0568f --- /dev/null +++ b/docs/topic_guides/history.md @@ -0,0 +1,9 @@ +# Selected history of multiscale filtering for brightness + +1. ... +2. Foley & McCourt (1985) - Spatial filtering of grating induction +3. Moulden & Kingdom (1989) - Spatial filtering White's effect +4. Moulden & Kingdom (1991) - Multiscale DoG filter model of White's effect +5. Blakeslee & McCourt (1997) - Multiscale DoG model of White's _and_ grating induction +6. Blakeslee & McCourt (1999) - (multiscale) Oriented DoG (ODOG) model of brightness perception +7. Robinson, Hammon, & de Sa (2007) - (F)(L)ODOG models of brightness perception \ No newline at end of file diff --git a/docs/topics/topics.rst b/docs/topic_guides/topic_guides.md similarity index 66% rename from docs/topics/topics.rst rename to docs/topic_guides/topic_guides.md index 53f0418..7bb910b 100644 --- a/docs/topics/topics.rst +++ b/docs/topic_guides/topic_guides.md @@ -1,5 +1,4 @@ -Topic guides -============= +# Explanations Models of brightness perception are numerous and varied. One major differentiating factor, is whether a model includes @@ -8,14 +7,4 @@ or whether a model attempts to more generally model early vision such, that brightness perception phenomena emerge but are not explicitly modeled. A (super)family of models of the latter kind that have been particularly successful, -are multiscale spatial filtering models, examplified in the -ODOG genus of models. - -.. toctree:: - :maxdepth: 1 - :caption: Topic Guides: - - brightness_modeling - -.. image:: diagram.pdf - :width: 400 - :alt: Historical roadmap and modular overview of -ODOG genus of models \ No newline at end of file +are multiscale spatial filtering models, examplified in the -ODOG genus of models. \ No newline at end of file diff --git a/docs/topics/brightness_modeling.rst b/docs/topics/brightness_modeling.rst deleted file mode 100644 index b6f77af..0000000 --- a/docs/topics/brightness_modeling.rst +++ /dev/null @@ -1,2 +0,0 @@ -Modeling brightness perception -=============================== diff --git a/docs/tutorials/demo_filters.nblink b/docs/tutorials/demo_filters.nblink deleted file mode 100644 index ca057e5..0000000 --- a/docs/tutorials/demo_filters.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../demo/filters.ipynb" -} \ No newline at end of file diff --git a/docs/tutorials/tutorials.rst b/docs/tutorials/tutorials.rst deleted file mode 100644 index eba73e3..0000000 --- a/docs/tutorials/tutorials.rst +++ /dev/null @@ -1,5 +0,0 @@ -Tutorials -========== - -.. toctree:: - demo_filters.nblink \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8638142..6f3f8dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,34 +25,11 @@ dependencies = ["numpy", "scipy", "matplotlib", "Pillow"] [project.optional-dependencies] dev = ["pytest", "black", "pyupgrade", "flake8"] docs = [ - "Sphinx", - "sphinx-autodoc-typehints", - "sphinx-rtd-theme", - "sphinx-tabs", - "sphinxcontrib-applehelp", - "sphinxcontrib-bibtex", - "sphinxcontrib-devhelp", - "sphinxcontrib-htmlhelp", - "sphinxcontrib-jsmath", - "sphinxcontrib-napoleon", - "sphinxcontrib-qthelp", - "sphinxcontrib-serializinghtml", - "ipykernel", - "ipython", - "ipython-genutils", - "ipywidgets", - "jupyter-client", - "jupyter-core", - "jupyterlab-pygments", - "jupyterlab-widgets", - "nbclient", - "nbconvert", - "nbformat", - "nbsphinx", - "nbsphinx-link", - "notebook", - "rstcheck", - "widgetsnbextension", + "jupyter-book", + "jupytext>=1.13.3", + "jupyterlab_myst", + "myst_nb", + "sphinx-hoverxref", ] [project.urls] From 024be982c5332360712ea33d19fa6c5f9627378c Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 12 Dec 2023 22:11:14 +0100 Subject: [PATCH 36/79] docs(reference): enable autodoc for API reference --- .gitignore | 2 + docs/_config.yml | 85 ++++++++++++++++++++----------------------- docs/_toc.yml | 6 +-- docs/reference/api.md | 4 +- 4 files changed, 47 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 67434ef..6e6685e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ multyscale.egg-info/ docs/_build/ test/odog_MATLAB.mat + +docs/reference/_api/ diff --git a/docs/_config.yml b/docs/_config.yml index 15bc6d5..dfc875d 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -36,61 +36,56 @@ html: # binderhub_url: "https://mybinder.org" sphinx: - # extra_extensions: - # - sphinx.ext.intersphinx - # - sphinx.ext.autosummary # generate summary tables of functions in modules - # - sphinx.ext.napoleon # recognize NumPy style docstrings - # - sphinx.ext.viewcode # add links to source code in API reference - # - hoverxref.extension + extra_extensions: + - sphinx.ext.intersphinx + - sphinx.ext.autosummary # generate summary tables of functions in modules + - sphinx.ext.napoleon # recognize NumPy style docstrings + - sphinx.ext.viewcode # add links to source code in API reference + - hoverxref.extension config: exclude_patterns: ['_build', '_templates'] suppress_warnings: ["etoc.toctree"] #autosummary_generate: True # autosummary generates module-level .rst files? add_module_names: False # Don't include module names in autosummary tables - autosummary_imported_members: True + autosummary_imported_members: False autosummary_ignore_module_all: False autodoc_default_options: { "members": True, # Include module/class members. "member-order": 'bysource', # Order members as in source file. } # templates_path: ['_templates'] # Path(s) that contain templates, relative to this config - exclude_patterns: ['_build', '_templates'] - # intersphinx_mapping: - # python: - # - 'https://docs.python.org/3/' - # - null - # numpy [stable]: - # - 'https://numpy.org/doc/stable/' - # - null - # matplotlib [stable]: - # - 'https://matplotlib.org/stable/' - # - null - # pandas [latest?]: - # - 'https://pandas.pydata.org/docs/' - # - null - # scipy [latest]: - # - 'https://docs.scipy.org/doc/scipy/' - # - null - # pillow [latest]: - # - 'https://pillow.readthedocs.io/en/latest/' - # - null - # suppress_warnings: ["etoc.toctree"] + intersphinx_mapping: + python: + - 'https://docs.python.org/3/' + - null + numpy [stable]: + - 'https://numpy.org/doc/stable/' + - null + matplotlib [stable]: + - 'https://matplotlib.org/stable/' + - null + pandas [latest?]: + - 'https://pandas.pydata.org/docs/' + - null + scipy [latest]: + - 'https://docs.scipy.org/doc/scipy/' + - null - # # Hoverxref Extension - # hoverxref_auto_ref: True - # hoverxref_intersphinx: [ - # "python", - # "numpy", - # "matplotlib", - # "scipy", - # ] - # hoverxref_domains: ["py", "numpy", "matplotlib", "scipy"] - # hoverxref_role_types: { - # "hoverxref": "modal", - # "ref": "modal", # for hoverxref_auto_ref config - # "mod": "tooltip", - # "class": "tooltip", - # "func": "tooltip", - # "obj": "tooltip" - # } \ No newline at end of file + # Hoverxref Extension + hoverxref_auto_ref: True + hoverxref_intersphinx: [ + "python", + "numpy", + "matplotlib", + "scipy", + ] + hoverxref_domains: ["py", "numpy", "matplotlib", "scipy"] + hoverxref_role_types: { + "hoverxref": "modal", + "ref": "modal", # for hoverxref_auto_ref config + "mod": "tooltip", + "class": "tooltip", + "func": "tooltip", + "obj": "tooltip" + } \ No newline at end of file diff --git a/docs/_toc.yml b/docs/_toc.yml index 35b1ba5..0c4b1f9 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -15,9 +15,9 @@ parts: # - file: topic_guides/normalization_ODOG # - file: topic_guides/normalization_LODOG # - file: topic_guides/normalization_FLODOG - # - caption: Reference - # chapters: - # - file: reference/api.md + - caption: Reference + chapters: + - file: reference/api.md - caption: Get in touch chapters: - file: contributing/get_in_touch diff --git a/docs/reference/api.md b/docs/reference/api.md index eb265d8..81bb049 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -1,5 +1,5 @@ # `multyscale` API - \ No newline at end of file +``` \ No newline at end of file From b608fbd01fd83045d238b2269f09e4f330c99c70 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 22 Dec 2023 13:25:08 +0100 Subject: [PATCH 37/79] docs: Run existing (F)(L)ODOG model As .py with percent-cell syntax, which can also be interpreted as a Jupyter Notebook by various editors --- demo/demo_odog.py | 86 ------ demo/howto_run_(F)(L)ODOG.py | 497 +++++++++++++++++++++++++++++++++++ demo/run_flodog.ipynb | 300 --------------------- 3 files changed, 497 insertions(+), 386 deletions(-) delete mode 100644 demo/demo_odog.py create mode 100644 demo/howto_run_(F)(L)ODOG.py delete mode 100644 demo/run_flodog.ipynb diff --git a/demo/demo_odog.py b/demo/demo_odog.py deleted file mode 100644 index 42cdee4..0000000 --- a/demo/demo_odog.py +++ /dev/null @@ -1,86 +0,0 @@ -# %% -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -# Import local module -from multyscale import models - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create model -model = models.ODOG_RHS2007(shape, visextent) - -# %% Integrated run -output_1 = model.apply(stimulus) - -# %% Visualise output -plt.subplot(1, 2, 1) -plt.imshow(output_1, extent=visextent) -plt.subplot(1, 2, 2) -plt.plot(output_1[512, 250:750]) - -# %% Visualise filterbank -for i in range(model.bank.filters.shape[0]): - for j in range(model.bank.filters.shape[1]): - plt.subplot( - model.bank.filters.shape[0], - model.bank.filters.shape[1], - i * model.bank.filters.shape[0] + ((j + i) * 1) + 1, - ) - plt.imshow(model.bank.filters[i, j, ...], extent=visextent) - -# %% Apply filterbank -filters_output = model.bank.apply(stimulus) - -# %% Visualise filter bank output -for i in range(filters_output.shape[0]): - for j in range(filters_output.shape[1]): - plt.subplot( - filters_output.shape[0], - filters_output.shape[1], - i * filters_output.shape[0] + ((j + i) * 1) + 1, - ) - plt.imshow(filters_output[i, j, ...], extent=visextent) - -# %% Sum over spatial scales, weighting relative to scale -multiscale_output = np.tensordot(filters_output, model.scale_weights, axes=(1, 0)) - -# %% Visualise oriented multiscale output -for i in range(multiscale_output.shape[0]): - plt.subplot(multiscale_output.shape[0], 1, i + 1) - plt.imshow(multiscale_output[i, ...], extent=visextent) - -# %% Normalize oriented multiscale outputs by their RMS -normalized_multiscale_output = np.empty(multiscale_output.shape) -rms = np.ndarray(6) -for i in range(multiscale_output.shape[0]): - image = multiscale_output[i] - rms[i] = np.sqrt(np.square(image).mean((-1, -2))) # image-wide RMS - normalized_multiscale_output[i] = image / rms[i] - -# %% Visualise normalized multiscale output -for i in range(normalized_multiscale_output.shape[0]): - plt.subplot(normalized_multiscale_output.shape[0], 1, i + 1) - plt.imshow(normalized_multiscale_output[i, ...], extent=visextent) - -# %% Sum over orientations -output_2 = normalized_multiscale_output.sum(0) - -# %% Visualise both outputs -plt.subplot(2, 2, 1) -plt.imshow(output_1, extent=visextent) -plt.subplot(2, 2, 2) -plt.plot(output_1[512, 250:750]) - -plt.subplot(2, 2, 3) -plt.imshow(output_2, extent=visextent) -plt.subplot(2, 2, 4) -plt.plot(output_2[512, 250:750]) diff --git a/demo/howto_run_(F)(L)ODOG.py b/demo/howto_run_(F)(L)ODOG.py new file mode 100644 index 0000000..0a11d79 --- /dev/null +++ b/demo/howto_run_(F)(L)ODOG.py @@ -0,0 +1,497 @@ +# %% [markdown] +# # How-to: Run the existing -ODOG models +# This Recipe describes how to run the existing -ODOG models +# (Blakeslee & McCourt, 1999; Robinson, Hammon, & de Sa, 2007) +# as implemented in the module. + +# %% Setup +# Third party libraries +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# Import local module +import multyscale + +# %% [markdown] +# ## Example stimulus +# The example stimulus used for this exploration +# is a version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_thick`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. + +# %% Load example stimulus +stimulus = np.load("example_stimulus.npy") + +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(1, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the stimulus image on the left, the left gray patch appears brighter than the right gray patch. +# On the right, the pixel intensity/gray scale values along the horizontal cut +# (indicated by the dashed line in the image) are shown. +# These reveal that, in fact, the two gray patches are identical in their physical intensity. + + +# %% [markdown] +# ## (F)(L)ODOG models +# The ODOG model (Blakeslee & McCourt, 1997), +# and later derivations LODOG and FLODOG (Robinson, Hammon, & de Sa, 2007) +# are _image-computable model_s of brightness perception. +# As image computable models, they take any arbitrary image-array (2D) as input, +# and output another 2D array +# where each pixel is the predicted perceived brightness for the corresponding pixel in the input. +# +# This recipe demonstrates how to apply these models in one integrated run, +# as well as breaks it down into its constituent components +# and executes these step-wise. +# +# These models are implement in `multyscale.models`, +# where the ODOG model is implemented as the class `ODOG_RHS2007` +# (note that this implementation mimics that of Robinson, Hammon, & de Sa, 2007, +# and deviates slightly from the Blakeslee & McCourt original implementation). +# The specification of the model depends on the image resolution and visual extent of the model, +# so requires these as constructor arguments. + + +# %% Create model +model_ODOG = multyscale.models.ODOG_RHS2007(stimulus.shape, visextent) + +# %% [markdown] +# ## Running the ODOG model +# The most straightforward way of running the model, +# is by calling the `apply()` method + +# %% Apply model +output_ODOG = model_ODOG.apply(stimulus) + +# Visualise output +plt.subplot(1, 2, 1) +plt.imshow(output_ODOG, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + output_ODOG[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the horizontal cut on the right, we can see that the model output +# is greater for the target patch on the left side of the stimulus, +# than for the patch on the right side +# -- also indicated in the output image by the warmer color on the left than on the right. +# This is in the same direction as the perceived brightness effect. + + +# %% [markdown] +# ### Readout +# From this output 2D-array, +# which represents the (predicted) brightness at each pixel location in the original input image, +# we can readout predicted brightness for target regions. +# +# To do this, we use a _mask_-array: +# a 2D-array with the same $Y \times X$ shape as the input image +# where each pixel is assigned to a region of interest. +# At each pixel location, +# an integer index indicates which region of interest it belongs to. + +# %% Mask +mask = np.load("example_stimulus_mask.npy") + +fig, (ax_im, ax_mask) = plt.subplots(1, 2) +ax_im.imshow(stimulus, cmap="gray", extent=visextent) +ax_mask.imshow(mask, extent=visextent) +plt.show() + +# %% [markdown] +# By indexing only those pixels in the image +# belong to a given masked region, +# we can average (median) the intensity value in this region. + +# %% Extract targets intensities +target_intensities = [] +for idx in np.unique(mask.astype(int)): + if idx > 0: + target_intensities.append(np.median(stimulus[mask == idx])) + +# Visualize +plt.bar(x=["left", "right"], height=target_intensities, color="k") +plt.ylim([0, 1]) +plt.xlabel("Target region") +plt.ylabel("Intensity (median)") +plt.show() + +# %% [markdown] +# In the original stimulus, both target regions have the same median intensity. +# In the output array from the ODOG model, however, +# the two target regions now have a different median predicted brightness: + +# %% Extract target outputs +targets_ODOG = [] +for idx in np.unique(mask.astype(int)): + if idx > 0: + targets_ODOG.append(np.median(output_ODOG[mask == idx])) + +# Visualize +plt.bar(x=["left", "right"], height=targets_ODOG, color="k") +plt.axhline(y=0.0, linestyle="dashed", color="k") +plt.xlabel("Target region") +plt.ylabel("Brightness (ODOG; median)") +plt.show() + +# %% [markdown] +# Here we get a _quantitative_ prediction from the model output. +# This quantitative prediction is in the same direction. +# as the perceived brightness effect. + +# %% [markdown] +# ## Comparing (F)(L)ODOG models +# The `multyscale.models` module also implements the LODOG and FLODOG models +# (Robinson, Hammon, & de Sa, 2007), +# as `.LODOG_RHS2007` and `.FLODOG_RHS2007` respectively. +# Running these models works the same way as the base ODOG model. + +# %% Initialize all three models +model_LODOG = multyscale.models.LODOG_RHS2007(stimulus.shape, visextent) +model_FLODOG = multyscale.models.FLODOG_RHS2007(stimulus.shape, visextent) + +# %% Integrated runs of all three models +output_LODOG = model_LODOG.apply(stimulus) +output_FLODOG = model_FLODOG.apply(stimulus) + +# %% Compare outputs +# Stimulus +plt.subplot(4, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(4, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) + +# ODOG +plt.subplot(4, 2, 3) +plt.imshow(output_ODOG, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(4, 2, 4) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + output_ODOG[512, 256:768], + color="black", +) + +# LODOG +plt.subplot(4, 2, 5) +plt.imshow(output_LODOG, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(4, 2, 6) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + output_LODOG[512, 256:768], + color="black", +) + +# FLODOG +plt.subplot(4, 2, 7) +plt.imshow(output_FLODOG, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(4, 2, 8) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + output_FLODOG[512, 256:768], + color="black", +) + +plt.show() + +# %% [markdown] +# From these cut througs, +# we see that the three models overall make similar predictions for this stimulus +# but that the brightness profile is locally quite different. + +# %% [markdown] +# Again, we can extract predictions for each target region +# from each model output. + +# %% Extract target outputs +targets_LODOG = [] +targets_FLODOG = [] +for idx in np.unique(mask.astype(int)): + if idx > 0: + targets_LODOG.append(np.median(output_LODOG[mask == idx])) + targets_FLODOG.append(np.median(output_FLODOG[mask == idx])) + +targets = pd.DataFrame( + { + "ODOG": targets_ODOG, + "LODOG": targets_LODOG, + "FLODOG": targets_FLODOG, + }, + index=["Left", "Right"], +) + +# Visualize +targets.plot(kind="bar") +plt.axhline(y=0.0, linestyle="dashed", color="k") +plt.xlabel("Target region") +plt.ylabel("Brightness (median)") +plt.show() + +# %% [markdown] +# Here we see that all three models +# _qualitative_ predict the same direction of effect, +# but make different _quantitative predictions +# for the magnitude of the brightness difference between target regions. + +# %% [markdown] +# ## Model components, and step-wise execution +# The (F)(L)ODOG models consist of several components, or steps: +# +# 1. The _multiscale spatial filering_ frontend +# _encodes_ the input (stimulus) image +# into a representation at multiple scales/spatial frequencies, and multiple orientations. +# 2. The normalization step regulates activity in each frequency-orientation channel, +# by normalizing it by the energy in all frequency bands +# 3. From the normalized, multichannel representation, +# a single 2D brightness "map" is readout, by linearly combining channels. +# From this, target predictions can be further decoded. +# +# +# The implementations in `multyscale` have the convenience `.apply()` method +# that runs an entire model in one go, +# but also provides access to each of the steps individually. + + +# %% [markdown] +# ### Frontend: filterbank +# _Multiscale spatial filtering_ models are defined by their multiscale spatial filtering frontend: +# A bank of filters $\mathbf{F}$ which span a range of spatial scales $S$ +# are convolved with the stimulus image. +# The general -DOG family of models uses +# *D*ifference-*o*f-*G*aussian filters, +# which act as intensity-difference detecters. +# The scale of the filter directly determines the spatial frequency selectivity. +# The -ODOG subfamily of models uses *O*riented DoG filters +# that also have one of several orientations $O$. +# Thus, filter $f_{o,s}$ is a single filter in the set $\mathbf{F}$ +# with orientation $o$ and scale $s$. +# +# Since each filter is 2D, it also has an implied $x,y$ pixels. +# As a result, we can also think of the 2D ($O\times S$) set $\mathbf{F}$ of filter(outputs), +# where each filter(output) $f_{o,s}$ is an image, +# as a 4D ($O \times S \times X \times Y$) set $\mathbf{I}$ of pixel intensities. +# $$ \mathbf{I}_{O \times S \times X \times Y} \equiv \mathbf{F}_{O \times S} $$ +# +# The filterbank object is stored the `model.bank` attribute of the model object. +# These, and other, filterbanks are created using the `multyscale.filterbanks`-module. +# All three models here use the same filterbank +# with 6 different orientation as 7 different spatial scales (spatial frequency selecitivity). + + +# %% Get parameters +print(f"{model_ODOG.bank.shape[0]} orientations, {model_ODOG.bank.shape[1]} spatial scales") + +# All three models have identical filters +assert np.array_equal(model_ODOG.bank.filters, model_LODOG.bank.filters) +assert np.array_equal(model_ODOG.bank.filters, model_FLODOG.bank.filters) +assert np.array_equal(model_LODOG.bank.filters, model_FLODOG.bank.filters) + +# Visualise filterbank +fig, axs = plt.subplots(*model_ODOG.bank.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(model_ODOG.bank.shape[:2]): + axs[o, s].imshow(model_ODOG.bank.filters[o, s, ...], cmap="coolwarm", extent=visextent) + +# %% [markdown] +# In this visualisation of all filters, +# the rows differ in orientation of the filter +# and columns differ in the spatial scale of the filter. + + +# %% [markdown] +# ### Frontend: filtering +# These filters are then convolved with the stimulus image. +# +# Filterbank-objects have an `apply(...)` method, +# which filters the input stimulus with the whole bank. +# The output is an $O \times S \times Y \times X$ tensor +# of channel responses. + +# %% Apply filterbank to example stimulus +filters_output = model_ODOG.bank.apply(stimulus) + +print(f"{filters_output.shape} channel responses") + + +# Visualise each filter output +fig, axs = plt.subplots(*filters_output.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(filters_output.shape[:2]): + axs[o, s].imshow(filters_output[o, s], cmap="coolwarm", extent=visextent) + +# %% [markdown] +# In this visualisation of filter output, +# again, rows differ in orientation of the filter +# and columns differ in the spatial scale of the filter. + + +# %% [markdown] +# ### Frontend: weighting filter(outputs) according to CSF +# In the -ODOG models, the filter outputs are weighted according to the CSF, +# that is, higher frequencies (smaller spatial scales), +# are weighted more strongly: +# "The seven spatial frequency filters [are weighted] across frequency +# using a power function with a slope of 0.1" +# +# This slope is an attribute of the `ODOG_RHS2007` object, +# which then determines the `scale_weights` attribute. +# The weights can be applied through the `.weight_outputs` method. + +# %% Weight individual filter(outputs) according to spatial size (frequency) +print(f"Slope of weights {model_ODOG.weights_slope}, gives weights:") +print(f"{model_ODOG.scale_weights}") + +weighted_outputs = model_ODOG.weight_outputs(filters_output) + +# Visualise weighted filter outputs +fig, axs = plt.subplots(*weighted_outputs.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(weighted_outputs.shape[:2]): + axs[o, s].imshow(weighted_outputs[o, s], cmap="coolwarm", extent=visextent) + +# Since this weighting just scales the output, +# it does not affect this visualisation of the filter outputs. + +# %% [markdown] +# ### Normalization +# Each filter output then gets normalized +# by a combination of the other filter outputs. + +# %% Normalize filter outputs +normalized_outputs = model_ODOG.normalize_outputs(weighted_outputs) + +# Visualise normalized filter outputs +fig, axs = plt.subplots(*normalized_outputs.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalized_outputs.shape[:2]): + axs[o, s].imshow(normalized_outputs[o, s], cmap="coolwarm", extent=visextent) + +# %% [markdown] +# ### Readout +# To readout from this (normalized) multiscale representation, +# first we integrate over orientations and scales, +# resulting in a 2D $(Y \times X)$ array again + +# %% Sum normalized filter outputs into single array +output_stepwise = normalized_outputs.sum((0, 1)) + +# Visualise output +plt.subplot(1, 2, 1) +plt.imshow(output_stepwise, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + output_stepwise[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the horizontal cut on the right, we can see that the model output +# is greater for the target patch on the left side of the stimulus, +# than for the patch on the right side +# -- also indicated in the output image by the warmer color on the left than on the right. +# This is in the same direction as the perceived brightness effect. + +# %% [markdown] +# This stepwise running of the model +# is exactly what the `model.apply(...)` function does, +# thus the output is identical. + +# %% Compare runs +# Stimulus +plt.subplot(3, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(3, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) + +# Integrated run +plt.subplot(3, 2, 3) +plt.imshow(output_ODOG, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(3, 2, 4) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + output_ODOG[512, 256:768], + color="black", +) + +# Stepwise +plt.subplot(3, 2, 5) +plt.imshow(output_stepwise, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(3, 2, 6) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + output_stepwise[512, 256:768], + color="black", +) + +# Check (assert) that the two runs are identical +assert np.array_equal(output_ODOG, output_stepwise) + + +# %% [markdown] +# ### Overlap and distinction between F-L-ODOG steps +# The three models-species are distinct, +# and their output is different, +# but they also share some components. +# +# All three models share the same frontend (filterbank, weighting), +# but their normalization steps are different. +# Thus, one can get the (weighted) filter output from only one model, +# and run only the distinct normalization steps from the other models +# to produce the three outputs. + +# %% Apply different normalizations +normalized_ODOG = model_ODOG.normalize_outputs(weighted_outputs) +normalized_LODOG = model_LODOG.normalize_outputs(weighted_outputs) +normalized_FLODOG = model_FLODOG.normalize_outputs(weighted_outputs) + +output_stepwise_ODOG = normalized_ODOG.sum((0, 1)) +output_stepwise_LODOG = normalized_LODOG.sum((0, 1)) +output_stepwise_FLODOG = normalized_FLODOG.sum((0, 1)) + +assert np.array_equal(output_ODOG, output_stepwise_ODOG) +assert np.array_equal(output_LODOG, output_stepwise_LODOG) +assert np.array_equal(output_FLODOG, output_stepwise_FLODOG) diff --git a/demo/run_flodog.ipynb b/demo/run_flodog.ipynb deleted file mode 100644 index 6986566..0000000 --- a/demo/run_flodog.ipynb +++ /dev/null @@ -1,300 +0,0 @@ -{ - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "python385jvsc74a57bd02092d3496cec6223f7568d467ac3bd437caae5364fbbef5eee989d574fa6e351", - "display_name": "Python 3.8.5 64-bit ('lightness_models': pyenv)" - } - }, - "nbformat": 4, - "nbformat_minor": 2, - "cells": [ - { - "source": [ - "# How-to: Run an existing model" - ], - "cell_type": "markdown", - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Third party libraries\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from PIL import Image\n", - "\n", - "# Import local module\n", - "import multyscale" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# %% Load example stimulus\n", - "stimulus = np.asarray(Image.open(\"example_stimulus.png\").convert(\"L\"))\n", - "\n", - "# %% Parameters of image\n", - "shape = stimulus.shape # filtershape in pixels\n", - "# visual extent, same convention as pyplot:\n", - "visextent = (-16, 16, -16, 16)" - ] - }, - { - "source": [ - "## Integrated run" - ], - "cell_type": "markdown", - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "model = multyscale.models.FLODOG_RHS2007(shape, visextent)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "output = model.apply(stimulus)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "[]" - ] - }, - "metadata": {}, - "execution_count": 7 - }, - { - "output_type": "display_data", - "data": { - "text/plain": "
", - "image/svg+xml": "\n\n\n \n \n \n \n 2021-05-23T21:08:14.336031\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - } - } - ], - "source": [ - "plt.subplot(1, 3, 1)\n", - "plt.imshow(stimulus, cmap=\"gray\", extent=visextent)\n", - "plt.subplot(1, 3, 2)\n", - "plt.imshow(output, cmap=\"coolwarm\", extent=visextent)\n", - "plt.subplot(1, 3, 3)\n", - "plt.plot(output[512, 250:750])" - ] - }, - { - "source": [ - "## Step-wise execution" - ], - "cell_type": "markdown", - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": "
", - "image/svg+xml": "\n\n\n \n \n \n \n 2021-05-23T21:12:59.462471\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - } - } - ], - "source": [ - "filters_output = model.bank.apply(stimulus)\n", - "\n", - "for i in range(filters_output.shape[0]):\n", - " for j in range(filters_output.shape[1]):\n", - " plt.subplot(\n", - " filters_output.shape[0],\n", - " filters_output.shape[1],\n", - " i * filters_output.shape[0] + ((j + i) * 1) + 1,\n", - " )\n", - " plt.imshow(filters_output[i, j, ...], cmap=\"coolwarm\", extent=visextent)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": "
", - "image/svg+xml": "\n\n\n \n \n \n \n 2021-05-23T21:13:39.021849\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - } - } - ], - "source": [ - "weighted_outputs = model.weight_outputs(filters_output)\n", - "\n", - "for i in range(weighted_outputs.shape[0]):\n", - " for j in range(weighted_outputs.shape[1]):\n", - " plt.subplot(\n", - " weighted_outputs.shape[0],\n", - " weighted_outputs.shape[1],\n", - " i * weighted_outputs.shape[0] + ((j + i) * 1) + 1,\n", - " )\n", - " plt.imshow(weighted_outputs[i, j, ...], cmap=\"coolwarm\", extent=visextent)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": "
", - "image/svg+xml": "\n\n\n \n \n \n \n 2021-05-23T21:14:08.576625\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - } - } - ], - "source": [ - "normalized_outputs = model.normalize_outputs(weighted_outputs)\n", - "\n", - "for i in range(normalized_outputs.shape[0]):\n", - " for j in range(normalized_outputs.shape[1]):\n", - " plt.subplot(\n", - " normalized_outputs.shape[0],\n", - " normalized_outputs.shape[1],\n", - " i * normalized_outputs.shape[0] + ((j + i) * 1) + 1,\n", - " )\n", - " plt.imshow(normalized_outputs[i, j, ...], cmap=\"coolwarm\", extent=visextent)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "[]" - ] - }, - "metadata": {}, - "execution_count": 11 - }, - { - "output_type": "display_data", - "data": { - "text/plain": "
", - "image/svg+xml": "\n\n\n \n \n \n \n 2021-05-23T21:14:16.976875\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - } - } - ], - "source": [ - "output = normalized_outputs.sum((0,1))\n", - "\n", - "plt.subplot(1, 3, 1)\n", - "plt.imshow(stimulus, cmap=\"gray\", extent=visextent)\n", - "plt.subplot(1, 3, 2)\n", - "plt.imshow(output, cmap=\"coolwarm\", extent=visextent)\n", - "plt.subplot(1, 3, 3)\n", - "plt.plot(output[512, 250:750])" - ] - }, - { - "source": [ - "## Non-default parameters" - ], - "cell_type": "markdown", - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.spatial_window_scalar = 3\n", - "model.sdmix = .75\n", - "model.scale_norm_weights = multyscale.normalization.scale_norm_weights_gaussian(\n", - " len(model.scale_weights), model.sdmix\n", - ")\n", - "model.normalization_weights = multyscale.normalization.create_normalization_weights(\n", - " 6, 7, model.scale_norm_weights, model.orientation_norm_weights\n", - ")\n", - "model.window_sigmas = np.broadcast_to(\n", - " np.array(model.center_sigmas)[None, ..., None], (6, 7, 2)\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ouput = model.apply(stimulus)\n", - "\n", - "plt.subplot(1, 3, 1)\n", - "plt.imshow(stimulus, cmap=\"gray\", extent=visextent)\n", - "plt.subplot(1, 3, 2)\n", - "plt.imshow(output, cmap=\"coolwarm\", extent=visextent)\n", - "plt.subplot(1, 3, 3)\n", - "plt.plot(output[512, 250:750])" - ] - } - ] -} \ No newline at end of file From 2e898aab45768b47e9c32a2e1a1017f3d4fb5bda Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 22 Dec 2023 16:56:58 +0100 Subject: [PATCH 38/79] docs: normalization, why and ODOG norm --- demo/normalization_ODOG.py | 537 +++++++++++++++++++++++++++++++++++++ 1 file changed, 537 insertions(+) create mode 100644 demo/normalization_ODOG.py diff --git a/demo/normalization_ODOG.py b/demo/normalization_ODOG.py new file mode 100644 index 0000000..a61ca02 --- /dev/null +++ b/demo/normalization_ODOG.py @@ -0,0 +1,537 @@ +# %% [markdown] +# # ODOG normalization +# This Tutorial describes the rationale behind +# the normalization step of the original ODOG model (Blakeslee & McCourt, 1997), +# and its implementation in `multyscale`. + + +# %% Setup +# Third party libraries +import matplotlib.pyplot as plt +import numpy as np + +# Import local module +import multyscale + +# %% [markdown] +# ## Frontend + + +# %% [markdown] +# ### Example stimulus +# The example stimulus used for this exploration +# is a version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_thick`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. + +# %% Load example stimulus +stimulus = np.load("example_stimulus.npy") + +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(1, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the stimulus image on the left, the left gray patch appears brighter than the right gray patch. +# On the right, the pixel intensity/gray scale values along the horizontal cut +# (indicated by the dashed line in the image) are shown. +# These reveal that, in fact, the two gray patches are identical in their physical intensity. + + +# %% [markdown] +# ### Filterbank +# The -DOG family of models starts with a _multiscale spatial filtering_ frontend. +# This consists of a set of filters, $\mathbf{F}$ +# which span a range of spatial scales $S$ +# The _oriented_ -ODOG subfamily of models uses filters +# that also have one of several orientations $O$. +# Thus, filter $f_{o,s}$ is a single filter in the set $\mathbf{F}$ +# with orientation $o$ and scale $s$. +# Since each filter is 2D, it also has an implied $x,y$ pixels. +# +# As a result, we can also think of the 2D ($O\times S$) set $\mathbf{F}$ of filter(outputs), +# where each filter(output) $f_{o,s}$ is an image, +# as a 4D ($O \times S \times X \times Y$) set $\mathbf{I}$ of pixel intensities. +# $$ \mathbf{I}_{O \times S \times X \times Y} \equiv \mathbf{F}_{O \times S} $$ +# +# For the current topic, we use the default ODOG filterbank, +# which can be created by `multyscale.filterbanks.RHS2007()`, +# with 6 orientations, and 7 spatial scales. + +# %% Frontend filterbank of ODOG implementation by Robinson et al. (2007) +filterbank = multyscale.filterbanks.RHS2007(filtershape=stimulus.shape, visextent=visextent) + +# Get parameters +print(f"{filterbank.filters.shape[0]} orientations, {filterbank.filters.shape[1]} spatial scales") + +# Visualise filterbank +fig, axs = plt.subplots(*filterbank.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(*filterbank.shape[:2]): + axs[o, s].imshow(filterbank.filters[o, s, ...], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() + +# %% [markdown] +# In this visualisation of all filters, +# the rows differ in orientation of the filter +# and columns differ in the spatial scale of the filter. + +# %% [markdown] +# These filters are then convolved with the stimulus image. +# +# Filterbank-objects have an `apply(...)` method, +# which filters the input stimulus with the whole bank. +# The output is an $O \times S \times Y \times X$ tensor +# of channel responses. + +# %% Apply filterbank to (example) stimulus +filters_output = filterbank.apply(stimulus) + +# Visualise each filter output +fig, axs = plt.subplots(*filters_output.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(filters_output.shape[:2]): + axs[o, s].imshow( + filters_output[o, s], + cmap="coolwarm", + extent=visextent, + vmin=filters_output.min(), + vmax=filters_output.max(), + ) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() + + +# %% [markdown] +# ### Recombination is not enough +# Simply recombining the outputs of these filters does not give rise to any effects; +# it merely gives a lossy reconstruction of the original stimulus, +# since the filters act as a decomposition. + +# %% Recombine +recombined_outputs = np.sum(filters_output, axis=(0, 1)) + +plt.subplot(1, 2, 1) +plt.imshow(recombined_outputs, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], recombined_outputs.shape[1])[256:768], + recombined_outputs[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the horizontal cut, we can see that the model output +# is still approximately equal for the two gray target patches in the stimulus image. + + +# %% [markdown] +# ### Frontend: weighting filter(outputs) according to CSF +# In the -ODOG models, the filter outputs are weighted according to the CSF, +# that is, higher frequencies (smaller spatial scales), +# are weighted more strongly: +# "The seven spatial frequency filters [are weighted] across frequency +# using a power function with a slope of 0.1" +# +# The weights can be created using `multyscale.filterbank.scale_weights`, +# and applied using `multyscale.filterbanks.weight_oriented_multiscale_outputs`. + +# %% Weight individual filter(outputs) according to spatial size (frequency) +center_sigmas = [center[0] for (center, s) in filterbank.sigmas] +weights = multyscale.filterbanks.scale_weights(center_sigmas, slope=0.1) + +weighted_outputs = multyscale.filterbanks.weight_oriented_multiscale_outputs( + filters_output, weights +) + +# Visualise weighted filter outputs +fig, axs = plt.subplots(*weighted_outputs.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(weighted_outputs.shape[:2]): + axs[o, s].imshow( + weighted_outputs[o, s], + cmap="coolwarm", + extent=visextent, + vmin=weighted_outputs.min(), + vmax=weighted_outputs.max(), + ) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() + +# %% [markdown] +# This does produce some effect, however, not the kind we wish to model: + +# %% Readout +recombined_outputs = np.sum(weighted_outputs, axis=(0, 1)) + +plt.subplot(1, 2, 1) +plt.imshow(recombined_outputs, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], recombined_outputs.shape[1])[256:768], + recombined_outputs[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the horizontal cut, we can see that the model output +# is actually _higher_ for the right patch, compared to the left patch, +# which is _opposite_ from the perceived brightness difference! + + +# %% [markdown] +# ## Normalization +# The predictive power of the -ODOG models comes from their normalization step, +# in which information from all the filters regulates the activity of each filter output. +# +# Each filter(output) $f_{o',s'}$ gets divided by +# a _normalizing coefficient_ $n_{o',s'}$, +# i.e., for each filter $f_{o',s'}$, the normalized $f'$ is +# +# $$f'_{o',s'} := \frac{f_{o',s'}}{n_{o',s'}}$$ +# +# The normalizing coefficient $n_{o', s'}$ is made up of (a subset of) +# the responses in all $\mathbf{F}$ filter outputs. +# Thus, the tensor of normalizing coefficients $\mathbf{N}$ +# contains $6 \times 7$ 2D ($X \times Y$): +# one normalizing coefficient $n_{o',s'}$ per filter $f_{o',s'}$ to normalize. + +# %% [markdown] +# ### Combine filter outputs into normalizing coefficients +# +# In the original ODOG specification +# a filter only gets normalized by the filters with the same orientation. +# Thus, the normalizing coefficient $n_{o',s'}$ +# is a combination of only those $f_s$ with the same orientation ($o=o'$): +# +# $$n_{o',s', x, y} := \sum_{s=1}^{S} f_{o',s,x,y}$$ + +# %% Normalizing coefficients +norm_coeffs = np.zeros_like(weighted_outputs) +for o_prime, s_prime in np.ndindex(weighted_outputs.shape[:2]): # for each filter to normalize + for o, s in np.ndindex(weighted_outputs.shape[:2]): # loop over all filters + if o == o_prime: # same orientation + norm_coeffs[o_prime, s_prime] += weighted_outputs[ + o, s, : + ] # add this filter to normalizing coefficient + +# Plot each normalizing coefficient n_{o,s}, +# i.e., the normalizer image for each individual filter f_{o,s} +fig, axs = plt.subplots(*norm_coeffs.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(norm_coeffs.shape[:2]): + axs[o, s].imshow( + norm_coeffs[o, s], + cmap="coolwarm", + extent=visextent, + vmin=norm_coeffs.min(), + vmax=norm_coeffs.max(), + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# NOTE that all normalizing coefficients within a row are identical, +# i.e., all filters of an orientation get the same normalizing coefficient in the ODOG model. +# This makes sense, +# since each filter $f_{o',...}$ gets normalized by all filters of that same orientation $o=o'$: +# +# $$n_{o',1} = n_{o',2} = ... = n_{o',7} = \sum_{s=1}^{S} f_{o',s,x,y}$$ + + +# %% [markdown] +# We can alternatively specify the combination +# as a weighted sum over all $\mathbf{F}$ such that +# $$n_{o',s',x,y}=\sum_{o=1}^{O}\sum_{s=1}^{S} w_{o',s',o,s}f_{o,s,x,y}$$ +# where the weight depends on whether $o$: +# $$w_{o',s',o,s} = \begin{cases} +# 1 & o = o' \\ +# 0 & else +# \end{cases}$$ +# +# Thus, $\mathbf{W}$ is a $O \times S$ set of $O \times S$ weights: +# for each $f_{o',s'}$ filter, we need to define $O \times S$ weights +# for whether each filter normalizes this one. + +# %% Define normalization weights +normalization_weights = np.ndarray(weighted_outputs.shape[:2] * 2) +for o_prime, s_prime in np.ndindex(weighted_outputs.shape[:2]): + for o, s in np.ndindex(weighted_outputs.shape[:2]): + if o == o_prime: + normalization_weights[o_prime, s_prime, o, s] = 1 # /filters_output.shape[1] + else: + normalization_weights[o_prime, s_prime, o, s] = 0 + +# Visualize weights +fig, axs = plt.subplots(*normalization_weights.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalization_weights.shape[:2]): + axs[o, s].pcolor( + normalization_weights[normalization_weights.shape[0] - o - 1, s], + cmap="Greens", + edgecolors="k", + linewidths=1, + vmin=0, + vmax=1, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# With a tensor (matrix) dot-product, +# these weights can be used to combine filter outputs into normalizing images + +# %% Normalizing images as weighted combination (tensor dot-product) of filter outputs +normalizing_coefficients = np.tensordot( + normalization_weights, weighted_outputs, axes=([0, 1], [0, 1]) +) + +# Visualize each normalizing coefficient n_{o,s}, i.e. +# the normalizer image for each individual filter f_{o,s} +fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients.shape[:2]): + axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent) + +# NOTE that these normalizing coefficients are identical the ones above: +assert np.allclose(normalizing_coefficients, norm_coeffs) + +# %% [markdown] +# In `multyscale`, such a matrix (tensor) of normalization weights +# can be easily and expressively created: +# by specifying the weights along each dimension (orientation, scale, etc.) +# and combining these into the single tensor. +# +# The function `multyscale.normalization.scale_norm_weights_equal()` +# defines how the $S$ spatial scales affect each other +# as a $(S \times S)$ matrix of weights: +# for each scale-to-be-normalized $s'$, +# it produces the weight given to each other scale $s$. +# +# In the ODOG model, all scales influence all (other) scales equally, +# thus this matrix is all $1$s. + +# %% Scale normalization weights +scale_norm_weights = multyscale.normalization.scale_norm_weights_equal(7) +plt.pcolor(scale_norm_weights, cmap="Greens", edgecolors="k", linewidths=1, vmin=0, vmax=1) +plt.xlabel("Spatial scale/freq. $s'$") +plt.ylabel("Spatial scale/freq. $s$") +plt.show() + +# %% [markdown] +# The function `multyscale.normalization.orientation_norm_weights()` similarly +# defines how the $O$ spatial scales affect each other, +# as a $(O \times O)$ matrix of weights: +# for each orientation-to-be-normalized $o'$, +# it produces the $O$ weights given to all other scales $o$. +# +# In the -ODOG models, filters only normalize other filters with the same orientation, +# i.e., when $o'=o$. Thus, this forms a diagonal matrix + +# %% Orientation normalization weights +orientation_norm_weights = multyscale.normalization.orientation_norm_weights(6) +plt.pcolor( + orientation_norm_weights[::-1, :], cmap="Greens", edgecolors="k", linewidths=1, vmin=0, vmax=1 +) +plt.xlabel("Orientation $o'$") +plt.ylabel("Orientation $o$") +plt.show() + +# %% [markdown] +# These normalization weights along each dimension +# are then combined into a single +# $(O' \times S' \times O \times S)$ matrix (tensor) of normalization weights. +# +# This tensor $w_{o',s',o,s}$ can be produced +# using the function `multyscale.normalization.create_normalization_weights()` +# from the separate sets of weights for orientations and scales. +# +# For the example where $(o'=3, s'=4)$, +# this means that all weights $w_{3,4,o,s}=1$ if $o==3$, regardless of $s$. + +# %% Normalization weights +norm_weights = multyscale.normalization.create_normalization_weights( + *weighted_outputs.shape[:2], scale_norm_weights, orientation_norm_weights +) +# NOTE that these are identical to the weights $w$ defined above. +assert np.array_equal(norm_weights, normalization_weights) + +# Visualize weights +fig, axs = plt.subplots(*norm_weights.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(norm_weights.shape[:2]): + axs[o, s].pcolor( + norm_weights[norm_weights.shape[0] - o - 1, s], + cmap="Greens", + edgecolors="k", + linewidths=1, + vmin=0, + vmax=1, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + + +# %% [markdown] +# `multyscale` also provides convenience functionality +# for constructing the normalizing coefficients +# from such weights, and the filter outputs to be normalized. +# +# The function `multyscale.normalization.normalizers()` creates these coefficients, +# and note that these are identical to the $N$ constructed above. + +# %% Identical normalizing coefficients +norm_coeffs = multyscale.normalization.normalizers(weighted_outputs, normalization_weights) +assert np.allclose(norm_coeffs, normalizing_coefficients) + +# Visualize each normalizing coefficient n_{o,s}, i.e. +# the normalizer image for each individual filter f_{o,s} +fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients.shape[:2]): + axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# ### Normalize by energy +# Rather than normalizing by this weighted sum of all filter outputs at each pixel location, +# instead the -ODOG models normalize by +# the _energy_ of the normalizing coefficient. +# Energy here is expressed as the (spatial) root-mean-square of the signal. +# +# 1. Square each ($X \times Y = 1024 \times 1024$) pixel of the combined normalizing coefficient +# 2. Mean across all ($X \times Y = 1024 \times 1024$) pixels +# 3. Square-root of this mean +# +# $$ RMS(n_{o',s'}) = \sqrt{\frac{1}{XY}\sum_{x=1}^{X}\sum_{y=1}^{Y}(n_{o',s',x,y})^2} $$ +# +# NOTE: this is the _quadratic mean_ of the normalizing coefficient. +# +# This results in a single value for each $f_{o', s'}$ filter-to-be-normalized. + +# %% Global image RMS +normalization_RMSs = np.ndarray(normalizing_coefficients.shape[:2]) +for o, s in np.ndindex(normalizing_coefficients.shape[:2]): + normalization_RMSs[o, s] = np.sqrt((normalizing_coefficients[o, s] ** 2).mean()) + +# Visualise +plt.pcolor(normalization_RMSs[::-1, :], cmap="coolwarm", edgecolors="k", linewidths=1) +plt.ylabel("Orientation $o'$") +plt.xlabel("Spatial scale $s'$") +plt.show() + +# %% [markdown] +# This heatmap represents the RMS value of the normalizing coefficient +# that each filter gets normalized by. + +# %% [markdown] +# ### Divisive normalization +# These values, the energy (spatial RMS) of the normalizing coefficients, +# form the denominator of the divisive normalization. +# +# Thus, the normalized filter output $f'_{o', s'}$ +# is calculating by dividing each filter(output) $f_{o',s'}$ +# by the energy of the normalizer coefficient $n_{o',s'}$: +# $$f'_{o',s'} = \frac{f_{o',s'}}{RMS(n_{o',s'})}$$ + +# %% Divisive normalization +normalized_outputs = np.ndarray(weighted_outputs.shape) +for o, s in np.ndindex(weighted_outputs.shape[:2]): + f = weighted_outputs[o, s, ...] + n = normalization_RMSs[o, s] + normalized_outputs[o, s] = f / n + +# Visualize each normalized f'_{o',s'} +fig, axs = plt.subplots(*normalized_outputs.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalized_outputs.shape[:2]): + axs[o, s].imshow( + normalized_outputs[o, s], + cmap="coolwarm", + extent=visextent, + vmin=normalized_outputs.min(), + vmax=normalized_outputs.max(), + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# In this visualisation of the normalized outputs, +# again, rows differ in orientation of the filter (being normalized) +# and columns differ in the spatial scale of the filter (being normalized). + +# %% [markdown] +# Thus, the full normalization schema of ODOG can be formulated as: +# $$ F' = +# \frac{f_{o',s',x,y}}{\sqrt{\frac{1}{XY}\sum_{y=1}^{Y} \sum_{x=1}^{X}(\sum_{o=1}^{O}\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}} +# = \frac{f_{o',s',x,y}}{\sqrt{\mathbf{avg}((\mathbf{W} \cdot \mathbf{F})^2)}} +# $$ +# where $w_{o', s', o, s} = \begin{cases} +# 1 & o = o' \\ +# 0 & else +# \end{cases} +# $ + +# %% [markdown] +# ## Readout +# Finally, we readout a final model output +# by recombining the normalized filter outputs +# -- simply by summing them all up. + +# %% Recombine +recombined_outputs = np.zeros(normalized_outputs.shape[-2:]) +for o, s in np.ndindex(normalized_outputs.shape[:2]): + recombined_outputs += normalized_outputs[o, s] + +# Visualize model output +plt.subplot(1, 2, 1) +plt.imshow(recombined_outputs, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], recombined_outputs.shape[1])[256:768], + recombined_outputs[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the horizontal cut, we can see that the model output +# is now greater for the target patch on the left side of the stimulus, +# than for the patch on the right side +# -- in the same direction as the perceived brightness effect. + +# %% [markdown] +# Thus, the normalization step is crucial for the proper functioning of the ODOG model. +# Robinson, Hammon, & de Sa (2007) also demonstrate this, +# by comparing the full ODOG model with a version without normalization (UNODOG). +# The UNODOG model fails to predict the direction of effect more often than the full ODOG model +# (Table 2). From dad2e39df1f67bb06efdd91e93e2ac43171f1d83 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 22 Dec 2023 13:24:47 +0100 Subject: [PATCH 39/79] docs: normalization LODOG, why and implementation Use WE_Dual as example, since ODOG does predict WE, but not the dual version, because of global normalization --- demo/WE_dual.npy | Bin 0 -> 8388736 bytes demo/WE_dual_mask.npy | Bin 0 -> 8388736 bytes demo/demo_lodog.py | 106 -------- demo/normalization_LODOG.py | 514 ++++++++++++++++++++++++++++++++++++ 4 files changed, 514 insertions(+), 106 deletions(-) create mode 100644 demo/WE_dual.npy create mode 100644 demo/WE_dual_mask.npy delete mode 100644 demo/demo_lodog.py create mode 100644 demo/normalization_LODOG.py diff --git a/demo/WE_dual.npy b/demo/WE_dual.npy new file mode 100644 index 0000000000000000000000000000000000000000..416bec8f4c93300f5ea7caa137218e54bbb60b03 GIT binary patch literal 8388736 zcmeF$vF>%;HfL_V548-~HD=`NjYG#sB{KpML$jfBfywfBn z_46-(`uR8i^4tIU+h6_nfB2W*{?o7T`~Uo_fBxOCfB*gO{@btq$FG0?fB%O+|Brw1 zm%se!&OiI*PyhPUAMu|*{l)+P&%gbR&r3iKdY}jPfCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14shV#n*-ZQrvxM*0SVL+`1kgEDyI8c4}x%c`l2RU2+k;VG9ZrMHS@BZ%OcXRV^H^BVO{}QP7z&mgKmxCNG0X@(IJ+KEHXgE-P_qq4_EeAPV z0(#Kwf#3PfT@G@%1oS`;^uQi)py9x7**)v;{_f;=bMtRE!2Hes5~%jTJ8%7$gB&ga zJDyI8c4}x%c`l2RU2+k;VG9ZrMHS@BZ%OcXRV^H^BVO{}QP7z&mgK zmxCNG0X@(IJ+KEHXgE-P_qq4_EeAPV0(#Kwf#3PfT@G@%1oS`;^uQi)py9x7**)v; z{_f;=bMtRE!2Hes5~%jTJ8%7$gB&gaJDyI8c4}x%c`l2RU2+k;VG9 zZrMHS@BZ%OcXRV^H^BVO{}QP7z&mgKmxCNG0X@(IJ+KEHXgE-P_qq4_EeAPV0(#Kw zf#3PfT@G@%1oS`;^uQi)py9x7**)v;{_f;=bMtRE!2Hes5~%jTJ8%7$gB&gaJDyI8c4}x%c`l2RU2+k;VG9ZrMHS@BZ%OcXRV^H^BVO{}QP7z&mgKmxCNG z0X@(IJ+KEHXgE-P_qq4_EeAPV0(#Kwf#3PfT@G@%1oS`;^uQi)py9x7**)v;{_f;= zbMtRE!2Hes5~%jTJ8%7$gB&gaJDyI8c4}x%c`l2RU2+k;VG9ZrMHS z@BZ%OcXRV^H^BVO{}QP7z&mgKmxCNG0X@(IJ+KEHXgE-P_qq4_EeAPV0(#Kwf#3Pf zT@G@%1oS`;^uQi)py9x7**)v;{_f;=bMtRE!2Hes5~%jTJ8%7$gB&gaJDy zI8c4}x%c`l2RU2+k;VG9ZrMHS@BZ%OcXRV^H^BVO{}QP7z&mgKmxCNG0X@(I zJ+KEHXgE-P_qq4_EeAPV0(#Kwf#3PfT@G@%1oS`;^uQi)py9x7**)v;{_f;=bMtRE z!2Hes5~%jTJ8%7$gB&gaJDyI8c4}x%c`l2RU2+k;VG9ZrMHS@BZ%O zcXRV^H^BVO{}QP7z&mgKmxCNG0X@(IJ+KEHXgE-P_qq4_EeAPV0(#Kwf#3PfT@G@% z1oS`;^uQi)py9x7**)v;{_f;=bMtRE!2Hes5~%jTJ8%7$gB&gaJDyI8c4} zx%c`l2RU2+k;Vco4*|7a0%#v z9_WER;6THH-O^c2z@9hu{B6$F-n)0Z_iy{)d%d^>^gs{vz#eem+Yan@-Lv|4-{;=l z$?xV*4%JWdZ}-6b+w=Fn+x+Dqhf6>Y^gs{n0S6im?3T`I0`|PI=Wlba_TIhQy?@&W z-|NLCpa*)O2ljvi-*#ZP>z>uW`#$&XPJTCka;Scqf4c|f-=4qs-R3U`Ia~sIpa*(j z4>-_pV7GKu6R_uvJ%5{XwfFAb?)}?7_+BqA0X@(IJ+KEH__hPPUH7d1-S@e7ck;XW zlSB2>{M$V+|MvX7?>2uq$l(&u13l0Kd%%H)1G}ZOnt(lT?D^ZAtG##ccJJTz!S{M` z3Fv_y=z%@pz_%UP?Yd|6@4nByyOZC|pB$>6=HKpt`M2lqeYg3`K@OLI9_WD{*aHqU z9M~fe2zdv_4(xW_v-)@6=ic4P@8(Yq)lc(p_rUzy z^Y^~n{N*5rOF$3wKo9Hz2O19Smd%}FY2YR3f_J9N5 zc3`*bp4GqmKKJfUem8$|sD7G%y9egqp1=3q<}U|1TmpKa2YO%+IM8rlw{%t$u;-0E zf17i)_wL>9{o6kHUN0^IJFYwgbCe_pJWi_qlg>^1JzyL-o`A+dVM<_WZr? zHh($D;S$gTJnJS9|Z? z?cTrbgYWg?63_!Z&;xtGfp0so+jY1>gB&ga zJDyIIvqfs|nci#-6{;x!Qa8ZukCeAAGMDmw+DVfgacc4t(2z-L899|L*(T zyF2;a{K=vEY5wgVn16fz-gle79OQ5b=z$*Sfj!_r!-3t>Sxvy6H}?E(&eh(#cf0p* z``~-MxCHb-5A?tuaNyex>~`I=`gh;w-rdRX=1&gQPxEj0!2H|u_rBZwB1&T0boys_tRbFTK@z1zKi+Xvt4#U-EzdY}jPfCJxlV7Kd@)xY~b_wG)9 zH-B=dewu%~2j<_NzxUndF9$hX0(zhadSDMY&~RY4bXF6v=Z!son{&1I?%nSF+dlYS zFD?N+&;vcN2ORje1G`=Ktp45ixp#N+yZMtt_0#;@Juv_F{Jrlse>uqE63_!Z&;xtG zfrbOSrL&rVJ#Xy!+nlStckg!Z-}b@xdT|Npfgb3AJ>bB%9oX%;VV9?Z9r=J*$8BeeT_z{BHi_Q2jLjb`Q+IJ%8`J&0h|3xCHb-5A?tuaG>G9 zZt1KhV9y(S{x;`o@7=rI`?r1YyCNIaEK*zug1#Z_nTRZu6Ie94-Mp&;vcN2OMZPuvWpYf!)$sO~9Tv_WW(m)!w^zyZ3MV;CsEe1oS`;^uQi) z;M)%DcHOi3ci-pU-O2CfPY%^j^KbXS{M+;QzT5oeAcspp5A;9}>;VTF4(yiBY6AAW zvFC4duJ+!&+r59=2jA<(C7=g-pa=GV1K)OFx9gtOzxzJ-?oNI;e{!gPnt!_o=HH&b z_ub|%2RU2&;vct1AD-Mh6B5$vzmZCZ|wQo zoU6Te?{@Fs_QCghaS7;w9_WER;J~*X*zLM!_3ysVy}Og&&7T~qpXT50f%&)R?|rxV z%Rvs8fF9_99@qm8G#uD1oz(>Fd1KGt=3MQ)d$)W4whzA7i%UQc^gs{n0SCVAz;4$) ztAF=>?%kdIZvNy@{WSk}56r(kfA71^Uk-A(1oS`;^uQi)py9x7>8vJT&l`LGHs@;Z z-MiiUw|(%vUR(ltpa*(j4><5`2X?#eS^c~3bMNltck?HQ>Zkd)dtmejP@80d+zwLwX_2LrH13l0Kd%%HjJFwez&+6ZO zpL^H$`^lmD+1>nayZ(pnb$$NTy?^fghxWw(A1^KeJFAz!w~N-a$UsXF15> z63_!ZDD=QPW<8gK94-Mp&;vcN2OMZPuv>P|`v1u$l_n-T3pXDHjOF$1=J@B5{yyYN=OF$3wKo9Hz2O19Smff@d z-mmX@eg56eZ}|SB{jSfyzW+b!hkxh9C7=g-pa=GV104901NHZx`){A+Acspp4_ZC& zp4q(RAcspp5A;9}>;VTF4(yiQv;N+%?|FUx-OX?K{-gb_&%eI^KkA2n=fowT2YR3f z_J9K%_>u$l_n-T3pXDHjOF$1=J@B5{yyYN=OF$3wKo9Hz2O19Smff@d-mmX@eg56e zZ}|SB{jSfyzW+b!hkxh9C7=g-pa=GV104901NHZx`){A+Acspp4_ZC&p4q(RAcspp z5A;9}>;VTF4(yiQv;N+%?|FUx-OX?K{-gb_&%eI^KkA2n=fowT2YR3f_J9K%_>u$l z_n-T3pXDHjOF$1=J@B5{yyYN=OF$3wKo9Hz2O19Smff@d-mmX@eg56eZ}|SB{jSfy zzW+b!hkxh9C7=g-pa=GV104901NHZx`){A+Acspp4_ZC&p4q(RAcspp5A;9}>;VTF z4(yiQv;N+%?|FUx-OX?K{-gb_&%eI^KkA2n=fowT2YR3f_J9K%_>u$l_n-T3pXDHj zOF$1=J@B5{yyYN=OF$3wKo9Hz2O19Smff@d-mmX@eg56eZ}|SB{jSfyzW@J^T|35+ z8wi3R`2SxEjl!7P^O@n0hODWsj-3PbS3mqaCoTa!&;vcN2OQwQmmH|y|D1pOEC)GU z0(#Kuf&0wnEeAPV0(zhadSDMY&~RX<>^|%F{raBQ=ilA@hVOsvcYXf#{r{^U{+$z- zfF9_99@qm8aNtW0)bD@JzkQa294-MpX!XE-X7iSV94-Mp&;vcN2OMZPuv2!Q_4|H( z&+GH=Zhph}zxKO6|N8#_)ery9iAz8a^gs{n0S7qnB?s#FKj+^*%Rvs8fF87Z;6Af? z%Rvs8fF9_99@qm8G#uC|yU+T4zrN@7`FA(J;rn0vU7vq_|NrWTf9J#{pa*)O2ljvi z9Qcw0_4}XmZ=dBLhf6>YT0L-|*}UZ-hf6>Y^gs{n0S6im?3CSS{k~t{^ZNX|o8R#L zul=sizrO!}^~1k&;u6pUJ^*XLi~|G)a--#Kv!=z$*Sfj!^=2fpM${r>0t z+h;k*;S$h;Ru9}~Hg7q|;S$gTJfyh>w8|Ge|Pg6zW=q~_4(KL|F3@dcTQXadY}h-U=KLJfiF2wzyCS^_E`>cxCHc| z)dTmL&07v~xCHb-5A?tuaG>G9PT76d@B8&Vug|}``3>Ly+VA@O>-+y#Km0o+|n!e#7^__Paj+`u_jb5C6`IOF$3wKo9Hz2RQI0 z2kQ4f=iffdK@OLI9<+MkKC^ktK@OLI9_WD{*aHqU9M~zl&-#78zUTG%cQ?P``(OKA zpMQP-|LTW-=fowT2YR3f_J9K%_>u$l`=9e~pXDHjOF$1=J#e4dyyYN=OF$3wKo9Hz z2O19Sl-+0jzF*(-`uw|_-|+pf{jSfyzW;yq!@qOl63_!Z&;xtG0SUQbzT`mt{^$JLXF15>63~NI58P)qZ#l@}63_!Z&;xtGfrbM+W%pUX z@7MRdKL76KH+=tVzw7g_@Bd%@@b8?s1oS`;^uQi)fCFE0pnm^z{_V3IWpYft|AZtl#(RdtRS^ck>&*|Fz%s`PcXVuYUM-VqFF8=Z|2hBmSq^fz1oWWQ1NWKDTMlx#1oS`;^uQi)py9wy*?rdU`}IAq&%e9* z4d4IT@A~}f`~O!z{5vNu0X@(IJ+KEH;J}w0sNesbfBP&4Ia~sI(CUHv%;qfzIa~sI zpa*(j4>-_pV5jUp>-YWop4aE!-Ta2{f9-dD{`LL;s~`TI6PJJ<=z$*C0}gQDOAgfU zf6l*smV+EF0X=B-zi0kA-#*Jh4wrx)w0ht^vw6!w4wrx)=z$*C0}eDC z*eScu`hCB?=k@t_H^1TgU;AC3e|`V|>W6>l#3i5ydY}jPfCC)(k^}YopYv~@A~-Dmy2U*Gfk{JWdq@cpm-uFt=||9|zvzjNXe z&;vct1AD*$4t&Xh`u)%Ox6g8r!zG{xtsc0~Y~FH^!zG{xdY}jPfCCK&cFOLve&4U} zd42xf&2RYr*M8UMU*G?~`r+R>aS7;w9_WER-~b1{(1TVF+-Ej# zImqD>&;vct1AD-Mh66ig_gTO1*Y~_W|L*2DeE(~|>+`Sg|6l#^@0_><^gs{vz#edb z17C8We*bg+?Xw)@a0%!^s|W5go3|X~a0%#v9_WER;6THHowEC^-}mc#UY~z=^BcbZ zwcqvm*Z2Rge)xAzTmpKa2YO%+IKY7~IZ(g$^*yi8zq|Pj-~Zb0`uywr|5rc!J0~syJFAz?U4@G(9CC z0SQQ;mVo=rCN2j#TmpKa2YO%+IM8rlr|dp`|7rC6f1cIn-`)In@83Su=U?Cd*8^`J zTmpKa2YO%+IKY8|1Dmp^1SB8<3DgpB&)LM~Acspp5A;9}>;VTF4(ycOr|&;VTjP;g*V_LP7GBp`uW0`57RxE$ng3Fv_y z=z%@pK*NEZvitP?r_uBOc~+l)ck|o5fBR6Me|`U754?GB3Fv_y=z%@p00#;VY|5Sz zkbndvP)oo)XA_r$94-Mp&;vcN2OMZPuv2!QzW+3O{y)#^^Y3ncyZ3J&>hrJf|LcJ_ z4=w>c&;vcN2OQu)!GTTLQvwo@fCOp@xaVx*a*)F%pa*)O2ljvi4F`70?$h_5M$iA} zS$+Q9&2RVq?L&S3_5FW6@aDlKpa*)O2ljvi94I)jDSJvl0uqowEdlqOOG9PT76>{?q9B|2(VDzq|SE-oJgQ&%eI^uLs^dxCHb-5A?tuaDW2^2R3C- z2}nQ!5~wBMp0kO|K@OLI9_WD{*aHqU9M~zlPv3tUJ^!C)_4#)?r{WNI(L$1l)5raXHB063_!Z&;xtGfrbM+W%ud(PowAm z^Q=Dq?&i09|MsCi|N8#F9(eQM63_!Z&;xtG0S*)#*pxjbAOQ(Tpq7Ap&L%DgIa~sI zpa*(j4>-_pV5jUpegA3n{C}R+=ilA@cJJRl)aPH{|JMU=9$W%?pa*(j4>-Vqf&-hf zrvxM*0SVL+aL?JqA~-KXzAjh_F{v--+zD z;LU?eKo9gl59|R4I8bn4Q}&dA1SBAVS_1Alo46e0a0%#v9_WER;6THHowED%{io6M z|9MuQe|PiSy?^^qpMQP-Uk|)_a0%#v9_WER-~a~-4s6Pv5|DrdBv4DhJ!cb_gB&ga zJDyIIvT8pT7Szdj3Dp>hteze!KT?AL{e3@Biz8HxDiWJFAK*51c z*;4`%kbneg3ApEM;&PC~C7=g-pa=GV0}Tgu%I?$mpGMFB=UILJ-OX?J{_R73{`LKT zJ@DqiC7=g-pa=GV0~{zguqk^=KmrnwKrI3HoK0K~a<~NaKo9i59&n)Hz)snH`u@}C z`Tsnt&%e9*?cTqAsL#K?|E~w$Jh%k(Ko9i59&msI1qU`|PYFms0urbt;GVOI%Rvs8 zfF9_99@qm8G#uC|yHDSL8a@A?XZ87aH^1Hcw-5FC*Z2SRz?%n`fF9_99@qm8aG>D8 zrtB#J2}nQ!wFKOAHgP$~;S$gTJWpYft|AZ^!=yN^Z$8PpMQ7r+r5ALP@jK& z|6dQhd2k8nfgb3AJ>UQb3Jz?_o)VCN1SC*Pz&&RZmxCNG0X@(IJ+KEHXgIJ_cAvig zGZy)OOukZisfj18>0X@(IJ+KEH;6TBFP1#cd5|DrdY6-aKY~pf| z!zG{xdY}jPfCCK&cFOM4_n$`3|L0kK{@u-Q_x|leeg5_Re?9Q#!6l#vdY}jPfCC&T zIIt;uN==uLVtIxl?`R(4neW=gB zzW=WW-aNPj^gs{vz#edb0|f^*Wlsr6Kmrn|CE%X3iOWF_mw+DVfgacc4m2FtDZ5YK ze;PgipJ(;?cQ?P?`?nAE`PcXV^}w44mw+DVfgacc4sf90z^3de0SQPz0<{F(b2f1~ z$l(&u13l0Kd%%H)13P8+>HANk=l}DpKL76Kw|oEgp+5im{=Xi0^WYND13l0Kd%yt> z6dc%;JtZIk2}q!pfP2m+E(bYW0(zhadSDMY&~RX<>^^<}Y4rSmp4I2y-TZd%-#*mm zU*G@N18*K&0(zhadSDMYz=47To3f__Bp?9^)Dm#d*~H}_hf6>Y^gs{n0S6im?3CT7 z?>~*6|If4f{JWdq?)}?``uywr|9arfgG)dU^gs{n0S7oxa9~sRlz;>zAc0x}?m3&d z9OQ5b=z$*Sfj!_r!-1W$`}F;%(ewX#R-b=&^V_|D`%s^Meg9t%ym@d5=z$*Sfj!^= z2MP{s%AOLCfCMB^OTay66PJS=E&)Bz13j<@9B4SOQ+A)e|1^63KhNs(?{0p(_irES z^RMs!>wz~9E&)Bz13j<@9N<8~flb*{0uqpb1ZoMm=WOC~ki#XQ2YR3f_J9Ko2X@Nt z)Ayf7&;RFHeg56eZ} z_4(KL|MkF|2bX{z=z$*C0}gPY;J~KrDFF#cKmxS{+;cW@ImqD>&;vct1AD-Mh66ig z_gVk`)1CZIZvO2Cn7{d70@WV4=dJ&8ki#XQ2YR3f_J9Ko2deiz=Wg_yW54HY50+0g z2mA4RKjb5yOF$3wKo9Hz2fpRNPSbr>@4fHb-O2CdFNf;e{M$V+|MvXd=Qe*i$l(&u z13l0Kd%%H)1J%F#oV(F)j{TmqJy<@~9PG#M{g97*E&)Bz13j<@9Qc+4J5Bdlz4yLz zcPGD-zZ|M>^KbXS{M++)pWFQ9Acspp5A;9}>;VTF4pjf{bM8jJIre+b_F(x`bFd%3 z_d`DNxdik;5A?tuaNt`G>@?kH_1^o=-JSeS{&J|k&A;6P^KZ}JeQxuYgB&gaJDyI8gn&&$%1@=GgB!+k@p(&B1>B-Vgc6=MvBZJ&;vct1AD-M zh6B~V`<%PcZ;t(*vprZo)g0`{@BNUEd@cb!&;vcN2ORj813OLkS-tnZb9X1dlfN9Q zZ}V^W!2H|ucc0t*^^l^qXV9=WGv_Pc;Yo@q0hyBcDq^5A;9} z>;VV9<-ktUeOB+i@7&$V@8mCs>f8L=Juv_F{N3j^e>uqE63_!Z&;xtGfrbOszx$lK z(Ql6Zp0hnzKGhuT$M5}+k9;lxJFYmIFIY_gTI7zH@gczmvZls&Dge_rUzy z^LL-y{N*5rOF$3wKo9Hz2O17k|L$|{M!z}sd(QS?`BZbTAHVlQKJvK)^gs{vz#eem zTMq0r-DmaQ`_A2+{7(LIsJ_j=-2?M)&)XCpl#Q@;mvEp_3u9CZuFaDzvpZZmQOVY`|*1}+dVM<_Wa%FHh($D;S$gTJY^gs{n0S6imRR8XC?nb{k_Iu9uVEI&YuphtoLq77k1oS`;^uQi);9CytG~H+Q z-uuqoo%~Mza;Uz|zug1#Z_nR-Zu6Ie94-Mp&;vcN2OMZPQ2o2lxf}iF*zY;pgXL4r z!G8SS5BbRF63_!Z&;xtGfp0mm({!KJd+$4Uck(;=%c1%<|8@_|zde8Vxy@e=a<~Na zKo9i59&n)HK=toF=Wg_yW54HY50+0g2mA4RKjb5yOF$3wKo9Hz2fpRNPSbr>@4fHb z-O2CdFNf;e{M$V+|MvXd=Qe*i$l(&u13l0Kd%%H)1J%F#oV(F)j{TmqJy<@~9PG#M z{g97*E&)Bz13j<@9Qc+4J5Bdlz4yLzcPGD-zZ|M>^KbXS{M++)pWFQ9Acspp5A;9} z>;VTF4pjf{bM8jJIre+b_F(x`bFd%3_d`DNxdik;5A?tuaNt`G>@?kH_1^o=-JSeS z{&J|k&A;6P^KZ}JeQxuYgB&gaJDyI8gn&&$%1@=GgB!+k@p(&B1>B-Vgc6 z=MvBZJ&;vct1AD-Mh6B~V`<%PcZ;t(*vprZo)g0`{@BNUEd@cb! z&;vcN2ORj813OLkS-tnZb9X1dlfN9QZ}V^W!2H|ucc0t*^^l z^qXV9=WGv_Pc;Yo@q0hyBcDq^5A;9}>;VV9<-ktUeOB+i@7&$V@8mCs>f8L=Juv_F z{N3j^e>uqE63_!Z&;xtGfrbOszx$lK(Ql6Zp0hnzKGhuT$M5}+k9;lxJFY zmIFIY_gTI7zH@gczmvZls&Dge_rUzy^LL-y{N*5rOF$3wKo9Hz2O17k|L$|{M!z}s zd(QS?`BZbTAHVlQKJvK)^gs{vz#eemTMq0r-DmaQ`_A2+{7(LIsJ_j=-2?M)&)XCpl#Q@;mvEp_3u9CZuFaDzvpZZ zmQOVY`|*1}+dVM<_Wa%FHh($D z;S$gTJY^gs{n0S6imRR8XC?nb{k_Iu9uVEI&Y zuphtoLq77k1oS`;^uQi);9CytG~H+Q-uuqoo%~Mza;Uz|zug1#Z_nR-Zu6Ie94-Mp z&;vcN2OMZPQ2o2lxf}iF*zY;pgXL4r!G8SS5BbRF63_!Z&;xtGfp0mm({!KJd+$4U zck(;=%c1%<|8@_|zde8Vxy@e=a<~NaKo9i59&n)Hz)tBY0SQPz0<{GE&Try!ki#XQ z2YR3f_J9Ko2X@Ntv-;ilox3~vo&4oceVc!~2j<_NzkA;1F9$hX0(zhadSDMY&~RX< z^pt=EBp`uW0)FQ=aXHB063_!Z&;xtGfrbM+W%pVA?)%Q&o%~Mza;Uz|zug1#Z_nR7 zZ}XRf94-Mp&;vcN2OMZPuv2WpYft|AZtbX@> z=k88^Cx1Cq-{#-$f%&)R@1D2$%Rvs8fF9_99@qm8G#uC|JtZIk2}q!pfZzE|Tn=)$ z1oS`;^uQi)py9wy*?m^O`@VB`C%==w9I9{gZ}-6b+w*tN+x+Dqhf6>Y^gs{n0S6im z?3A7okbndvP)oq?{3b33Ia~sIpa*(j4>-_pV5jUptKWU!xx16!$zKlDxB0hwVE*m- zyXS5Ga*)F%pa*)O2ljvi4F`5gPYFms0urbt;CFr#mxCNG0X@(IJ+KEHXgIJ_cAwSn zzVF=K$?xPZhw9t>+dVM<_Wa%RHh($D;S$gTJUZCF?(XDw@|Q#PZT{^Zn16fz?s=QP9OQ5b=z$*Sfj!_r z!-1XBQvwo@fCOp@_?_RxA~-DmZ??>l#Q@;mvG9PU$HD2}nQ!wFLamZ{l*0!zG{xdY}jPfCCK&cFOLv z`rY@PyF2-v{N+%6n}53p=HH&bd*0?R2RU2zAc0x}e&;uF zImqD>&;vct1AD-Mh66ig_gVe!`_A2+{7(LIsJ_j=-2?M)&)+?7^Ou7hE&)Bz13j<@ z9B4SOQ+i530uqowEdjsto46e0a0%#v9_WER;6THHowEC^e)oOn?oNIue>qg&=HKpt z`M2lqp11kSK@OLI9_WD{*aHqU9M~y6B_II_NT8O0-}y~k4sy5z^gs{vz#ed*;lNJW zeOABwzH@gczmvZls&Dge_rUzy^LNkN{N*5rOF$3wKo9Hz2O19Sl%5ihfCMB^OTh2^ zCN2j#TmpKa2YO%+IM8rlr|dqf-+kY?yOZC^Uk=r``L}yu{_Xj@=WYISki#XQ2YR3f z_J9Ko2X;zN2}nQ!5~wBMcYYI>gB&gaJDyIIvT8pVjZa@7&$V@8mCs>f8L= zJuv_F{N3|5e>uqE63_!Z&;xtGfrbM+rKbcWAOQ)~67V~}iOWF_mw+DVfgacc4m2Ft zDZ9_=ci(sJ?&Np!mqYb!{_P%^e|!G!d7Hl+WpYft}J*0uqpb1ZoNR zo!`XeAcspp5A;9}>;VTF4(ycOXZ5@9J9l^TJNe6@`ZoV|56r(kfA_r2Uk-A(1oS`; z^uQi)py9wy=_vsTNI(L$1pLl#;&PC~C7=g-pa=GV0}Tgu%I>rJ-S?flJNcdb-_pV5jtyfCMBUfm#B7=QnXV$l(&u13l0Kd%%H) z13P8+S^e(&&fT5-PX2PJzRkbg1M_ds-#u^hmxCNG0X@(IJ+KEHXgIJ_dP+b75|BVG z0l)K`xE$ng3Fv_y=z%@pK*NEZviq!l_kHK?PJSnUIaJ@~-|m6=x99JkxB1IK4wrx) z=z$*C0}eDC*eN|FAOQ(Tpq7B&`Au97a<~NaKo9i59&n)Hz)snHR=@kcb9X1dlfN9Q zZ}V^W!2H|uchB4WA~o)VCN1SC*P!0-GfE(bYW0(zhadSDMY z&~RX<>^`gCec!peli$f-4%N5$w|ijz?fJXsZT@nQ!zG{xdY}jPfCCK&c1lkPNI(J- zs3qWceiN6294-Mp&;vcN2OMZPuv2!Q)$hLV+}+9V z&;vct1AD-Mh66jLrvxM*0SVL+@H@YW%Rvs8fF9_99@qm8G#uC|yU*%(-*@irY^gs{n z0S6im?3CSS^}FvocX#qT`OBgDHve`H%)dQ<_q@$t4sy5z^gs{vz#ed*;lNJmDFF#c zKmxS{{LXLUa*)F%pa*)O2ljvi4F`70?z8&c_no^t`JMdbP<@+!y9egqp1*tE<}U|1 zTmpKa2YO%+IM8rlr}UJ71SBAVS^|FOH*q=0;S$gTJDyIIvTCNcNVedq2@ekXr9RNv;`?t%HY=kK1k`O85Lmw+DVfgacc4m2FtDLo}1 z0SQQ;mVn>+OG9PT74{zx%#(cPGD-zZ|M>^KbXS{M++)&)fXv zAcspp5A;9}>;VTF4(ybk5|DrdBv4Dh@BAh%2RU22$$Kmrn|CE#~{6PJS=E&)Bz z13j<@9B4SOQ+A)#@4oNc-O2CdFNf;e{M$V+|MvXd^EQ7u$l(&u13l0Kd%%H)13RUs z1SB8<3DgqsJHLs`K@OLI9_WD{*aHqU9M~zl&+2#Ickb@wck-7*^=T9!zG{xdY}jPfCC)h00%h0 z0S<70103K02ROh14sd`29Qc9*zWc4ma*)F%pa*(T>VbRA`Ys1KTmpKa2YO%+IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h0fiF1Vd*^yA2RU2?{bjCC7=g-pa=GV z103K02ROh14sd`29N+*4IKTl8aDW3G_<{qzcdo~Bki#XQ2YOKIfqTsQE(bYW0(zha zdSDMYzyS_$fCC)h00%h00S<70103K02ROiiFF4?P=Xxv$Ia~sIpa-QMxW}yTa*)F% zpa*)O2ljvi9N+*4IKTl8aDW3G-~b0WzyS_$fCC)(f&;#HuE%na!zG{xdQj?td(8SS z2RU2-U94sd`29N+*4IKTl8aDW3G-~b0Wz=1C~;CttKEC)GU z0(zhar5?D)tnYG=!zG{xdY}jPfCC)h00%h00S<70103K02ROh14sd`29Qc9*zIU$2 za*)F%pa*(T>VbRA`Ys1KTmpKa2YO%+IKTl8aDW3G-~b0WzyS_$fCC)h00%h0fiF1V zd*^yA2RU2?{bjCC7=g-pa=GV103K02ROh14sd`29N+*4IKTl8aDW3G z_<{qzcdo~Bki#XQ2YOKIfqTsQE(bYW0(zhadSDMYzyS_$fCC)h00%h00S<70103K0 z2ROiiFF4?P=Xxv$Ia~sIpa-QMxW}yTa*)F%pa*)O2ljvi9N+*4IKTl8aDW3G-~b0W zzyS_$fCC)(f&;#HuE%na!zG{xdQj?td(8SS2RU2-U94sd`2 z9N+*4IKTl8aDW3G-~b0Wz=1C~;CttKEC)GU0(zhar5?D)tnYG=!zG{xdY}jPfCC)h z00%h00S<70103K02ROh14sd`29Qc9*zIU$2a*)F%pa*(T>VbRA`Ys1KTmpKa2YO%+ zIKTl8aDW3G-~b0WzyS_$fCC)h00%h0fiF1Vd*^yA2RU2?{bjCC7=g- zpa=GV103K02ROh14sd`29N+*4IKTl8aDW3G_<{qzcdo~Bki#XQ2YOKIfqTsQE(bYW z0(zhadSDMYzyS_$fCC)h00%h00S<70103K02ROiiFF4?P=Xxv$Ia~sIpa-QMxW}yT za*)F%pa*)O2ljvi9N+*4IKTl8aDW3G-~b0WzyS_$fCC)(f&;#HuE%na!zG{xdQj?t zd(8SS2RU2-U94sd`29N+*4IKTl8aDW3G-~b0Wz=1C~;CttK zEC)GU0(zhar5?D)tnYG=!zG{xdY}jPfCC)h00%h00S<70103K02ROh14sd`29Qc9* zzIU$2a*)F%pa*(T>VbRA`Ys1KTmpKa2YO%+IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 zfiF1Vd*^yA2RU2?{bjCC7=g-pa=GV103K02ROh14sd`29N+*4IKTl8 zaDW3G_<{qzcdo~Bki#XQ2YOKIfqTsQE(bYW0(zhadSDMYzyS_$fCC)h00%h00S<70 z103K02ROiiFF4?P=Xxv$Ia~sIpa-QMxW}yTa*)F%pa*)O2ljvi9N+*4IKTl8aDW3G z-~b0WzyS_$fCC)(f&;#HuE%na!zG{xdQj?td(8SS2RU2-U9 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW5<+NlE)00000LH}R3BhV3gzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 k@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|w45K&ZgZ0{{R3 literal 0 HcmV?d00001 diff --git a/demo/WE_dual_mask.npy b/demo/WE_dual_mask.npy new file mode 100644 index 0000000000000000000000000000000000000000..bea451e71bc6f6befa6cd5b7c0a1963132477bd7 GIT binary patch literal 8388736 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 R1`HT5V8DO@0|pE{1_Ix%Yuo?; literal 0 HcmV?d00001 diff --git a/demo/demo_lodog.py b/demo/demo_lodog.py deleted file mode 100644 index 9cca32a..0000000 --- a/demo/demo_lodog.py +++ /dev/null @@ -1,106 +0,0 @@ -# %% -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -# Import local module -from multyscale import filters, models - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create model -model = models.LODOG_RHS2007(shape, visextent) - -# %% Integrated run -output_1 = model.apply(stimulus) - -# %% Visualise output -plt.subplot(1, 2, 1) -plt.imshow(output_1, extent=visextent) -plt.subplot(1, 2, 2) -plt.plot(output_1[512, 250:750]) - -# %% Visualise filterbank -for i in range(model.bank.filters.shape[0]): - for j in range(model.bank.filters.shape[1]): - plt.subplot( - model.bank.filters.shape[0], - model.bank.filters.shape[1], - i * model.bank.filters.shape[0] + ((j + i) * 1) + 1, - ) - plt.imshow(model.bank.filters[i, j, ...], extent=visextent) - -# %% Apply filterbank -filters_output = model.bank.apply(stimulus) - -# %% Visualise filter bank output -for i in range(filters_output.shape[0]): - for j in range(filters_output.shape[1]): - plt.subplot( - filters_output.shape[0], - filters_output.shape[1], - i * filters_output.shape[0] + ((j + i) * 1) + 1, - ) - plt.imshow(filters_output[i, j, ...], extent=visextent) - -# %% Sum over spatial scales, weighting relative to scale -multiscale_output = np.tensordot(filters_output, model.scale_weights, axes=(1, 0)) - -# %% Visualise oriented multiscale output -for i in range(multiscale_output.shape[0]): - plt.subplot(multiscale_output.shape[0], 1, i + 1) - plt.imshow(multiscale_output[i, ...], extent=visextent) - -# %% Normalize oriented multiscale outputs by local mean -# Create Gaussian window -window = filters.gaussian2d(model.bank.x, model.bank.y, (model.window_sigma, model.window_sigma)) - -# Normalize window to unit-sum (== spatial averaging filter) -window = window / window.sum() - -# Create normalizer images -normalized_multiscale_output = np.empty(multiscale_output.shape) -normalizers = np.empty(multiscale_output.shape) -for i, image in enumerate(multiscale_output): - # Square image - normalizer = np.square(image) - - # Apply Gaussian window - normalizer = filters.apply(normalizer, window) - - # Square root - normalizer = np.sqrt(normalizer) - normalizers[i, ...] = normalizer - - # Normalize - normalized_multiscale_output[i, ...] = image / normalizer - -# %% Visualise normalized multiscale output -for i in range(normalized_multiscale_output.shape[0]): - plt.subplot(normalized_multiscale_output.shape[0], 3, i * 3 + 1) - plt.imshow(multiscale_output[i, ...], extent=visextent) - plt.subplot(normalized_multiscale_output.shape[0], 3, i * 3 + 2) - plt.imshow(normalizers[i, ...], extent=visextent) - plt.subplot(normalized_multiscale_output.shape[0], 3, i * 3 + 3) - plt.imshow(normalized_multiscale_output[i, ...], extent=visextent) - -# %% Sum over orientations -output_2 = normalized_multiscale_output.sum(0) - -# %% Visualise both outputs -plt.subplot(2, 2, 1) -plt.imshow(output_1, extent=visextent) -plt.subplot(2, 2, 2) -plt.plot(output_1[512, 250:750]) - -plt.subplot(2, 2, 3) -plt.imshow(output_2, extent=visextent) -plt.subplot(2, 2, 4) -plt.plot(output_2[512, 250:750]) diff --git a/demo/normalization_LODOG.py b/demo/normalization_LODOG.py new file mode 100644 index 0000000..aad790b --- /dev/null +++ b/demo/normalization_LODOG.py @@ -0,0 +1,514 @@ +# %% [markdown] +# # LODOG normalization +# This Tutorial describes the rationale behind +# the normalization step of the LODOG model (Robinson, Hammon, de Sa 2007), +# and its implementation in `multyscale`. + +# %% Setup +# Third party libraries +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# Import local module +import multyscale + +# %% [markdown] +# ## Example stimulus +# The example stimulus used for this exploration +# is an image containing two version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_dual`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. + +# %% Load example stimulus +stimulus = np.load("WE_dual.npy") + +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(2, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, xmin=0, xmax=0.5, color="black", dashes=(1, 1)) +plt.axvline(x=8, ymin=0.25, ymax=0.75, color="black", dashes=(1, 1)) +plt.xlabel("x (deg. vis. angle)") +plt.ylabel("y (deg. vis. angle)") + +plt.subplot(2, 2, 3) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[0:512], + stimulus[512, 0:512], + color="black", + dashes=(1, 1), +) +plt.xlabel("x") +plt.ylabel("luminance (normalized)") + +plt.subplot(2, 2, 2) +plt.plot( + stimulus[256:768, 768], + np.linspace(visextent[0], visextent[1], stimulus.shape[0])[256:768], + color="black", + dashes=(1, 1), +) +plt.ylabel("y") +plt.xlabel("luminance (normalized)") +plt.show() + +# %% [markdown] +# In this image there are two White's stimuli: +# one oriented horizontally (left), and one oriented vertically (right). +# In both stimuli, +# the gray target patch embedded in the black phase of the grating +# looks brighter than the identical gray target embedded in the white phase. +# For the left stimulus, this means the left target looks brighter +# than the isoluminant right target +# (cut-through shown in bottom left panel). +# For the right stimulus, this means the top target looks brighter +# than the isoluminant bottom target +# (cut-through shown in top right panel). + +# %% [markdown] +# ## ODOG model fails +# +# This image, with two stimuli in different regions, +# challenges the original ODOG model by Blakeslee & McCourt (1997) + +# %% ODOG +ODOG = multyscale.models.ODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +output_ODOG = ODOG.apply(stimulus) + +# %% Extract target predictions +targets_ODOG = [] +mask = np.load("WE_dual_mask.npy") +for idx in np.unique(mask.astype(int)): + if idx > 0: + targets_ODOG.append(np.median(output_ODOG[mask == idx])) + +# %% Visualise +plt.subplot(2, 2, 1) +plt.imshow(output_ODOG, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, xmin=0, xmax=0.5, color="black", dashes=(1, 1)) +plt.axvline(x=8, ymin=0.25, ymax=0.75, color="black", dashes=(1, 1)) +plt.xlabel("x (deg. vis. angle)") +plt.ylabel("y (deg. vis. angle)") + +plt.subplot(2, 2, 3) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[0:512], + output_ODOG[512, 0:512], + color="black", +) +plt.xlabel("x") +plt.ylabel("brightness (ODOG)") + +plt.subplot(2, 2, 2) +plt.plot( + output_ODOG[256:768, 768], + np.linspace(visextent[0], visextent[1], stimulus.shape[0])[256:768], + color="black", +) +plt.ylabel("y") +plt.xlabel("Brightness (ODOG)") + +plt.subplot(2, 2, 4) +plt.bar(x=["left", "right", "top", "bottom"], height=targets_ODOG, color=plt.colormaps["tab10"](0)) +plt.ylim(-2, 2) +plt.axhline(y=0, linestyle="dashed", color="k") +plt.xlabel("Target region") +plt.ylabel("Brightness (ODOG; median)") + +plt.show() + +# %% [markdown] +# Here we see that the ODOG model incorrectly predicts that +# the targets in the black phases +# (left target in left/horizontal stimulus; +# top target in right/vertical stimulus) +# are _darker_ than the targets in the white phases. + + +# %% [markdown] +# **ODOG is affected by global energy** +# This unsuccessful prediction arises from the fact that +# the ODOG model normalizes each filter output at each location +# by the _image-wide_/_global_ energy of other filter outputs. +# This means that the (energy of the) right stimulus +# affects the normalization of the left stimulus, +# and vice versa. + + +# %% [markdown] +# ## LODOG model +# To overcome these kinds of issues, Robinson, Hammon, and de Sa (2007) +# developed the LODOG model variant. +# The LODOG model differs from the original/base ODOG model in its normalization step. +# Specifically, where the ODOG normalization calculates the global energy +# as the spatial RMS image-wide, +# the LODOG normalization calculates _local_ RMS, +# by averaging within a Gaussian window. +# +# Since the models differ only in the _normalization_ step, +# and not in the filterbank used in the _encoding_ step, +# the same filter(outputs) can be used for both models. + +# %% Instantiate LODOG +LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +assert np.array_equal(ODOG.bank.filters, LODOG.bank.filters) + +# %% Run LODOG normalization on -ODOG filter output +filters_output = ODOG.bank.apply(stimulus) +weighted_outputs = ODOG.weight_outputs(filters_output) + +norm_outputs = LODOG.normalize_outputs(weighted_outputs) +output_LODOG = norm_outputs.sum(axis=(0, 1)) + +# %% Extract target prediction +targets_LODOG = [] +for idx in np.unique(mask.astype(int)): + if idx > 0: + targets_LODOG.append(np.median(output_LODOG[mask == idx])) + +targets = pd.DataFrame( + { + "ODOG": targets_ODOG, + "LODOG": targets_LODOG, + }, + index=["Left", "Right", "Top", "Bottom"], +) + +# %% Visualise +plt.subplot(2, 2, 1) +plt.imshow(output_LODOG, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, xmin=0, xmax=0.5, color="black", dashes=(1, 1)) +plt.axvline(x=8, ymin=0.25, ymax=0.75, color="black", dashes=(1, 1)) +plt.xlabel("x (deg. vis. angle)") +plt.ylabel("y (deg. vis. angle)") + +plt.subplot(2, 2, 3) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[0:512], + output_LODOG[512, 0:512], + color="black", +) +plt.xlabel("x") +plt.ylabel("brightness (LODOG)") + +plt.subplot(2, 2, 2) +plt.plot( + output_LODOG[256:768, 768], + np.linspace(visextent[0], visextent[1], stimulus.shape[0])[256:768], + color="black", +) +plt.ylabel("y") +plt.xlabel("brightness (LODOG)") + +ax = plt.subplot(2, 2, 4) +targets.plot(ax=ax, kind="bar") +plt.axhline(y=0.0, linestyle="dashed", color="k") +plt.ylim(-2, 2) +plt.xlabel("Target region") +plt.ylabel("Brightness (median)") + +plt.show() + + +# %% [markdown] +# This LODOG model _can_ successfully predict that +# the targets in the black phases +# (left target in left/horizontal stimulus; +# top target in right/vertical stimulus) +# are _brighter_ than the targets in the white phases. + +# %% [markdown] +# ## LODOG normalization (compared to ODOG) +# The power of the -ODOG models comes from their normalization step, +# in which information from all the filters regulates the activity of each filter output. +# Generally, this normalization consists of three parts: +# +# 1. The _normalizing coefficients_: weighted combinations of all filter outputs +# 2. Energy-calculating (as spatial averaging) of the normalizing coefficients +# 3. Divisive normalization, where a filter output is divided by the energy (2) +# of its normalizing coefficient (1) + +# %% [markdown] +# ### Normalizing coefficients +# The LODOG model uses the same _normalizing coefficients_ $\mathbf{N}$ +# as the original ODOG model. +# Briefly, $\mathbf{N}$ consists of $6 \times 7$ 2D ($X\times Y$) matrices, +# (one _normalizing coefficient_ $n_{o',s'}$ per filter $f_{o',s'}$ to normalize). +# Each normalizing coefficient $n_{o', s'}$ is a weighted combination of all filter outputs $F$, +# where the weights $w_{o', s', o, s}$ can vary. + +# %% Define weights +scale_norm_weights = multyscale.normalization.scale_norm_weights_equal(7) +orientation_norm_weights = multyscale.normalization.orientation_norm_weights(6) +norm_weights = multyscale.normalization.create_normalization_weights( + *filters_output.shape[:2], scale_norm_weights, orientation_norm_weights +) +assert np.array_equal(norm_weights, LODOG.normalization_weights) + +# %% Normalizing images as weighted combination (tensor dot-product) of filter outputs +normalizing_coefficients = multyscale.normalization.normalizers(weighted_outputs, norm_weights) + +# Visualize each normalizing coefficient n_{o,s}, i.e. +# the normalizer image for each individual filter f_{o,s} +fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients.shape[:2]): + axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") + +# %% [markdown] +# NOTE that all normalizing coefficients within a row are identical, +# i.e., all filters of an orientation get the same normalizing coefficient in the ODOG model. + +# %% [markdown] +# ### Normalize by energy +# Rather than normalizing by this weighted sum of all filter outputs at each pixel location, +# instead the -ODOG models normalize by +# the _energy_ of the normalizing coefficient. +# Energy here is expressed as the (spatial) root-mean-square of the signal. +# +# 1. Square each ($X \times Y = 1024 \times 1024$) pixel of the combined normalizing coefficient +# 2. Spatial average (e.g., mean) over pixels ($X, Y$) +# 3. Square-root of this mean +# +# $$ RMS(n_{o',s'}) = \sqrt{\mathrm{avg}(n_{o',s',x,y})^2} $$ +# +# %% [markdown] +# The base ODOG normalized calculates an image-wide mean: + +# %% Global image RMS +normalization_RMSs = np.ndarray(weighted_outputs.shape[:2]) +for o, s in np.ndindex(normalizing_coefficients.shape[:2]): + normalization_RMSs[o, s] = np.sqrt((normalizing_coefficients[o, s] ** 2).mean()) + +# Visualise +plt.pcolor(normalization_RMSs, cmap="Greens", edgecolors="k", linewidths=1, vmin=0, vmax=1) +plt.ylabel("Orientation $o'$") +plt.xlabel("Spatial scale $s'$") +plt.show() + +# %% [markdown] +# This heatmap represents the RMS value of the normalizing coefficient +# that each filter gets normalized by. +# +# As mentioned, this image-wide calculation +# means that the (energy of the) right stimulus +# affects the normalization of the left stimulus, +# and vice versa. + +# %% [markdown] +# #### L is for Local +# The way LODOG and ODOG differ, is in how the two models +# spatially average the normalizing energy. +# +# Robinson, Hammon, & de Sa (2007) consider the ODOG kind of global image averaging +# not plausible in the human visual system. +# Considering each pixel in the 4D filter output $F_{o,s,x,y}$ +# to represent the "activity" of one "neural channel/unit", +# a global image mean would imply that a neural channel is influenced +# not just by its close neighbors with similar/overlapping receptive fields, +# but also by units responding to distant visual regions. +# +# Instead in the LODOG model, units are influenced only by (spatially) nearby units. +# Instead of the global image mean, +# the LODOG model uses a Gaussian window to average over pixels, +# giving the _local_ (estimate of) energy: +# +# $$ RMS_\mathrm{local}(n_{o',s'},\sigma) = \sqrt{G(\sigma) * (n_{o',s',x,y})^2}$$ + +# %% Gaussian averaging window +sigma = LODOG.window_sigma +window = multyscale.filters.gaussian2d(LODOG.bank.x, LODOG.bank.y, (sigma, sigma)) + +# Normalize window to unit-sum (== spatial averaging filter) +window = window / window.sum() + +# Visualize Gaussian spatial averaging window +plt.subplot(2, 2, 1) +plt.imshow(window, extent=visextent, cmap="coolwarm") +plt.subplot(2, 2, 2) +plt.plot(LODOG.bank.x[int(window.shape[0] / 2)], window[int(window.shape[0] / 2), ...]) +plt.show() + +# %% [markdown] +# The function `multyscale.normalization.spatial_avg_windows_gaussian()` +# generates a ( $O\times S$ set of) Gaussian filters $G$, +# where each Gaussian $G_{o',s'}$ is used to locally average filter $f_{o',s'}$. +# In the LODOG model, all $G$ are identical. +# +# The parameter $\sigma$ controls the spatial size of each $G(\sigma)$ Gaussian filter; +# thus this function takes in $O \times S$ $\sigma_{o', s'}$. +# In the LODOG model, all $\sigma_{o', s'}$ are identical. + +# %% Spatial Gaussian +sigmas = LODOG.window_sigmas +G = multyscale.normalization.spatial_avg_windows_gaussian(LODOG.bank.x, LODOG.bank.y, sigmas) + +assert np.array_equal(G[0, 0, :], window) + +idx = (2, 3) + +plt.imshow(G[*idx], cmap="coolwarm", extent=visextent) +plt.show() + +# %% [markdown] +# Applying this Gaussian window gives the _local_ (estimate of) of energy + +# %% Local RMS +normalization_local_RMS = np.square(normalizing_coefficients.copy()) +for o, s in np.ndindex(normalizing_coefficients.shape[:2]): + normalization_local_RMS[o, s] = multyscale.filters.apply( + window, normalization_local_RMS[o, s], padval=0 + ) + +normalization_local_RMS = ( + np.sqrt(normalization_local_RMS + 1e-6) + 1e-6 +) # minor offset to avoid negatives/0's + +assert np.array_equal(normalization_local_RMS, LODOG.normalizers_to_RMS(normalizing_coefficients)) + +# Visualize each local RMS +fig, axs = plt.subplots(*normalization_local_RMS.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalization_local_RMS.shape[:2]): + axs[o, s].imshow(normalization_local_RMS[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# NOTE that the _local_ energy here for each filter has to be a full $X \times Y$ pixel array, +# whereas the global energy in the base ODOG model is a single scalar value for each filter. +# Also NOTE that the energy in each _row_ (i.e., across scales) is constant, +# only along columns (i.e., across orientations) does the energy vary. +# +# Here we can see that +# the normalizing coefficients constructed from more vertically-oriented filer(outputs) +# -- top and bottom rows -- +# have more energy on the right hand side of the image. +# Conversely, +# the normalizing coefficients constructed from more horizontally-oriented filter(outputs) +# -- middle rows -- +# have more energy on the left hand side of the image. + + +# %% [markdown] +# ### Divisive normalization +# These matrices, expressing the local energy (Gaussian RMS) of the normalizing coefficients, +# form the denominator of the divisive normalization. +# +# Since the local energy is different in the two halves, +# we can see that the filters sensitive to vertical contrast (top/bottom row) +# will get normalized more strongly on the right hand side of the image. +# Conversely, the filters sensitive to horizontal contrast (middle row(s)) +# will get normalized more strongly on the left hand side of the image. +# Thus, the filter outputs to the two halves of the image, i.e., to the two stimuli, +# get normalized quite differently. +# +# We now divide each filter(output) $f_{o',s'}$ +# by the spatial RMS of the normalizer coefficient $n_{o',s'}$: +# $$f'_{o',s'} = \frac{f_{o',s'}}{RMS(n_{o',s'})}$$ + +# %% Divisive normalization +# Since the local RMSs tensor is the same (O, S, X, Y) shape as the filter outputs +# we can simply divide +normalized_outputs = weighted_outputs / normalization_local_RMS +assert np.array_equal(normalized_outputs, norm_outputs) + +# Visualize each normalized f'_{o',s'} +fig, axs = plt.subplots(*normalized_outputs.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalized_outputs.shape[:2]): + axs[o, s].imshow(normalized_outputs[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + + +# %% [markdown] +# Thus, the full normalization schema of LODOG can be formulated as: +# $$ F' = \frac{f_{o',s',x,y}}{\sqrt{G(\sigma) * +# (\sum_{o=1}^{O}\sum_{s=1}^{S} w_{o',s',o,s}f_{o,s,x,y})^2}}$$ +# where $w_{o', s', o, s} = \begin{cases} +# 1 & o = o' \\ +# 0 & else +# \end{cases} +# $ + +# %% [markdown] +# ## Readout +# Finally, we readout a final model output +# by recombining the normalized filter outputs +# -- simply by summing them all up. + +# %% Recombine +recombined_outputs = np.sum(normalized_outputs, axis=(0, 1)) +assert np.array_equal(recombined_outputs, output_LODOG) + +plt.subplot(2, 2, 1) +plt.imshow(recombined_outputs, cmap="coolwarm", extent=visextent) +plt.axhline(y=0, xmin=0, xmax=0.5, color="black", dashes=(1, 1)) +plt.axvline(x=8, ymin=0.25, ymax=0.75, color="black", dashes=(1, 1)) +plt.xlabel("x (deg. vis. angle)") +plt.ylabel("y (deg. vis. angle)") + +plt.subplot(2, 2, 3) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[0:512], + recombined_outputs[512, 0:512], + color="black", +) +plt.xlabel("x") +plt.ylabel("brightness (ODOG)") + +plt.subplot(2, 2, 2) +plt.plot( + recombined_outputs[256:768, 768], + np.linspace(visextent[0], visextent[1], stimulus.shape[0])[256:768], + color="black", +) +plt.ylabel("y") +plt.xlabel("Brightness (ODOG)") + +plt.subplot(2, 2, 4) +plt.bar( + x=["left", "right", "top", "bottom"], height=targets_LODOG, color=plt.colormaps["tab10"](1) +) +plt.ylim(-2, 2) +plt.axhline(y=0, linestyle="dashed", color="k") +plt.xlabel("Target region") +plt.ylabel("Brightness (LODOG; median)") + +plt.show() + + +# %% [markdown] +# In the horizontal cut, we can see that the model output +# is now greater for the targets in the black phases +# than for the targets in the white phaes +# -- in the same direction as the perceived brightness effect. + +# %% [markdown] +# The normalization by global energy in the original ODOG model, +# means this model fails to correctly predict for those images +# where the perceptual effect is present in more localized regions. +# The different regions of the image, which are perceived separately, +# affect each other's normalization. +# +# Instead, the LODOG model implements normalization by _local_ energy estimates. +# This prevents some of this cross-contamination by different image regions. +# As a result, the LODOG model overcomes the challenge to the original model, +# and correctly predicts the direction of effect for the combined stimulus. From cdaf613f1ecad4f8b6b1dc9c6eba7580772fd95a Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 22 Dec 2023 16:57:26 +0100 Subject: [PATCH 40/79] docs: explore LODOG parameter Replicates Figure 3 from Robinson, Hammon, & de Sa (2007) --- demo/explore_LODOG_parameter.py | 388 ++++++++++++++++++++++++++++++++ demo/replication_lodog_param.py | 61 ----- 2 files changed, 388 insertions(+), 61 deletions(-) create mode 100644 demo/explore_LODOG_parameter.py delete mode 100644 demo/replication_lodog_param.py diff --git a/demo/explore_LODOG_parameter.py b/demo/explore_LODOG_parameter.py new file mode 100644 index 0000000..bf984ed --- /dev/null +++ b/demo/explore_LODOG_parameter.py @@ -0,0 +1,388 @@ +# %% [markdown] +# # Exploring LODOG normalization parameter +# This guide describes how to change the normalization parameter +# of the LODOG model +# and explores how this affects model output. +# This variation is also a replication of Robinson, Hammon, & de Sa (2007; fig 3). + +# %% Setup +# Third party libraries +import matplotlib.pyplot as plt +import numpy as np + +# Import local module +import multyscale + +# %% [markdown] +# ## Example stimulus +# The example stimulus used for this exploration +# is a version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_thick`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. + +# %% Load example stimulus +stimulus = np.load("example_stimulus.npy") + +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(1, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the stimulus image on the left, the left gray patch appears brighter than the right gray patch. +# On the right, the pixel intensity/gray scale values along the horizontal cut +# (indicated by the dashed line in the image) are shown. +# These reveal that, in fact, the two gray patches are identical in their physical intensity. + + +# %% [markdown] +# ## Exploring LODOG normalization +# The LODOG model differs from the original/base ODOG model in its normalization step +# (see normalization_LODOG). +# Specifically, where the ODOG normalization calculates the global energy +# as the spatial RMS image-wide, +# the LODOG normalization calculates _local_ RMS, +# by averaging within a Gaussian window. + +# %% Instantiate model +LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent) + +# %% Apply filterbank to (example) stimulus +filters_output = LODOG.bank.apply(stimulus) +filters_output = LODOG.weight_outputs(filters_output) + +# Visualise each filter output +fig, axs = plt.subplots(*filters_output.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(filters_output.shape[:2]): + axs[o, s].imshow( + filters_output[o, s], + cmap="coolwarm", + extent=visextent, + vmin=filters_output.min(), + vmax=filters_output.max(), + ) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() + + +# %% [markdown] +# Generally, the -ODOG normalization steps consist of three parts: +# +# 1. The _normalizing coefficients_: weighted combinations of all filter outputs +# 2. Energy-calculating (as spatial averaging) of the normalizing coefficients +# 3. Divisive normalization, where a filter output is divided by the energy (2) +# of its normalizing coefficient (1) +# +# The LODOG model performs a local, rather than image-wide, spatial averaging (2) +# to calculate the energy of the normalizing coefficients. +# +# The size of the local averaging window will affect the model predictions. +# To explore how, here we apply various parameterizations of this normalization +# to the same stimulus image and corresponding (weighted) filter outputs. + +# %% [markdown] +# The normalizing coefficients (1) are not affected by +# the LODOG-specific spatial window parameter, +# and thus also only need to be calculated once for all the parameterizations explored here. + +# %% +normalizing_coefficients = LODOG.normalizers(filters_output) + +# %% Gaussian spatial averaging window [markdown] +# The spatial averaging window in the LODOG model is +# a circular 2D Gaussian. +# Thus, its size is specified as a single standard deviation $\sigma$ +# that defines the width of the Gaussian in both directions. +# The default is $\sigma = 4°$. +# The same $\sigma$ is used for all normalizing coefficients +# (though `multyscale` implements this not as a single Gaussian filter, +# but as a set of identical filters). + +# %% +window_sigma = LODOG.window_sigma +print(f"sigma = {window_sigma} deg") +window_sigmas = LODOG.window_sigmas + +assert np.all(window_sigmas == window_sigma) + +spatial_filters = multyscale.normalization.spatial_avg_windows_gaussian( + LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas +) +plt.subplot(1, 2, 1) +plt.imshow(spatial_filters[0, 0, ...], cmap="coolwarm", extent=visextent) +plt.colorbar() +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + spatial_filters[0, 0, 512, :], + color="black", +) +plt.axvline(x=-window_sigma, color="grey", linestyle="dashed") +plt.axvline(x=window_sigma, color="grey", linestyle="dashed") +plt.xlabel("X (deg. vis. angle)") +plt.ylabel("Y (deg. vis. angle)") +plt.show() + +# %% [markdown] +# The model method `.normalizers_to_RMS()` automatically generates and applies +# these spatial averaging windows to the proved normalizing coeffiencts. +# This produces an $O \times S$ set of locally ($Y \times X$) calculated energies, +# one for each normalizaing coefficient. + +# %% +energies_4 = LODOG.normalizers_to_RMS(normalizing_coefficients) + +# Visualize each local energy +fig, axs = plt.subplots(*energies_4.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(energies_4.shape[:2]): + axs[o, s].imshow(energies_4[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# These locally calculated energies form the denominator of each divisive normalization. +# Since the local energy tensor is the same $(O, S, X, Y)$ shape as the filter outputs +# we can simply divide. + +# %% +normalized_outputs_4 = filters_output / energies_4 + +# Visualize each normalized output +fig, axs = plt.subplots(*normalized_outputs_4.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalized_outputs_4.shape[:2]): + axs[o, s].imshow(normalized_outputs_4[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# Combining the normalized outputs gives a final model prediction. + +# %% +output_LODOG_4 = np.sum(normalized_outputs_4, axis=(0, 1)) + +# %% [markdown] +# Adjusting spatial averaging window size +# The easiest way to adjust the window sigma, +# is to set the `window_sigma` attribute of the model-object. + +# %% +LODOG.window_sigma = 1 + +# %% [markdown] +# Since `multyscale` actually implements the spatial averaging window +# separately for each normalizing coefficient (i.e., for each model-filter) +# this requires then to also set the whole set of ($O \times S$) `window_sigmas`: + +# %% +LODOG.window_sigmas = np.ones(shape=(*LODOG.bank.shape[:2], 2)) * LODOG.window_sigma +assert np.all(LODOG.window_sigmas == LODOG.window_sigma) + +# %% +spatial_filters = multyscale.normalization.spatial_avg_windows_gaussian( + LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas +) +plt.subplot(1, 2, 1) +plt.imshow(spatial_filters[0, 0, ...], cmap="coolwarm", extent=visextent) +plt.colorbar() +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + spatial_filters[0, 0, 512, :], + color="black", +) +plt.axvline(x=-window_sigma, color="grey", linestyle="dashed") +plt.axvline(x=window_sigma, color="grey", linestyle="dashed") +plt.xlabel("X (deg. vis. angle)") +plt.ylabel("Y (deg. vis. angle)") +plt.show() + +# %% [markdown] +# From here, the exact same steps can be taken to calculate these +# more locally restricted (smaller spatial averaging window) energies + +# %% +energies_1 = LODOG.normalizers_to_RMS(normalizing_coefficients) + +# Visualize each local energy +fig, axs = plt.subplots(*energies_4.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(energies_1.shape[:2]): + axs[o, s].imshow(energies_1[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# What can be seen here, is that the more local energy calculation +# means much more spatial variation over the whole image, +# as would be expected. + +# %% +normalized_outputs_1 = filters_output / energies_1 + +# Visualize each normalized output +vmin = min(np.min(normalized_outputs_4), np.min(normalized_outputs_1)) +vmax = max(np.max(normalized_outputs_4), np.max(normalized_outputs_1)) +fig, axs = plt.subplots(*normalized_outputs_1.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalized_outputs_1.shape[:2]): + axs[o, s].imshow(normalized_outputs_1[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# Resultingly, the normalized filter outputs can also vary much more locally. + +# %% [markdown] +# Combining the normalized outputs gives a final model prediction. + +# %% +output_LODOG_1 = np.sum(normalized_outputs_1, axis=(0, 1)) + +# %% Comparing LODOG outputs +vmin = min(np.min(output_LODOG_4), np.min(output_LODOG_1)) +vmax = max(np.max(output_LODOG_4), np.max(output_LODOG_1)) + +plt.subplot(2, 2, 1) +plt.imshow(output_LODOG_4, cmap="coolwarm", vmin=vmin, vmax=vmax, extent=visextent) +plt.subplot(2, 2, 2) +plt.imshow(output_LODOG_1, cmap="coolwarm", vmin=vmin, vmax=vmax, extent=visextent) +plt.subplot(2, 1, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + output_LODOG_4[512, :], + color="black", + linestyle="dotted", + label="LODOG $\sigma=4$", +) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + output_LODOG_1[512, :], + color="black", + linestyle="dashed", + label="LODOG $\sigma=1$", +) +plt.legend() +plt.show() + +# %% [markdown] +# Here it can be seen that in the complete model output +# for the LODOG model with spatial filtering $\sigma = 1°$ +# shows a lot more spatial inhomogeneity; +# the neutral gray background/surround shows strong inhomogeneity in the model output, +# but also the bars of carrier grating show stronger edge artifacts. + +# %% [markdown] +# ### Construct different models +# An easier way to explore different values for this parameter, +# is to create a different instance of the `LODOG_RHS2007` model-class +# passing in the `window_sigma` parameter to the constructor. +# +# Since the model-object has then been constructed already with the different `window_sigma`, +# we can just run the `.normalize_outputs()` method +# to normalize the filter outputs accordingly. +# +# To then readout the final model prediction, +# we sum over orientations and spatial scales. + +# %% +LODOG_2 = multyscale.models.LODOG_RHS2007( + shape=stimulus.shape, visextent=visextent, window_sigma=2 +) +output_LODOG_2 = LODOG_2.normalize_outputs(filters_output).sum((0, 1)) + +# %% [markdown] +# ## Recreate Robinson, Hammon, & de Sa (2007, Fig. 3) +# Robinson, Hammon, & de Sa (2007) compare +# three values for the $\sigma$ parameter +# controlling the size of the (local) spatial normalization window: +# $1°$, $2°$, and $4°$. +# Figure 3 shows and compares the spatial inhomogeneities +# of the three different parameterizations of the model. +# Also, these three versions of LODOG will be compared to the default ODOG model. + +# %% +ODOG = multyscale.models.ODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +output_ODOG = ODOG.normalize_outputs(filters_output).sum((0, 1)) + + +# %% [markdown] +# To more properly compare the outputs of these different model instances, +# Robinson, Hammon, & de Sa (2007) standardize model outputs such that +# the difference between the mean output in the two target regions +# on the `WE_thick` stimulus +# is equal to $1.0$. +# +# To implement this here, we: +# - load a mask image (as a `numpy.NDArray`) that defines the two target regions +# - apply this mask to model output to get model output in just the two target regions +# - calculate the mean for each target region +# - subtract the two means +# - divide the whole model output by this difference + +# %% Standardize such that effect size = 1 +mask = np.load("example_stimulus_mask.npy") + + +def target_diff(output, mask): + left_target_mask = mask == 1 + right_target_mask = mask == 2 + + left_target_output = output[left_target_mask] + right_target_output = output[right_target_mask] + return left_target_output.mean() - right_target_output.mean() + + +output_ODOG /= target_diff(output_ODOG, mask) +output_LODOG_1 /= target_diff(output_LODOG_1, mask) +output_LODOG_2 /= target_diff(output_LODOG_2, mask) +output_LODOG_4 /= target_diff(output_LODOG_4, mask) + +# %% [markdown] +# This, then, gives us the outputs scaled into the same range, +# which perfectly replicates Figure 3 from Robinson, Hammon, & de Sa (2007). + +# %% +plt.figure(figsize=(6, 10)) + +ax = plt.subplot(2, 1, 1) +ax.imshow(stimulus[275:750, :], cmap="gray") +ax.axhline(y=235, color="black", dashes=(1, 1)) + + +plt.subplot(2, 1, 2) +plt.plot(output_ODOG[512, :], color="black", linestyle="solid", label="ODOG") +plt.plot(output_LODOG_4[512, :], color="grey", linestyle="dotted", label="LODOG N=4") +plt.plot(output_LODOG_2[512, :], color="grey", linestyle="solid", label="LODOG N=2") +plt.plot(output_LODOG_1[512, :], color="black", linestyle="dashed", label="LODOG N=1") + +plt.grid(axis="y") +plt.yticks(ticks=range(-9, 12, 3)) + +plt.legend() + +plt.show() diff --git a/demo/replication_lodog_param.py b/demo/replication_lodog_param.py deleted file mode 100644 index c2ac116..0000000 --- a/demo/replication_lodog_param.py +++ /dev/null @@ -1,61 +0,0 @@ -# %% -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from cycler import cycler -from PIL import Image - -# Import local module -from multyscale import models - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create models -ODOG = models.ODOG_RHS2007(shape, visextent) -LODOG = models.LODOG_RHS2007(shape, visextent) - -# %% -output_odog = ODOG.apply(stimulus) -filters_output = LODOG.bank.apply(stimulus) -weighted_outputs = LODOG.weight_outputs(filters_output) -output_odog2 = ODOG.normalize_outputs(weighted_outputs).sum((0, 1)) - - -# %% -plt.plot(output_odog[512, :] / 32, label="ODOG") -np.allclose(output_odog2, output_odog) - -# %% -LODOG.window_sigma = 1 -LODOG.window_sigmas = np.ones(shape=(6, 7, 2)) * LODOG.window_sigma -output_lodog1 = LODOG.normalize_outputs(weighted_outputs).sum((0, 1)) - -LODOG.window_sigma = 2 -LODOG.window_sigmas = np.ones(shape=(6, 7, 2)) * LODOG.window_sigma -output_lodog2 = LODOG.normalize_outputs(weighted_outputs).sum((0, 1)) - -LODOG.window_sigma = 4 -LODOG.window_sigmas = np.ones(shape=(6, 7, 2)) * LODOG.window_sigma -output_lodog4 = LODOG.normalize_outputs(weighted_outputs).sum((0, 1)) - -# %% -scale = 32 -linestyle_cycler = cycler("linestyle", ["-", ":", "-.", "--"]) -f = plt.figure(figsize=(5, 5)) -plt.rc("axes", prop_cycle=linestyle_cycler) -plt.plot(output_odog[512, :] / scale, label="ODOG") -plt.plot(output_lodog4[512, :] / scale * 2, label="LODOG 4deg") -plt.plot(output_lodog2[512, :] / scale * 2, label="LODOG 2deg") -plt.plot(output_lodog1[512, :] / scale * 4.5, label="LODOG 1deg") -plt.ylim(-9, 9) -plt.yticks(np.arange(-9, 9, step=3)) -plt.grid(True, axis="y") -plt.legend() -plt.savefig("RHS_Fig_3_LODOG_params.pdf", bbox_inches=0, transparent=False) -# %% From 0746a719d7df5008971b15106448e47f522edb4b Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 22 Dec 2023 13:24:58 +0100 Subject: [PATCH 41/79] docs: normalization FLODOG, why and implementation --- demo/demo_flodog.py | 125 ------------ demo/normalization_FLODOG.py | 385 +++++++++++++++++++++++++++++++++++ 2 files changed, 385 insertions(+), 125 deletions(-) delete mode 100644 demo/demo_flodog.py create mode 100644 demo/normalization_FLODOG.py diff --git a/demo/demo_flodog.py b/demo/demo_flodog.py deleted file mode 100644 index 4cd0d05..0000000 --- a/demo/demo_flodog.py +++ /dev/null @@ -1,125 +0,0 @@ -# %% -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -# Import local module -from multyscale import filters, models - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create model -model = models.FLODOG_RHS2007(shape, visextent) - -# %% Integrated run -output_1 = model.apply(stimulus) - -# %% Visualise output -plt.subplot(1, 2, 1) -plt.imshow(output_1, extent=visextent) -plt.subplot(1, 2, 2) -plt.plot(output_1[512, 250:750]) - - -# %% Visualise filterbank -for i in range(model.bank.filters.shape[0]): - for j in range(model.bank.filters.shape[1]): - plt.subplot( - model.bank.filters.shape[0], - model.bank.filters.shape[1], - i * model.bank.filters.shape[0] + ((j + i) * 1) + 1, - ) - plt.imshow(model.bank.filters[i, j, ...], extent=visextent) - -# %% Apply filterbank -filters_output = model.bank.apply(stimulus) - -# %% Visualise filter bank output -for i in range(filters_output.shape[0]): - for j in range(filters_output.shape[1]): - plt.subplot( - filters_output.shape[0], - filters_output.shape[1], - i * filters_output.shape[0] + ((j + i) * 1) + 1, - ) - plt.imshow(filters_output[i, j, ...], extent=visextent) - - -# %% Weight each filter output according to scale -weighted_outputs = np.ndarray(filters_output.shape) -for i in range(filters_output.shape[0]): - for j, output in enumerate(filters_output[i, ...]): - weighted_outputs[i, j, ...] = output * model.scale_weights[j] - -# %% Build normalizer for each filter output -center_sigmas = [sigma[0][0] for sigma in model.bank.sigmas] -sdmix = model.sdmix # stdev of Gaussian weights for scale mixing - -# Create normalizer images -normalizers = np.empty(weighted_outputs.shape) -for o, multiscale in enumerate(weighted_outputs): # separate for orientations - for i, filt in enumerate(multiscale): - normalizer = np.empty(filt.shape) - - # Identify relative index of each scale to the current one - rel_i = i - np.asarray(range(multiscale.shape[0])) - - # Gaussian weights, based on relative index - gweights = np.exp(-(rel_i**2) / 2 * sdmix**2) / (sdmix * np.sqrt(2 * np.pi)) - - # Sum filter outputs, by Gaussian weights - normalizer = np.tensordot(multiscale, gweights, axes=(0, 0)) - - # Normalize normalizer... - area = gweights.sum() - normalizer = normalizer / area - - # Accumulate - normalizers[o, i, ...] = normalizer - -# %% Blur normalizers -# Create Gaussian window -window = filters.gaussian2d(model.bank.x, model.bank.y, (model.window_sigma, model.window_sigma)) - -# Normalize window to unit-sum (== spatial averaging filter) -window = window / window.sum() - -for o, multiscale in enumerate(normalizers): - for s, normalizer in enumerate(multiscale): - # Square image - normalizer = np.square(normalizer) - - # Apply Gaussian window - normalizer = filters.apply(normalizer, window) - - # Square root - normalizer = np.sqrt(normalizer) - normalizers[o, s, ...] = normalizer - -# %% Normalize filter output -normalized_outputs = np.ndarray(weighted_outputs.shape) -for o, s in np.ndindex(weighted_outputs.shape[:2]): - normalized_outputs[o, s] = weighted_outputs[o, s] / normalizers[o, s] - -# %% Sum over orientations -output_2 = normalized_outputs.sum((0, 1)) - -# %% Visualise both outputs -plt.subplot(2, 2, 1) -plt.imshow(output_1, extent=visextent) -plt.subplot(2, 2, 2) -plt.plot(output_1[512, 250:750]) - -plt.subplot(2, 2, 3) -plt.imshow(output_2, extent=visextent) -plt.subplot(2, 2, 4) -plt.plot(output_2[512, 250:750]) - -np.allclose(output_1, output_2) diff --git a/demo/normalization_FLODOG.py b/demo/normalization_FLODOG.py new file mode 100644 index 0000000..aa5b1aa --- /dev/null +++ b/demo/normalization_FLODOG.py @@ -0,0 +1,385 @@ +# %% [markdown] +# # FLODOG normalization +# This Tutorial describes the rationale behind +# the normalization step of the FLODOG model (Robinson, Hammon, de Sa 2007), +# and its implementation in `multyscale`. + +# %% Setup +# Third party libraries +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# Import local module +import multyscale + +# %% [markdown] +# ## Example stimulus +# The example stimulus used for this exploration +# is a version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_thick`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. + +# %% Load example stimulus +stimulus = np.load("example_stimulus.npy") + +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(1, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the stimulus image on the left, the left gray patch appears brighter than the right gray patch. +# On the right, the pixel intensity/gray scale values along the horizontal cut +# (indicated by the dashed line in the image) are shown. +# These reveal that, in fact, the two gray patches are identical in their physical intensity. + + +# %% [markdown] +# ## FLODOG: making -ODOG more biologically plausible +# +# Robinson, Hammon, & de Sa (2007) argue for two changes to the -ODOG models, +# based on known properties of the visual system. +# +# Firstly, the divisive normalization should be spatially local. +# This is partly implemented in the LODOG normalization step (see normalization_LODOG). +# However, it would be more plausible if the spatial window for normalization +# depends on scale of the filter being normalized: +# since small filters operate on small spatial areas, +# the normalization should similarly only be affected by nearby activity; +# and large scale filter should be affected by energy in more distal regions. +# +# Secondly, they argue that because similar spatial frequencies cluster in early visual regions, +# they are also more likely to inhibit each other. +# In other words, filters at similar scales (sensitive to similar spatial frequencies) +# should normalize each other more strongly than filters with very different sensitivies. +# +# Thus, Robinson, Hammon, & de Sa (2007) propose a +# (spatial) **F**requency-specific **L**ocally normalized -ODOG (FLODOG) model, +# which builds on the LODOG model but differs in these two aspects. + +# %% +LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +FLODOG = multyscale.models.FLODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +assert np.array_equal(FLODOG.bank.filters, LODOG.bank.filters) + + +# %% [markdown] +# The models share the same filterbank frontend, +# so it's only necessary to apply this bank +# (and weight the filteroutputs) +# once. + +# %% +filters_output = FLODOG.bank.apply(stimulus) +filters_output = FLODOG.weight_outputs(filters_output) + +# %% [markdown] +# ## FLODOG normalization +# Generally, the -ODOG normalization step consists of three parts: +# +# 1. The _normalizing coefficients_: weighted combinations of all filter outputs +# 2. Energy-calculating (as spatial averaging) of the normalizing coefficients +# 3. Divisive normalization, where a filter output is divided by the energy (2) +# of its normalizing coefficient (1) +# +# The FLODOG differs from the (L)ODOG models in both normalizing coefficients (1), +# and how energy is estimated (2). + +# %% [markdown] +# ### Normalizing coefficients +# Where the (L)ODOG models use equal weighting +# for filters of all scales to construct the normalizing coefficients $\mathbf{N}$, +# the FLODOG normalizing coefficients weight more heavily +# those spatial scales that are more similar to the filter being normalized +# than more different scales. + +# %% [markdown] +# Instead of the equal weighting, +# the FLODOG model uses a 1D Gaussian as the weights profile: +# centered on the spatial scale of the filter being normalized +# and dropping off as a Gaussian function of the relative index the other spatial scales. + +# %% Define weights +scale_norm_weights_FLODOG = multyscale.normalization.scale_norm_weights_gaussian(7, sdmix=0.5) +assert np.array_equal(scale_norm_weights_FLODOG, FLODOG.scale_norm_weights) + +# %% +fig, axs = plt.subplots(2, 2, sharex="row", sharey="row") +axs[0, 0].pcolor( + LODOG.scale_norm_weights, + cmap="Greens", + edgecolors="k", + linewidths=1, + vmin=0, + vmax=1, +) +axs[0, 0].set_ylabel("scale of filter to normalize (idx)") +axs[0, 0].set_title("LODOG weights") + +axs[0, 1].pcolor( + FLODOG.scale_norm_weights, + cmap="Greens", + edgecolors="k", + linewidths=1, + vmin=0, + vmax=1, +) +axs[0, 1].set_xlabel("scale of filter to normalize (idx)") +axs[0, 1].set_title("FLODOG weights") + +axs[1, 0].plot(LODOG.scale_norm_weights[3, :], color="black") +axs[1, 0].set_xlabel("scale of other filter (idx)") +axs[1, 0].set_ylabel("weight") + +axs[1, 1].plot(FLODOG.scale_norm_weights[3, :], color="black") +axs[1, 1].set_xlabel("scale of other filter (idx)") + +plt.show() + +# %% [markdown] +# Since these weights are strongly biased towards same/similar spatial scales, +# the resulting normalizing coefficients also more strongly resemble +# the filter outputs at these spatial scales. + +# %% Normalizing coefficients +normalizing_coefficients_LODOG = LODOG.normalizers(filters_output) +normalizing_coefficients_FLODOG = FLODOG.normalizers(filters_output) + +# Visualize each norm. coeff. +vmin = min(np.min(normalizing_coefficients_LODOG), np.min(normalizing_coefficients_FLODOG)) +vmax = max(np.max(normalizing_coefficients_LODOG), np.max(normalizing_coefficients_FLODOG)) + +fig, axs = plt.subplots(*normalizing_coefficients_LODOG.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients_LODOG.shape[:2]): + axs[o, s].imshow( + normalizing_coefficients_LODOG[o, s], + cmap="coolwarm", + extent=visextent, + vmin=vmin, + vmax=vmax, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +fig.suptitle("LODOG") +plt.show() + +fig, axs = plt.subplots(*normalizing_coefficients_FLODOG.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients_FLODOG.shape[:2]): + axs[o, s].imshow( + normalizing_coefficients_FLODOG[o, s], + cmap="coolwarm", + extent=visextent, + vmin=vmin, + vmax=vmax, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +fig.suptitle("FLODOG") +plt.show() + + +# %% [markdown] +# ### Scale-localized energy estimate +# Secondly, the LODOG and FLODOG models differ in +# the Gaussian spatial averaging window that they use +# to locally calculate the energy (spatial RMS) of each normalization coefficients. + +# Where the LODOG model uses a single Gaussian window for all normalization coefficients, +# in FLODOG, the width of this spatial averaging window scales with the scale of the filter +# being normalized. +# +# Thus the normalization of the **F**LODOG model is "localized" +# both in space, and in _spatial scale_ / _**F**requency_. + +# %% Spatial averaging window +spatial_windows_LODOG = multyscale.normalization.spatial_avg_windows_gaussian( + LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas +) +spatial_windows_FLODOG = multyscale.normalization.spatial_avg_windows_gaussian( + FLODOG.bank.x, FLODOG.bank.y, FLODOG.window_sigmas +) + +# Visualize each spatial avg. window +fig, axs = plt.subplots(2, spatial_windows_LODOG.shape[1], sharex="all", sharey="all") +for s in range(spatial_windows_LODOG.shape[1]): + axs[0, s].imshow( + spatial_windows_LODOG[3, s], + cmap="coolwarm", + extent=visextent, + ) + axs[1, s].imshow( + spatial_windows_FLODOG[3, s], + cmap="coolwarm", + extent=visextent, + ) +axs[0, 3].set_title("LODOG") +axs[1, 3].set_title("FLODOG") +plt.show() + +# %% [markdown] +# Both these factors influence the final energy estimates +# that form the denominators of the normalization + +# %% Energy estimates +energies_LODOG = LODOG.normalizers_to_RMS(normalizing_coefficients_LODOG) +energies_FLODOG = FLODOG.normalizers_to_RMS(normalizing_coefficients_FLODOG) + +# Visualize +vmin = min(np.min(energies_LODOG), np.min(energies_FLODOG)) +vmax = max(np.max(energies_LODOG), np.max(energies_FLODOG)) +fig, axs = plt.subplots(*energies_LODOG.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(energies_LODOG.shape[:2]): + axs[o, s].imshow( + energies_LODOG[o, s], + cmap="coolwarm", + extent=visextent, + vmin=vmin, + vmax=vmax, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +fig.suptitle("LODOG") +plt.show() + +fig, axs = plt.subplots(*energies_FLODOG.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(energies_FLODOG.shape[:2]): + axs[o, s].imshow( + energies_FLODOG[o, s], + cmap="coolwarm", + extent=visextent, + vmin=vmin, + vmax=vmax, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +fig.suptitle("FLODOG") +plt.show() + +# %% [markdown] +# With these parameters, +# the normalizing energy coefficients (i.e., denominators in normalization) +# of the FLODOG model are much more spatially localized, +# and look much more similar to the filters that they are normalizing. +# Moreover, in absolute terms, the values are a lot smaller, +# meaning stronger enhancement of channels + +# %% +pd.DataFrame( + { + "LODOG": pd.Series({"min": np.min(energies_LODOG), "max": np.max(energies_LODOG)}), + "FLODOG": pd.Series({"min": np.min(energies_FLODOG), "max": np.max(energies_FLODOG)}), + } +) + +# %% [markdown] +# As a result, +# the FLODOG normalization results in much stronger local "peaks" +# and thus more spatial inhomogeneities + +# %% Normalize +LODOG_normalized = filters_output / energies_LODOG +FLODOG_normalized = filters_output / energies_FLODOG + +# Visualize normalized outputs +vmin = min(np.min(LODOG_normalized), np.min(FLODOG_normalized)) +vmax = max(np.max(LODOG_normalized), np.max(FLODOG_normalized)) +fig, axs = plt.subplots(*LODOG_normalized.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(LODOG_normalized.shape[:2]): + axs[o, s].imshow( + LODOG_normalized[o, s], + cmap="coolwarm", + extent=visextent, + vmin=vmin, + vmax=vmax, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +fig.suptitle("LODOG") +plt.show() + +fig, axs = plt.subplots(*FLODOG_normalized.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(FLODOG_normalized.shape[:2]): + axs[o, s].imshow( + FLODOG_normalized[o, s], + cmap="coolwarm", + extent=visextent, + vmin=vmin, + vmax=vmax, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +fig.suptitle("FLODOG") +plt.show() + + +# %% [markdown] +# Similarly, this then shows up in the final integrated outputs + +# %% Integrated +output_LODOG = LODOG_normalized.sum((0, 1)) +output_FLODOG = FLODOG_normalized.sum((0, 1)) + +# Visualize outputs +vmin = min(np.min(output_LODOG), np.min(output_FLODOG)) +vmax = max(np.max(output_LODOG), np.max(output_FLODOG)) + +plt.subplot(2, 2, 1) +plt.imshow(output_LODOG, cmap="coolwarm", vmin=vmin, vmax=vmax, extent=visextent) +plt.title("LODOG") +plt.subplot(2, 2, 2) +plt.imshow(output_FLODOG, cmap="coolwarm", vmin=vmin, vmax=vmax, extent=visextent) +plt.title("FLODOG") +plt.subplot(2, 1, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + output_LODOG[512, :], + color="black", + linestyle="solid", + label="LODOG", +) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + output_FLODOG[512, :], + color="black", + linestyle="dotted", + label="FLODOG", +) +plt.legend() +plt.show() + +# %% [markdown] +# The "more biological plausible" FLODOG model predicts +# the direction of many brightness stimuli well, +# including ones that the (L)ODOG models fail to predict. +# However, it also produces these strong spatial inhomogeneities, +# which strongly emphasize borders and are clearly visible in the model outputs. +# These inhomogeneities are not immediately perceptually experienced in the original stimuli, +# though some suggest they can be measured psychophysically. +# +# From a modeling point of view, it is also important to recognize that the output ranges, +# i.e., the units on the vertical axes, are vastly different. +# Thus, for meaningful quantitative comparisons, +# some form of standardization is required. From 735ffcedc0f660abe4c66730be4b620e073736ef Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 22 Dec 2023 13:28:25 +0100 Subject: [PATCH 42/79] docs: explore FLODOG parameters Replicates Figure 5 from Robinson, Hammon, & de Sa (2007) --- demo/demo_flodog_params.ipynb | 145 ----------- demo/explore_FLODOG_parameters.py | 396 ++++++++++++++++++++++++++++++ demo/replication_flodog_params.py | 46 ---- 3 files changed, 396 insertions(+), 191 deletions(-) delete mode 100644 demo/demo_flodog_params.ipynb create mode 100644 demo/explore_FLODOG_parameters.py delete mode 100644 demo/replication_flodog_params.py diff --git a/demo/demo_flodog_params.ipynb b/demo/demo_flodog_params.ipynb deleted file mode 100644 index babbe5f..0000000 --- a/demo/demo_flodog_params.ipynb +++ /dev/null @@ -1,145 +0,0 @@ -{ - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "python385jvsc74a57bd09ce64e1dc51e0fcc26138e95c5a56d90c12dc0aa848e7879d92efc252f96a3f1", - "display_name": "Python 3.8.5 64-bit ('lightness_models')" - } - }, - "nbformat": 4, - "nbformat_minor": 2, - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Third party libraries\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from PIL import Image\n", - "import ipywidgets\n", - "\n", - "# Import local module\n", - "import multyscale" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# %% Load example stimulus\n", - "stimulus = np.asarray(Image.open(\"example_stimulus.png\").convert(\"L\"))\n", - "\n", - "# %% Parameters of image\n", - "shape = stimulus.shape # filtershape in pixels\n", - "# visual extent, same convention as pyplot:\n", - "visextent = (-16, 16, -16, 16)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# %% Create model\n", - "model = multyscale.models.FLODOG_RHS2007(shape, visextent)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "filters_output = model.bank.apply(stimulus)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "weighted_outputs = model.weight_outputs(filters_output)" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": "interactive(children=(FloatSlider(value=0.5, description='Scale-mixing Gaussian SD:', max=3.0, min=0.25, step=…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 0, - "model_id": "255cb991dd1a4167adbe247a309d8d2d" - } - }, - "metadata": {} - }, - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "" - ] - }, - "metadata": {}, - "execution_count": 29 - } - ], - "source": [ - "def f(sdmix, spatial_window_scalar):\n", - " model.spatial_window_scalar = spatial_window_scalar\n", - " model.sdmix = sdmix\n", - " model.scale_norm_weights = multyscale.normalization.scale_norm_weights_gaussian(\n", - " len(model.scale_weights), model.sdmix\n", - " )\n", - " model.normalization_weights = multyscale.normalization.create_normalization_weights(\n", - " 6, 7, model.scale_norm_weights, model.orientation_norm_weights\n", - " )\n", - " model.window_sigmas = np.broadcast_to(\n", - " np.array(model.center_sigmas)[None, ..., None], (6, 7, 2)\n", - " )\n", - "\n", - " print(f\"Running {model.sdmix,model.spatial_window_scalar}...\")\n", - " output = model.normalize_outputs(weighted_outputs).sum((0,1))\n", - "\n", - " plt.subplot(1, 2, 1)\n", - " plt.imshow(output, extent=visextent)\n", - " plt.subplot(1, 2, 2)\n", - " plt.plot(output[512, 250:750])\n", - " print(\"Done!\")\n", - "\n", - "ipywidgets.interact_manual(f, sdmix=ipywidgets.FloatSlider(description = \"Scale-mixing Gaussian SD:\", min = 0.25, max = 3., value = .5, step=0.25), spatial_window_scalar=ipywidgets.FloatSlider(description = \"Spatial averaging window scalar:\", min = 1, max = 4, value = 2, step=1))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ] -} \ No newline at end of file diff --git a/demo/explore_FLODOG_parameters.py b/demo/explore_FLODOG_parameters.py new file mode 100644 index 0000000..51d66be --- /dev/null +++ b/demo/explore_FLODOG_parameters.py @@ -0,0 +1,396 @@ +# %% [markdown] +# # Exploring FLODOG normalization parameter +# This guide describes how to change the normalization parameters +# of the FLODOG model +# and explores how this affects model output. +# This variation is also a replication of Robinson, Hammon, & de Sa (2007; fig 5). + +# %% Setup +# Third party libraries +import matplotlib.pyplot as plt +import numpy as np + +# Import local module +import multyscale + +# %% [markdown] +# ## Example stimulus +# The example stimulus used for this exploration +# is a version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_thick`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. + +# %% Load example stimulus +stimulus = np.load("example_stimulus.npy") + +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(1, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) +plt.show() + +# %% [markdown] +# In the stimulus image on the left, the left gray patch appears brighter than the right gray patch. +# On the right, the pixel intensity/gray scale values along the horizontal cut +# (indicated by the dashed line in the image) are shown. +# These reveal that, in fact, the two gray patches are identical in their physical intensity. + +# %% [markdown] +# ## Exploring FLODOG normalization +# The FLODOG model differs from the (L)ODOG models in its normalization step +# (see normalization_FLODOG). +# The parameters we are exploring here, only affect this normalization step. +# Thus, we can apply differently parameterized normalizations +# to the same (weighted) filterouputs. + +# %% Instantiate model +FLODOG = multyscale.models.FLODOG_RHS2007(shape=stimulus.shape, visextent=visextent) + +# %% Apply filterbank to (example) stimulus +filters_output = FLODOG.bank.apply(stimulus) +filters_output = FLODOG.weight_outputs(filters_output) + +# Visualise each filter output +fig, axs = plt.subplots(*filters_output.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(filters_output.shape[:2]): + axs[o, s].imshow( + filters_output[o, s], + cmap="coolwarm", + extent=visextent, + vmin=filters_output.min(), + vmax=filters_output.max(), + ) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() + +# %% Output default model +normalized_4_05 = FLODOG.normalize_outputs(filters_output) +output_4_05 = np.sum(normalized_4_05, axis=(0, 1)) + +# %% [markdown] +# Generally, the -ODOG normalization steps consist of three parts: +# +# 1. The _normalizing coefficients_: weighted combinations of all filter outputs +# 2. Energy-calculating (as spatial averaging) of the normalizing coefficients +# 3. Divisive normalization, where a filter output is divided by the energy (2) +# of its normalizing coefficient (1) +# +# Which (other) filter outputs get weighted stronger (1) in the normalizing coefficients, +# differs between the models. +# The (L)ODOG models weight every filer with the same orientation, equally. +# The FLODOG model biases the normalization towards similar spatial scales: +# the weight is a (1D) Gaussian function of the difference between +# the scale of the filter being normalized, and the other scales. +# It's centered around 0, such that filters are most strongly normalized by themselves. +# The width of this Gaussian weighting function is specified using +# the parameter `sdmix`: +# $\sigma_w = \mathrm{sdmix} \times s$ + +# %% FLODOG, with sdmix=3 +# Set parameter +sdmix = 3 +# FLODOG.sdmix = sdmix + +# Determine weight +scale_norm_weights = multyscale.normalization.scale_norm_weights_gaussian( + len(FLODOG.scale_weights), sdmix +) +# FLODOG.scale_norm_weights = scale_norm_weights +normalization_weights = multyscale.normalization.create_normalization_weights( + n_orientations=FLODOG.bank.shape[0], + n_scales=FLODOG.bank.shape[1], + scale_norm_weights=scale_norm_weights, + orientation_norm_weights=FLODOG.orientation_norm_weights, +) +# FLODOG.normalization_weights = normalization_weights + +# %% Determine normalizing coefficients +normalizing_coefficients_3 = multyscale.normalization.normalizers( + filters_output, normalization_weights +) + +# Visualise each normalizing coefficient +fig, axs = plt.subplots(*normalizing_coefficients_3.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients_3.shape[:2]): + axs[o, s].imshow( + normalizing_coefficients_3[o, s], + cmap="coolwarm", + extent=visextent, + vmin=normalizing_coefficients_3.min(), + vmax=normalizing_coefficients_3.max(), + ) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() + +# %% [markdown] +# The (F)LODOG models also perform a local spatial averaging (2) +# to calculate the energy of the normalizing coefficients. +# The size of the local averaging window will affect the model predictions. +# The FLODOG model specifically scales the local averaging window +# (the $\sigma$ of the 2D Gaussian) +# to the spatial scale of the filter being normalized. +# This is controlled by a parameter `spatial_window_scalar`: +# $\sigma = \mathrm{spatial_window_scalar} \times s$. +# To explore how, here we apply various parameterizations of this normalization +# to the same stimulus image and corresponding (weighted) filter outputs. + +# %% FLODOG with spatial_window_scalar = 2 +# Set parameter +spatial_window_scalar = 2 +# FLODOG.spatial_window_scalar = spaial_window_scalar + +# Create spatial window sigmas +window_sigmas = spatial_window_scalar * np.broadcast_to( + np.array(FLODOG.center_sigmas)[None, ..., None], (*FLODOG.bank.shape[:2], 2) +) +FLODOG.window_sigmas = window_sigmas + +# Apply spatial averaging windows to normalizing coefficients +energies_2_3 = FLODOG.normalizers_to_RMS(normalizing_coefficients_3) + +# Visualize each energy estimate +fig, axs = plt.subplots(*energies_2_3.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(energies_2_3.shape[:2]): + axs[o, s].imshow( + energies_2_3[o, s], + cmap="coolwarm", + extent=visextent, + vmin=energies_2_3.min(), + vmax=energies_2_3.max(), + ) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() + +# %% Output +normalized_2_3 = filters_output / energies_2_3 +output_2_3 = np.sum(normalized_2_3, axis=(0, 1)) + +# %% Comparing FLODOG outputs +vmin = min(np.min(output_4_05), np.min(output_2_3)) +vmax = max(np.max(output_4_05), np.max(output_2_3)) + +plt.subplot(2, 2, 1) +plt.imshow(output_4_05, cmap="coolwarm", vmin=vmin, vmax=vmax, extent=visextent) +plt.subplot(2, 2, 2) +plt.imshow(output_2_3, cmap="coolwarm", vmin=vmin, vmax=vmax, extent=visextent) +plt.subplot(2, 1, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + output_4_05[512, :], + color="black", + linestyle="dotted", + label="FLODOG $\sigma=4; sdmix=0.5$", +) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + output_2_3[512, :], + color="black", + linestyle="dashed", + label="FLODOG $\sigma=2s; sdmix=1$", +) +plt.legend() +plt.show() + + +# %% [markdown] +# ### Construct different models +# An easy(r) way to explore different values for these parameter, +# is to create a different instance of the `FLODOG_RHS2007` model-class +# passing in the `sdmix` and/or spatial_window_scaler parameter(s) to the constructor. + +# %% Instantiate new model object +FLODOG_4_3 = multyscale.models.FLODOG_RHS2007( + shape=stimulus.shape, visextent=visextent, spatial_window_scalar=4, sdmix=3 +) + +# %% [markdown] +# Since the model-object has then been constructed already with the different attributes, +# we can just run the `.normalize_outputs()` method +# to normalize the filter outputs accordingly. + +# %% Normalize +normalized_4_3 = FLODOG_4_3.normalize_outputs(filters_output) + +# %% [markdown] +# To then readout the final model prediction, +# we sum over orientations and spatial scales. + +# %% Readout +output_4_3 = np.sum(normalized_4_3, axis=(0, 1)) + +# Visualize +vmin = min(np.min(output_4_05), np.min(output_4_3)) +vmax = max(np.max(output_4_05), np.max(output_4_3)) + +plt.subplot(2, 2, 1) +plt.imshow(output_4_05, cmap="coolwarm", vmin=vmin, vmax=vmax, extent=visextent) +plt.subplot(2, 2, 2) +plt.imshow(output_4_3, cmap="coolwarm", vmin=vmin, vmax=vmax, extent=visextent) +plt.subplot(2, 1, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + output_4_05[512, :], + color="black", + linestyle="dotted", + label="FLODOG $\sigma=4; sdmix=0.5$", +) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1]), + output_4_3[512, :], + color="black", + linestyle="dashed", + label="FLODOG $\sigma=2s; sdmix=1$", +) +plt.legend() +plt.show() + +# %% [markdown] +# ## Recreate Robinson, Hammon, & de Sa (2007, Fig. 5) +# Robinson, Hammon, & de Sa (2007) compare +# three sets of parameters, +# varying the window size scaling factor between $\sigma=2s$ and $\sigma=4s$, +# and varying the width of the Gaussian defining the mixing weights between $m=0.5$ and $m=3.0$. +# +# Figure 5 shows and compares the spatial inhomogeneities +# of the three different parameterizations of the model. +# Also, these three versions of FLODOG are compared to the default LODOG model ($\sigma=4$). + +# %% FLODOG parameterizations +# FLODOG_4_05 = multyscale.models.FLODOG_RHS2007( +# shape=stimulus.shape, visextent=visextent, spatial_window_scalar=4, sdmix=0.5 +# ) +FLODOG_2_05 = multyscale.models.FLODOG_RHS2007( + shape=stimulus.shape, visextent=visextent, spatial_window_scalar=2, sdmix=0.5 +) +# FLODOG_4_3 = multyscale.models.FLODOG_RHS2007( +# shape=stimulus.shape, visextent=visextent, spatial_window_scalar=4, sdmix=3 +# ) + +# %% Outputs +output_2_05 = FLODOG_2_05.normalize_outputs(filters_output).sum((0, 1)) + +# %% LODOG, $\sigma=4$ +LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent, window_sigma=4) +output_LODOG_4 = LODOG.normalize_outputs(filters_output).sum((0, 1)) + + +# %% [markdown] +# To more properly compare the outputs of these different model instances, +# Robinson, Hammon, & de Sa (2007) standardize model outputs such that +# the difference between the mean output in the two target regions +# on the `WE_thick` stimulus +# is equal to $1.0$. +# +# To implement this here, we: +# - load a mask image (as a `numpy.NDArray`) that defines the two target regions +# - apply this mask to model output to get model output in just the two target regions +# - calculate the mean for each target region +# - subtract the two means +# - divide the whole model output by this difference + +# %% Standardize such that effect size = 1 +mask = np.load("example_stimulus_mask.npy") + + +def target_diff(output, mask): + left_target_mask = mask == 1 + right_target_mask = mask == 2 + + left_target_output = output[left_target_mask] + right_target_output = output[right_target_mask] + return left_target_output.mean() - right_target_output.mean() + + +output_LODOG_4 /= target_diff(output_LODOG_4, mask) +output_4_05 /= target_diff(output_4_05, mask) +output_2_05 /= target_diff(output_2_05, mask) +output_4_3 /= target_diff(output_4_3, mask) + +# %% [markdown] +# This, then, gives us the outputs scaled into the same range, +# which replicates Figure 5 from Robinson, Hammon, & de Sa (2007). + +# %% +plt.figure(figsize=(6, 10)) + +ax = plt.subplot(3, 1, 1) +ax.imshow(stimulus[275:750, :], cmap="gray") +ax.axhline(y=235, color="black", dashes=(1, 1)) + + +plt.subplot(6, 1, 3) +plt.plot(output_LODOG_4[512, :], color="blue", linestyle="solid", label="LODOG n=4") +plt.grid(axis="y") +plt.yticks(ticks=range(-4, 6, 2)) +plt.ylim(-4, 4) +plt.xlim(0, output_LODOG_4.shape[0]) +plt.legend() + + +plt.subplot(6, 1, 4) +plt.plot( + output_4_05[512, :], + color="blue", + linestyle="solid", + label="FLODOG $\sigma=4s, m=0.5$", +) +plt.grid(axis="y") +plt.yticks(ticks=range(-4, 6, 2)) +plt.ylim(-4, 4) +plt.xlim(0, output_4_05.shape[0]) +plt.legend() + + +plt.subplot(6, 1, 5) +plt.plot( + output_2_05[512, :], + color="blue", + linestyle="solid", + label="FLODOG $\sigma=2s, m=0.5$", +) +plt.grid(axis="y") +plt.yticks(ticks=range(-4, 6, 2)) +plt.ylim(-4, 4) +plt.xlim(0, output_2_05.shape[0]) +plt.legend() + + +plt.subplot(6, 1, 6) +plt.plot( + output_4_3[512, :], + color="blue", + linestyle="solid", + label="FLODOG $\sigma=4s, m=3.0$", +) +plt.grid(axis="y") +plt.yticks(ticks=range(-4, 6, 2)) +plt.ylim(-4, 4) +plt.xlim(0, output_4_3.shape[0]) +plt.legend() + + +plt.show() diff --git a/demo/replication_flodog_params.py b/demo/replication_flodog_params.py deleted file mode 100644 index 58eac43..0000000 --- a/demo/replication_flodog_params.py +++ /dev/null @@ -1,46 +0,0 @@ -# %% -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -# Import local module -import multyscale - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) -stimulus = (stimulus - stimulus.min()) / (stimulus.max() - stimulus.min()) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create model -model = multyscale.models.FLODOG_RHS2007(shape, visextent) - -# %% Weighted filter outputs -filter_outputs = model.bank.apply(stimulus) -weighted_outputs = model.weight_outputs(filter_outputs) - -# %% Parameterized normalization -model.spatial_window_scalar = 4 -model.sdmix = 0.5 -model.scale_norm_weights = multyscale.normalization.scale_norm_weights_gaussian( - len(model.scale_weights), model.sdmix -) -model.normalization_weights = multyscale.normalization.create_normalization_weights( - 6, 7, model.scale_norm_weights, model.orientation_norm_weights -) -model.window_sigmas = np.broadcast_to(np.array(model.center_sigmas)[None, ..., None], (6, 7, 2)) - -# %% -output_4_05 = model.normalize_outputs(weighted_outputs).sum((0, 1)) - -# %% -plt.subplot(2, 1, 1) -plt.imshow(output_4_05, extent=visextent) -plt.subplot(2, 1, 2) -plt.plot(output_4_05[512, :]) - -# %% From ead7be33c31d59f8264ebc2473aaccceb62a2e0d Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 22 Dec 2023 13:17:56 +0100 Subject: [PATCH 43/79] docs(demos): filters --- demo/demo_filters.py | 68 ------------- demo/filters.ipynb | 1 - demo/filters.py | 236 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+), 69 deletions(-) delete mode 100644 demo/demo_filters.py delete mode 100644 demo/filters.ipynb create mode 100644 demo/filters.py diff --git a/demo/demo_filters.py b/demo/demo_filters.py deleted file mode 100644 index ac9fab7..0000000 --- a/demo/demo_filters.py +++ /dev/null @@ -1,68 +0,0 @@ -# %% -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -# Import local module -from multyscale import filters - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create image coordinate system: -axish = np.linspace(visextent[0], visextent[1], shape[0]) -axisv = np.linspace(visextent[2], visextent[3], shape[1]) - -(x, y) = np.meshgrid(axish, axisv) - -# %% Circular gaussian -img = filters.gaussian2d(x, y, (2, 2)) - -# Plot -plt.subplot(1, 2, 1) -plt.imshow(img, extent=visextent) - -# Plot horizontal meridian -plt.subplot(1, 2, 2) -plt.plot(x[int(img.shape[0] / 2)], img[int(img.shape[0] / 2), ...]) - -# %% Elliptical gaussian (3:1 axes; rotated) -img = filters.gaussian2d(x, y, (6, 2), orientation=15) - -# Plot -plt.subplot(1, 2, 1) -plt.imshow(img, extent=visextent) - -# Plot horizontal meridian -plt.subplot(1, 2, 2) -plt.plot(x[int(img.shape[0] / 2)], img[int(img.shape[0] / 2), ...]) - -# %% ODOG -sigmas = ((2, 2), (2, 4)) # surround Gaussian is 2:1 in one axis - -img = filters.odog(x, y, sigmas, orientation=(80, 80)) - -# % Apply filter -filt_img = filters.apply(stimulus, img) - -# Plot stimulus -plt.subplot(1, 3, 1) -plt.imshow(stimulus, cmap="gray", extent=visextent) - -# Plot filter + horizontal meridian -plt.subplot(2, 3, 2) -plt.imshow(img, extent=visextent) -plt.subplot(2, 3, 5) -plt.plot(x[int(img.shape[0] / 2)], img[int(img.shape[0] / 2), ...]) - -# Plot filtered image -plt.subplot(1, 3, 3) -plt.imshow(filt_img, extent=visextent) - -# %% diff --git a/demo/filters.ipynb b/demo/filters.ipynb deleted file mode 100644 index 6984850..0000000 --- a/demo/filters.ipynb +++ /dev/null @@ -1 +0,0 @@ -{"cells":[{"source":["# Creating and applying image filters"],"cell_type":"markdown","metadata":{}},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["# Third party libraries\n","import numpy as np\n","import matplotlib.pyplot as plt\n","from PIL import Image\n","\n","# Import local module\n","from multyscale import filters"]},{"source":["## Example stimulus\n","The example stimulus that this tutorial uses is a version of White's (1979) classic illusion, as also used by Robinson, Hammon, & de Sa (2007).\n","This stimulus is stored as an `.png` file, so it is first loaded in using `PIL` and converted to grayscale,\n","and then cast as a numpy NDArray.\n","The image 1024x1024 pixels represent 32x32 degrees of the visual field,\n","so, if centered, the visual extent of this stimulus subtends\n","from -16 degrees on the left, to 16 degrees on the right,\n","and from -16 degrees on top, to 16 degrees on the bottom."],"cell_type":"markdown","metadata":{}},{"cell_type":"code","execution_count":2,"metadata":{},"outputs":[{"output_type":"execute_result","data":{"text/plain":[""]},"metadata":{},"execution_count":2},{"output_type":"display_data","data":{"text/plain":"
","image/svg+xml":"\n\n\n \n \n \n \n 2021-05-23T17:23:22.922883\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","image/png":"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\n"},"metadata":{"needs_background":"light"}}],"source":["stimulus = np.asarray(Image.open(\"example_stimulus.png\").convert(\"L\"))\n","shape = stimulus.shape # filtershape, in pixels\n","# visual extent, in degrees visual angle,\n","# same convention as pyplot (left, right, top, bottom):\n","visextent = (-16, 16, -16, 16)\n","\n","plt.imshow(stimulus, cmap='gray', extent=visextent)"]},{"source":["## Image space\n","To construct any filters, first the coordinates of the space must be defined.\n","Here, the stimulus is said to subtend 32x32 degrees of visual extent, and has a resolution of 1024x1024 pixels.\n","The horizontal (`axish`) and vertical (`axisv`) axes of the space, then, sample the range set by the limits of the visual extent, in 1024 steps.\n","The numpy.meshgrid function then gives arrays with the `x` and `y` coordinate, in degrees of visual angle, of each of the 1024x1024 pixels in the space.\n","\n","Defining the coordinates in degrees of visual angle allows for defining all other space quantities also in degrees.\n","If, instead, the coordinates are in pixels, all other quantities (e.g., standard deviations of filters), must also be given in pixels."],"cell_type":"markdown","metadata":{}},{"cell_type":"code","execution_count":3,"metadata":{},"outputs":[],"source":["axish = np.linspace(visextent[0], visextent[1], shape[0])\n","axisv = np.linspace(visextent[2], visextent[3], shape[1])\n","\n","(x, y) = np.meshgrid(axish, axisv)"]},{"source":["## Filter types\n","The [multyscale.filters](../reference/multyscale.filters.rst) module implements several filter types:\n","\n","- `multyscale.filters.gaussian2d`: a two-dimensional Gaussian filter, which has a standard deviation along a major and minor axis, and optionally a rotation\n","- `multyscale.filters.dog`: a isotropic difference-of-Gaussian filter, composed of a center 2D Gaussian and a surround 2D Gaussian, that are each symmetrical (i.e., standard deviation along major and minor axes are identical)\n","- `multyscale.filters.odog`: an oriented difference-of-Gaussian filter, composed of a center 2D Gaussian and a surround 2D Gaussian, which do not have to be isotropic (i.e., can have different standard deviations along their major and minor axes)."],"cell_type":"markdown","metadata":{}},{"source":["### Two-dimensional Gaussian\n","The two-dimensional Gaussian is evaluate over the whole input space, i.e., for each coordinate in `x` and `y`.\n","The shape of a Gaussian is generally defined by two parameters: its central tendency, and its spread.\n","For a 2D Gaussian in space, this corresponds to the `center` location,\n","and the standard deviation `sigma`.\n","The present implementation of the Gaussian defaults to `center`ing on `(0,0)`."],"cell_type":"markdown","metadata":{}},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[{"output_type":"execute_result","data":{"text/plain":["[]"]},"metadata":{},"execution_count":4},{"output_type":"display_data","data":{"text/plain":"
","image/svg+xml":"\n\n\n \n \n \n \n 2021-05-23T17:23:23.442675\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","image/png":"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\n"},"metadata":{"needs_background":"light"}}],"source":["img = filters.gaussian2d(x, y, sigma=2)\n","img2 = filters.gaussian2d(x,y, sigma=1, center=(-4,6))\n","\n","# Plot\n","plt.subplot(2, 2, 1)\n","plt.imshow(img, extent=visextent, cmap=\"coolwarm\")\n","plt.subplot(2, 2, 2)\n","plt.imshow(img2, extent=visextent, cmap=\"coolwarm\")\n","\n","# Plot horizontal meridians\n","plt.subplot(2, 2, 3)\n","plt.plot(x[int(img.shape[0] / 2)], img[int(img.shape[0] / 2), ...])\n","plt.subplot(2, 2, 4)\n","plt.plot(x[int(img2.shape[0] / 2)], img2[int(img2.shape[0] / 2), ...])"]},{"source":["### Difference-of-Gaussian\n","A common type of filter of image processing, is the difference-of-Gaussian (DoG) filter.\n","As the name implies, it consists of two, 2D Gaussian filters:\n","a smaller \"center\" Gaussian, and a larger \"surround\" Gaussian,\n","and the filter subtracts the surround Gaussian from the center Gaussian.\n","\n","For an isotropic DoG filter only one standard deviation is required for each of the constituent Gaussians.\n","These filters are radially symmetric, and therefore considered _unoriented_ DoG filters."],"cell_type":"markdown","metadata":{}},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[{"output_type":"execute_result","data":{"text/plain":["[]"]},"metadata":{},"execution_count":5},{"output_type":"display_data","data":{"text/plain":"
","image/svg+xml":"\n\n\n \n \n \n \n 2021-05-23T17:23:24.023313\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","image/png":"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\n"},"metadata":{"needs_background":"light"}}],"source":["img = filters.dog(x, y, sigma=(2,4)) # surround Gaussian is 2:1 of center gaussian\n","\n","# Plot\n","plt.subplot(1, 2, 1)\n","plt.imshow(img, extent=visextent, cmap=\"coolwarm\")\n","\n","# Plot horizontal meridian\n","plt.subplot(1, 2, 2)\n","plt.plot(x[int(img.shape[0] / 2)], img[int(img.shape[0] / 2), ...])"]},{"source":["### Oriented 2D Gaussian\n","The previous filters are isotropic: they are radially symmetric and thus _unoriented_.\n","This is because the 2D Gaussian(s) have the same standard deviation along both axes.\n","However, this is not required.\n","_Oriented_ 2D Gaussians can be made by differing the standard deviation along the two axes.\n","Optionally, the major and minor axes of the Gaussian can be rotated away from the horizontal and vertical axes."],"cell_type":"markdown","metadata":{}},{"cell_type":"code","execution_count":6,"metadata":{},"outputs":[{"output_type":"execute_result","data":{"text/plain":["[]"]},"metadata":{},"execution_count":6},{"output_type":"display_data","data":{"text/plain":"
","image/svg+xml":"\n\n\n \n \n \n \n 2021-05-23T17:23:24.625369\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","image/png":"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\n"},"metadata":{"needs_background":"light"}}],"source":["img = filters.gaussian2d(x, y, sigma=(6, 2))\n","img2 = filters.gaussian2d(x, y, sigma=(6, 2), orientation=65) # rotation is counterclockwise\n","\n","# Plot\n","plt.subplot(2, 2, 1)\n","plt.imshow(img, extent=visextent, cmap=\"coolwarm\")\n","plt.subplot(2, 2, 2)\n","plt.imshow(img2, extent=visextent, cmap=\"coolwarm\")\n","\n","# Plot horizontal meridian\n","plt.subplot(2, 2, 3)\n","plt.plot(x[int(img.shape[0] / 2)], img[int(img.shape[0] / 2), ...])\n","plt.subplot(2, 2, 4)\n","plt.plot(x[int(img2.shape[0] / 2)], img2[int(img2.shape[0] / 2), ...])"]},{"source":["### Oriented Difference-of-Gaussian\n","A difference-of-Gaussian filter also does not have to be isotropic.\n","If at least one of the constituent Gaussians (center or surround) is oriented,\n","so will the combined filter be.\n","This filter now is an _oriented_ difference-of-Gaussians (ODoG) filter."],"cell_type":"markdown","metadata":{}},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[{"output_type":"execute_result","data":{"text/plain":["[]"]},"metadata":{},"execution_count":7},{"output_type":"display_data","data":{"text/plain":"
","image/svg+xml":"\n\n\n \n \n \n \n 2021-05-23T17:23:25.228805\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","image/png":"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\n"},"metadata":{"needs_background":"light"}}],"source":["sigma = ((2, 2), (2, 4)) # surround Gaussian is 2:1 in one axis\n","img = filters.odog(x, y, sigma, orientation=80)\n","\n","# Plot filter + horizontal meridian\n","plt.subplot(1, 2, 1)\n","plt.imshow(img, extent=visextent, cmap=\"coolwarm\")\n","plt.subplot(1, 2, 2)\n","plt.plot(x[int(img.shape[0] / 2)], img[int(img.shape[0] / 2), ...])"]},{"source":["## Applying filters"],"cell_type":"markdown","metadata":{}},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[{"output_type":"execute_result","data":{"text/plain":[""]},"metadata":{},"execution_count":8},{"output_type":"display_data","data":{"text/plain":"
","image/svg+xml":"\n\n\n \n \n \n \n 2021-05-23T17:23:25.978060\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","image/png":"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\n"},"metadata":{"needs_background":"light"}}],"source":["img = filters.odog(x, y, sigma=((2, 2), (2, 4)), orientation=80)\n","\n","# % Apply filter\n","filt_img = filters.apply(stimulus, img)\n","\n","# Plot stimulus\n","plt.subplot(1, 3, 1)\n","plt.imshow(stimulus, cmap=\"gray\", extent=visextent)\n","\n","# Plot filter + horizontal meridian\n","plt.subplot(2, 3, 2)\n","plt.imshow(img, extent=visextent, cmap=\"coolwarm\")\n","plt.subplot(2, 3, 5)\n","plt.plot(x[int(img.shape[0] / 2)], img[int(img.shape[0] / 2), ...])\n","\n","# Plot filtered image\n","plt.subplot(1, 3, 3)\n","plt.imshow(filt_img, cmap=\"coolwarm\", extent=visextent)"]}],"nbformat":4,"nbformat_minor":2,"metadata":{"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.8.5"},"orig_nbformat":2,"kernelspec":{"name":"python385jvsc74a57bd02092d3496cec6223f7568d467ac3bd437caae5364fbbef5eee989d574fa6e351","display_name":"Python 3.8.5 64-bit ('lightness_models': pyenv)"}}} \ No newline at end of file diff --git a/demo/filters.py b/demo/filters.py new file mode 100644 index 0000000..c681fb3 --- /dev/null +++ b/demo/filters.py @@ -0,0 +1,236 @@ +# %% [markdown] +# # Creating and applying image filters + +# %% Setup +# Third party libraries +import matplotlib.pyplot as plt +import numpy as np + +# Import local module +from multyscale import filters + +# %% [markdown] +# ## Example stimulus +# The example stimulus used for this exploration +# is a version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_thick`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. + +# %% Load example stimulus +stimulus = np.load("example_stimulus.npy") + +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(1, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) + +plt.show() + +# %% [markdown] +# In the stimulus image on the left, the left gray patch appears brighter than the right gray patch. +# On the right, the pixel intensity/gray scale values along the horizontal cut +# (indicated by the dashed line in the image) are shown. +# These reveal that, in fact, the two gray patches are identical in their physical intensity. + +# %% [markdown] +# ## Image space +# To construct any filters, first the coordinates of the space must be defined. +# Here, the stimulus is said to subtend $32 \times 32$ degrees of visual angle, +# and has a resolution of $1024 \times 1024$ pixels. +# The horizontal (`axish`) and vertical (`axisv`) axes of the space +# sample the range set by the limits of the visual extent, in $1024$ steps. +# The `numpy.meshgrid()` function produces arrays with the `x` and `y` coordinate, +# in degrees of visual angle, of each of the $1024 \times 1024$ pixels in the image(space). +# +# Defining the coordinates in degrees of visual angle +# allows for defining all other space quantities also in degrees. +# If, instead, the coordinates are in pixels, +# all other quantities (e.g., size of filters) +# must also be given in pixels. + +# %% +axish = np.linspace(visextent[0], visextent[1], stimulus.shape[0]) +axisv = np.linspace(visextent[2], visextent[3], stimulus.shape[1]) + +(x, y) = np.meshgrid(axish, axisv) + +# %% [markdown] +# ## Filter types +# The `multyscale.filters` module implements several filters: +# +# - `multyscale.filters.gaussian2d`: a two-dimensional Gaussian filter, +# which is defined by a standard deviation along a major and minor axis, +# and optionally a rotation +# - `multyscale.filters.dog`: a isotropic difference-of-Gaussian filter, +# composed of a center 2D Gaussian and a surround 2D Gaussian, +# which are each symmetrical +# (i.e., standard deviation along major and minor axes are identical) +# - `multyscale.filters.odog`: an oriented difference-of-Gaussian filter, +# composed of a center 2D Gaussian and a surround 2D Gaussian, +# which do not have to be isotropic +# (i.e., can have different standard deviations along their major and minor axes). + +# %% [markdown] +# ### Two-dimensional Gaussian +# The two-dimensional Gaussian is evaluate over the whole input space, +# i.e., for each coordinate in `x` and `y`. +# The shape of a Gaussian is generally defined by two parameters: +# its central tendency, and its spread. +# For a 2D Gaussian in space, this corresponds to the `center` location, +# and the standard deviation `sigma`. +# The present implementation of the Gaussian defaults to `center`ing on `(0,0)`. + +# %% 2D Gaussians, both circular and elliptic +gaussian_circular = filters.gaussian2d(x, y, sigma=2) +gaussian_elliptic = filters.gaussian2d(x, y, sigma=1, center=(-4, 6)) + +# Plot +plt.subplot(2, 2, 1) +plt.imshow(gaussian_circular, extent=visextent, cmap="coolwarm") +plt.subplot(2, 2, 2) +plt.imshow(gaussian_elliptic, extent=visextent, cmap="coolwarm") + +# Plot horizontal meridians +plt.subplot(2, 2, 3) +plt.plot( + x[int(gaussian_circular.shape[0] / 2)], + gaussian_circular[int(gaussian_circular.shape[0] / 2), ...], +) +plt.subplot(2, 2, 4) +plt.plot( + x[int(gaussian_elliptic.shape[0] / 2)], + gaussian_elliptic[int(gaussian_elliptic.shape[0] / 2), ...], +) + +plt.show() + +# %% [markdown] +# ### Oriented 2D Gaussian +# The previous filters are isotropic: they are radially symmetric and thus _unoriented_. +# This is because the 2D Gaussian(s) have the same standard deviation along both axes. +# However, this is not required. +# _Oriented_ 2D Gaussians can be made by differing the standard deviation along the two axes. +# Optionally, the major and minor axes of the Gaussian +# can be rotated away from the horizontal and vertical axes. + +# %% Oriented Gaussians filters +gaussian_circular = filters.gaussian2d(x, y, sigma=(6, 2)) +gaussian_elliptic = filters.gaussian2d( + x, y, sigma=(6, 2), orientation=65 +) # rotation is counterclockwise + +# % Visualize +fig, axs = plt.subplots(2, 2, sharex="all", sharey="row") + +# Plot images +axs[0, 0].imshow(gaussian_circular, extent=visextent, cmap="coolwarm") +axs[0, 1].imshow(gaussian_elliptic, extent=visextent, cmap="coolwarm") + +# Plot horizontal meridians +axs[1, 0].plot( + x[int(gaussian_circular.shape[0] / 2)], + gaussian_circular[int(gaussian_circular.shape[0] / 2), ...], +) +axs[1, 1].plot( + x[int(gaussian_elliptic.shape[0] / 2)], + gaussian_elliptic[int(gaussian_elliptic.shape[0] / 2), ...], +) + +plt.show() + +# %% [markdown] +# ### Difference-of-Gaussian +# A common type of filter of image processing, is the difference-of-Gaussian (DoG) filter. +# As the name implies, it consists of two, 2D Gaussian filters: +# a smaller "center" Gaussian, and a larger "surround" Gaussian, +# and the filter subtracts the surround Gaussian from the center Gaussian. +# +# For an isotropic DoG filter only one standard deviation is required +# for each of the constituent Gaussians. +# These filters are radially symmetric +# and therefore considered _unoriented_ DoG filters. + +# %% Difference-of-Gaussian +filt_dog = filters.dog(x, y, sigma=(2, 4)) # surround Gaussian is 2:1 of center gaussian + +# Plot +plt.subplot(1, 2, 1) +plt.imshow(filt_dog, extent=visextent, cmap="coolwarm") + +# Plot horizontal meridian +plt.subplot(1, 2, 2) +plt.plot(x[int(filt_dog.shape[0] / 2)], filt_dog[int(filt_dog.shape[0] / 2), ...]) + +plt.show() + +# %% [markdown] +# ### Oriented Difference-of-Gaussian +# A difference-of-Gaussian filter also does not have to be isotropic. +# If at least one of the constituent Gaussians (center or surround) is oriented, +# so will the combined filter be. +# This filter now is an _oriented_ difference-of-Gaussians (ODoG) filter. + +# %% ODOG filter +sigma = ((2, 2), (2, 4)) # surround Gaussian is 2:1 in one axis +filt_odog = filters.odog(x, y, sigma, orientation=80) + +# Plot filter and horizontal meridian +plt.subplot(1, 2, 1) +plt.imshow(filt_odog, extent=visextent, cmap="coolwarm") +plt.subplot(1, 2, 2) +plt.plot(x[int(filt_odog.shape[0] / 2)], filt_odog[int(filt_odog.shape[0] / 2), ...]) + +plt.show() + + +# %% [markdown] +# ## Applying filters +# The `multyscale.filters.apply()` function is used to apply +# a filter (as an `numpy.NDArray`) to an image (also an `numpy.NDArray`). +# +# If the filter and image are *not* the same `shape` (in pixels), +# the smaller will be _padded_ to match the larger one. +# The `padval` argument specifies what value will be used, by default `0.5`. + +# %% ODOG filter +filt_odog = filters.odog(x, y, sigma=((1, 1), (1, 2)), orientation=90) + +# % Apply filter +filtered_img = filters.apply(stimulus, filt_odog) + +# Plot stimulus +plt.subplot(1, 3, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) + +# Plot filter + horizontal meridian +plt.subplot(2, 3, 2) +plt.imshow(filt_odog, extent=visextent, cmap="coolwarm") +plt.subplot(2, 3, 5) +plt.plot(x[int(filt_odog.shape[0] / 2)], filt_odog[int(filt_odog.shape[0] / 2), ...]) + +# Plot filtered image +plt.subplot(1, 3, 3) +plt.imshow(filtered_img, cmap="coolwarm", extent=visextent) + +plt.show() From 84a4b367449a4b744bcb5fbe061631bc000100d9 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 12 Dec 2023 18:09:56 +0100 Subject: [PATCH 44/79] docs(demos): filterbanks --- demo/demo_dogbank.py | 47 ---------- demo/demo_filterbank.py | 60 ------------- demo/filterbanks.py | 186 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 107 deletions(-) delete mode 100644 demo/demo_dogbank.py delete mode 100644 demo/demo_filterbank.py create mode 100644 demo/filterbanks.py diff --git a/demo/demo_dogbank.py b/demo/demo_dogbank.py deleted file mode 100644 index f8c2283..0000000 --- a/demo/demo_dogbank.py +++ /dev/null @@ -1,47 +0,0 @@ -# %% -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -# Import local module -from multyscale import filterbanks, utils - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create image coordinate system: -axish = np.linspace(visextent[0], visextent[1], shape[0]) -axisv = np.linspace(visextent[2], visextent[3], shape[1]) - -(x, y) = np.meshgrid(axish, axisv) - -# %% Filterbank parameters -num_scales = 7 -largest_center_sigma = 3 # in degrees -center_sigmas = utils.octave_intervals(num_scales) * largest_center_sigma -cs_ratio = 2 # center-surround ratio - -# Convert to filterbank parameters -sigmas = [(s, cs_ratio * s) for s in center_sigmas] - -# %% Create filterbank -bank = filterbanks.DOGBank(sigmas, x, y) - -# %% Visualise filterbank -for i in range(bank.filters.shape[0]): - plt.subplot(1, bank.filters.shape[0], i + 1) - plt.imshow(bank.filters[i, ...], extent=visextent) - -# %% Apply filterbank -filters_output = bank.apply(stimulus) - -# %% Visualise filter bank output -for i in range(filters_output.shape[0]): - plt.subplot(1, filters_output.shape[0], i + 1) - plt.imshow(filters_output[i, ...], extent=visextent) diff --git a/demo/demo_filterbank.py b/demo/demo_filterbank.py deleted file mode 100644 index 09da30c..0000000 --- a/demo/demo_filterbank.py +++ /dev/null @@ -1,60 +0,0 @@ -# %% -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -# Import local module -from multyscale import filterbanks, utils - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create image coordinate system: -axish = np.linspace(visextent[0], visextent[1], shape[0]) -axisv = np.linspace(visextent[2], visextent[3], shape[1]) - -(x, y) = np.meshgrid(axish, axisv) - -# %% Filterbank parameters -# Parameters (BM1999) -n_orientations = 6 -num_scales = 7 -largest_center_sigma = 3 # in degrees -center_sigmas = utils.octave_intervals(num_scales) * largest_center_sigma -cs_ratio = 2 # center-surround ratio - -# Convert to filterbank parameters -orientations = np.arange(0, 180, 180 / n_orientations) -sigmas = [((s, s), (s, cs_ratio * s)) for s in center_sigmas] - -# %% Create filterbank -bank = filterbanks.ODOGBank(orientations, sigmas, x, y) - -# %% Visualise filterbank -for i in range(bank.filters.shape[0]): - for j in range(bank.filters.shape[1]): - plt.subplot( - bank.filters.shape[0], - bank.filters.shape[1], - i * bank.filters.shape[0] + ((j + i) * 1) + 1, - ) - plt.imshow(bank.filters[i, j, ...], extent=visextent) - -# %% Apply filterbank -filters_output = bank.apply(stimulus) - -# %% Visualise filter bank output -for i in range(filters_output.shape[0]): - for j in range(filters_output.shape[1]): - plt.subplot( - filters_output.shape[0], - filters_output.shape[1], - i * filters_output.shape[0] + ((j + i) * 1) + 1, - ) - plt.imshow(filters_output[i, j, ...], extent=visextent) diff --git a/demo/filterbanks.py b/demo/filterbanks.py new file mode 100644 index 0000000..abc3469 --- /dev/null +++ b/demo/filterbanks.py @@ -0,0 +1,186 @@ +# %% [markdown] +# # Bank of filters + +# %% Setup +# Third party libraries +import matplotlib.pyplot as plt +import numpy as np + +# Import local module +import multyscale + +# %% [markdown] +# ## Example stimulus +# The example stimulus used for this exploration +# is a version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_thick`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. + +# %% Load example stimulus +stimulus = np.load("example_stimulus.npy") + +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(1, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) + +plt.show() + +# %% Image coordinate system: +axish = np.linspace(visextent[0], visextent[1], stimulus.shape[0]) +axisv = np.linspace(visextent[2], visextent[3], stimulus.shape[1]) + +(x, y) = np.meshgrid(axish, axisv) + +# %% [markdown] +# In the stimulus image on the left, the left gray patch appears brighter than the right gray patch. +# On the right, the pixel intensity/gray scale values along the horizontal cut +# (indicated by the dashed line in the image) are shown. +# These reveal that, in fact, the two gray patches are identical in their physical intensity. + +# %% [markdown] +# ## Difference-of-Gaussian bank +# The -DOG family of models starts with a _multiscale spatial filtering_ frontend. +# This consists of a set of filters, $\mathbf{F}$ +# which span a range of spatial scales $S$ + +# %% Spacing of filters +# Scales (spatial frequency sensitivities) +num_scales = 7 +largest_center_sigma = 3 # in degrees +center_sigmas = multyscale.utils.octave_intervals(num_scales) * largest_center_sigma +cs_ratio = 2 # center-surround ratio +sigmas = [((s, cs_ratio * s)) for s in center_sigmas] + +# %% Create filterbank +bank_DOG = multyscale.filterbanks.DOGBank(sigmas, x, y) + +# Visualise filterbank +fig, axs = plt.subplots(1, *bank_DOG.shape[:-2], sharex="all", sharey="all") +for s in np.ndindex(*bank_DOG.shape[:-2]): + axs[s].imshow(bank_DOG.filters[s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s$") +plt.show() + +# %% [markdown] +# In this visualisation of all filters, +# the columns differ in the spatial scale of the filter. + +# %% [markdown] +# These filters are then convolved with the stimulus image. +# +# Filterbank-objects have an `apply(...)` method, +# which filters the input stimulus with the whole bank. +# The output is an $O \times S \times Y \times X$ tensor +# of channel responses. + +# %% Apply filterbank to (example) stimulus +filters_output = bank_DOG.apply(stimulus) + +# Visualise each filter output +fig, axs = plt.subplots(1, *filters_output.shape[:-2], sharex="all", sharey="all") +for s in np.ndindex(filters_output.shape[:-2]): + axs[s].imshow( + filters_output[s], + cmap="coolwarm", + extent=visextent, + vmin=filters_output.min(), + vmax=filters_output.max(), + ) +fig.supxlabel("Spatial scale/freq. $s$") +plt.show() + + +# %% [markdown] +# ## ODoG bank +# The _oriented_ -ODOG subfamily of models uses filters +# that also have one of several orientations $O$. +# Thus, filter $f_{o,s}$ is a single filter in the set $\mathbf{F}$ +# with orientation $o$ and scale $s$. +# Since each filter is 2D, it also has an implied $x,y$ pixels. +# +# As a result, we can also think of the 2D ($O\times S$) set $\mathbf{F}$ of filter(outputs), +# where each filter(output) $f_{o,s}$ is an image, +# as a 4D ($O \times S \times X \times Y$) set $\mathbf{I}$ of pixel intensities. +# $$ \mathbf{I}_{O \times S \times X \times Y} \equiv \mathbf{F}_{O \times S} $$ +# +# For the current topic, we use the default ODOG filterbank, +# which can be created by `multyscale.filterbanks.RHS2007()`, +# with 6 orientations, and 7 spatial scales. + +# %% Number of filters +n_orientations = 6 +num_scales = 7 + +# %% Spacing of filters + +# Orientations +orientations = tuple(np.arange(0, 180, 180 / n_orientations)) + +# Scales (spatial frequency sensitivities) +largest_center_sigma = 3 # in degrees +center_sigmas = multyscale.utils.octave_intervals(num_scales) * largest_center_sigma +cs_ratio = 2 # center-surround ratio +sigmas = [((s, s), (s, cs_ratio * s)) for s in center_sigmas] + +# %% Create filterbank +bank_ODOG = multyscale.filterbanks.ODOGBank(orientations, sigmas, x, y) + +# Visualise filterbank +fig, axs = plt.subplots(*bank_ODOG.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(*bank_ODOG.shape[:2]): + axs[o, s].imshow(bank_ODOG.filters[o, s, ...], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() + +# %% [markdown] +# In this visualisation of all filters, +# the rows differ in orientation of the filter +# and columns differ in the spatial scale of the filter. + +# %% [markdown] +# These filters are then convolved with the stimulus image. +# +# Filterbank-objects have an `apply(...)` method, +# which filters the input stimulus with the whole bank. +# The output is an $O \times S \times Y \times X$ tensor +# of channel responses. + +# %% Apply filterbank to (example) stimulus +filters_output = bank_ODOG.apply(stimulus) + +# Visualise each filter output +fig, axs = plt.subplots(*filters_output.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(filters_output.shape[:2]): + axs[o, s].imshow( + filters_output[o, s], + cmap="coolwarm", + extent=visextent, + vmin=filters_output.min(), + vmax=filters_output.max(), + ) +fig.supxlabel("Spatial scale/freq. $s$") +fig.supylabel("Orientation $o$") +plt.show() From 526244292c33d63cd68af44b1275275e77003536 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Mon, 25 Dec 2023 17:22:51 +0100 Subject: [PATCH 45/79] docs: integrate demos into book --- demo/demo_fodog.py | 65 ------------------ docs/_config.yml | 5 ++ docs/_toc.yml | 32 ++++++--- {demo => docs/filtering}/example_stimulus.npy | Bin {demo => docs/filtering}/example_stimulus.png | Bin .../filtering}/example_stimulus_mask.npy | Bin {demo => docs/filtering}/filterbanks.py | 0 {demo => docs/filtering}/filters.py | 0 docs/getting_started/getting_started.md | 14 ---- docs/howtos/howtos.md | 8 --- docs/index.md | 13 ++-- {demo => docs/normalization}/WE_dual.npy | Bin {demo => docs/normalization}/WE_dual_mask.npy | Bin docs/normalization/example_stimulus.npy | Bin 0 -> 8388736 bytes .../example_stimulus.png | Bin docs/normalization/example_stimulus_mask.npy | Bin 0 -> 8388736 bytes .../explore_FLODOG_parameters.py | 0 .../normalization}/explore_LODOG_parameter.py | 0 .../normalization}/normalization_FLODOG.py | 0 .../normalization}/normalization_LODOG.py | 0 .../normalization}/normalization_ODOG.py | 0 .../topic_guides.md | 8 ++- docs/quickstart/example_stimulus.npy | Bin 0 -> 8388736 bytes docs/quickstart/example_stimulus.png | Bin 0 -> 6627 bytes docs/quickstart/example_stimulus_mask.npy | Bin 0 -> 8388736 bytes .../installation.md | 2 +- docs/quickstart/quickstart.md | 14 ++++ .../quickstart/run_(F)(L)ODOG.py | 2 +- docs/topic_guides/overview.md | 1 + 29 files changed, 60 insertions(+), 104 deletions(-) delete mode 100644 demo/demo_fodog.py rename {demo => docs/filtering}/example_stimulus.npy (100%) rename {demo => docs/filtering}/example_stimulus.png (100%) rename {demo => docs/filtering}/example_stimulus_mask.npy (100%) rename {demo => docs/filtering}/filterbanks.py (100%) rename {demo => docs/filtering}/filters.py (100%) delete mode 100644 docs/getting_started/getting_started.md delete mode 100644 docs/howtos/howtos.md rename {demo => docs/normalization}/WE_dual.npy (100%) rename {demo => docs/normalization}/WE_dual_mask.npy (100%) create mode 100644 docs/normalization/example_stimulus.npy rename docs/{getting_started => normalization}/example_stimulus.png (100%) create mode 100644 docs/normalization/example_stimulus_mask.npy rename {demo => docs/normalization}/explore_FLODOG_parameters.py (100%) rename {demo => docs/normalization}/explore_LODOG_parameter.py (100%) rename {demo => docs/normalization}/normalization_FLODOG.py (100%) rename {demo => docs/normalization}/normalization_LODOG.py (100%) rename {demo => docs/normalization}/normalization_ODOG.py (100%) rename docs/{topic_guides => normalization}/topic_guides.md (83%) create mode 100644 docs/quickstart/example_stimulus.npy create mode 100644 docs/quickstart/example_stimulus.png create mode 100644 docs/quickstart/example_stimulus_mask.npy rename docs/{getting_started => quickstart}/installation.md (99%) create mode 100644 docs/quickstart/quickstart.md rename demo/howto_run_(F)(L)ODOG.py => docs/quickstart/run_(F)(L)ODOG.py (99%) create mode 100644 docs/topic_guides/overview.md diff --git a/demo/demo_fodog.py b/demo/demo_fodog.py deleted file mode 100644 index 44a27b6..0000000 --- a/demo/demo_fodog.py +++ /dev/null @@ -1,65 +0,0 @@ -# Third party libraries -import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -import multyscale - -# Import local module -from multyscale.models import FLODOG_RHS2007, ODOG_RHS2007 - - -# %% -class FODOG(ODOG_RHS2007): - def __init__(self, shape, visextent): - super().__init__(shape, visextent) - - self.sdmix = 0.5 # stdev of Gaussian weights for scale mixing - self.scale_norm_weights = multyscale.normalization.scale_norm_weights_gaussian( - len(self.scale_weights), self.sdmix - ) - self.normalization_weights = multyscale.normalization.create_normalization_weights( - 6, 7, self.scale_norm_weights, self.orientation_norm_weights - ) - - pass - - -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -shape = stimulus.shape # filtershape in pixels -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) - -# %% Create models -model_ODOG = ODOG_RHS2007(shape, visextent) -model_FODOG = FODOG(shape, visextent) -model_FLODOG = FLODOG_RHS2007(shape, visextent) - -# %% Run -output_ODOG = model_ODOG.apply(stimulus) -output_FODOG = model_FODOG.apply(stimulus) -output_FLODOG = model_FLODOG.apply(stimulus) - -# %% -output_ODOG / output_ODOG.sum() -output_FODOG / output_FODOG.sum() -output_FLODOG / output_FLODOG.sum() - -# %% Visualise all outputs -plt.subplot(3, 2, 1) -plt.imshow(output_ODOG, extent=visextent) -plt.subplot(3, 2, 2) -plt.plot(output_ODOG[512, 250:750]) - -plt.subplot(3, 2, 3) -plt.imshow(output_FLODOG, extent=visextent) -plt.subplot(3, 2, 4) -plt.plot(output_FLODOG[512, 250:750]) - -plt.subplot(3, 2, 5) -plt.imshow(output_FODOG, extent=visextent) -plt.subplot(3, 2, 6) -plt.plot(output_FODOG[512, 250:750]) diff --git a/docs/_config.yml b/docs/_config.yml index dfc875d..1f1747d 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -9,6 +9,7 @@ logo: logo.png # See https://jupyterbook.org/content/execute.html execute: execute_notebooks: force + timeout: 300 # Define the name of the latex output file for PDF builds #latex: @@ -44,6 +45,10 @@ sphinx: - hoverxref.extension config: + nb_custom_formats: + .py: + - jupytext.reads + - fmt: py:percent exclude_patterns: ['_build', '_templates'] suppress_warnings: ["etoc.toctree"] #autosummary_generate: True # autosummary generates module-level .rst files? diff --git a/docs/_toc.yml b/docs/_toc.yml index 0c4b1f9..fa9b526 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -4,17 +4,33 @@ format: jb-book root: index parts: - - caption: Getting started with multyscale + - caption: Quickstart with `multyscale` + numbered: False chapters: - - file: getting_started/installation - - file: getting_started/getting_started - - file: howtos/howtos - - caption: Topic guides + - file: quickstart/installation + - file: quickstart/quickstart + - caption: Overiew chapters: + - file: topic_guides/overview - file: topic_guides/history - # - file: topic_guides/normalization_ODOG - # - file: topic_guides/normalization_LODOG - # - file: topic_guides/normalization_FLODOG + - caption: Filtering + numbered: True + chapters: + - file: filtering/filters + - file: filtering/multiscale + - file: filtering/filterbanks + - caption: Normalization + numbered: True + chapters: + - file: normalization/normalization_ODOG + - file: normalization/normalization_LODOG + sections: + - file: normalization/explore_LODOG_parameter + - file: normalization/normalization_FLODOG + sections: + - file: normalization/explore_FLODOG_parameters + # - caption: Decoding + # numbered: False - caption: Reference chapters: - file: reference/api.md diff --git a/demo/example_stimulus.npy b/docs/filtering/example_stimulus.npy similarity index 100% rename from demo/example_stimulus.npy rename to docs/filtering/example_stimulus.npy diff --git a/demo/example_stimulus.png b/docs/filtering/example_stimulus.png similarity index 100% rename from demo/example_stimulus.png rename to docs/filtering/example_stimulus.png diff --git a/demo/example_stimulus_mask.npy b/docs/filtering/example_stimulus_mask.npy similarity index 100% rename from demo/example_stimulus_mask.npy rename to docs/filtering/example_stimulus_mask.npy diff --git a/demo/filterbanks.py b/docs/filtering/filterbanks.py similarity index 100% rename from demo/filterbanks.py rename to docs/filtering/filterbanks.py diff --git a/demo/filters.py b/docs/filtering/filters.py similarity index 100% rename from demo/filters.py rename to docs/filtering/filters.py diff --git a/docs/getting_started/getting_started.md b/docs/getting_started/getting_started.md deleted file mode 100644 index 2b792fa..0000000 --- a/docs/getting_started/getting_started.md +++ /dev/null @@ -1,14 +0,0 @@ -# Getting started (Tutorial) - -The following pages serve as **tutorial**, -walking you through the very basics of using `multyscale`. - -Along the way, they also refer to some [topic guides](../topic_guides/topic_guides), -which have more in-depth explanation on various concepts and design decisions. - -First, [install `multyscale`](./installation/) - - \ No newline at end of file diff --git a/docs/howtos/howtos.md b/docs/howtos/howtos.md deleted file mode 100644 index 38b0b29..0000000 --- a/docs/howtos/howtos.md +++ /dev/null @@ -1,8 +0,0 @@ -# How-to... (recipes) - - \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 7e9d859..1ccd419 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,26 +10,27 @@ for implementing multiscale spatial filtering models of early vision ::::{grid} 2 :gutter: 3 -:::{grid-item-card} Get started -:link: getting_started/getting_started +:::{grid-item-card} Quickstart +:link: quickstart/quickstart :link-type: doc :text-align: center 🏃 ^^^ Install `multyscale` -and follow an introductory tutorial +and get started, +if you're already familiar with multiscale filtering ::: :::{grid-item-card} Learn more -:link: topic_guides/topic_guides +:link: overview/overview :link-type: doc :text-align: center 🧑‍🏫 ^^^ -Learn more about design decisions -and particular conventions and topics within `multyscale` +Learn more about multiscale spatial filtering, +and how `multyscale` implements these ideas ::: :::{grid-item-card} Reference diff --git a/demo/WE_dual.npy b/docs/normalization/WE_dual.npy similarity index 100% rename from demo/WE_dual.npy rename to docs/normalization/WE_dual.npy diff --git a/demo/WE_dual_mask.npy b/docs/normalization/WE_dual_mask.npy similarity index 100% rename from demo/WE_dual_mask.npy rename to docs/normalization/WE_dual_mask.npy diff --git a/docs/normalization/example_stimulus.npy b/docs/normalization/example_stimulus.npy new file mode 100644 index 0000000000000000000000000000000000000000..a602b2dd23304fd9afacd943e686290f4f10005b GIT binary patch literal 8388736 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%0AqDKf4(z}kaDW3G-~b0WzyS_$ zfCC)hz-b41RbCZPKmi35P(T3%6i{G8fz!_uzFt?OgAOTR2X7>8w%Wh{_y>~G97eC0Xwh*J8%aa-~b0WzyS_$ zfCC)h00%g5+JRn`R|OPMKmi35P(T3%6xdMU^z($T*VX8tLkie|9oT_8-~b0WzyS_$ zfCC)h00%h0f!hxB3cV_zfC36Apnw7jD4@WG0=J((eE+UY2OUzt4(z}V+yMtTzyS_$ zfCC)h00%h00S=sYpjYKp0RxjfC36Apnw7jD6pZx?dK2Qzbn&0hZL{_JFo+HzyS_$ zfCC)h00%h00S<701E(G6Re4oF0R0tzUgfC36Apnw7!3fzAF@cp|o9dt+mJFo*g za0eXV00%h00S<70103K02RLxrfnJqY1r$&~0R1=MUe%E7L)T z6tDw3umg9%0S<70103K02ROh14sd`2ryb~3c~w9G1r$&~0RhU zI;4Od*nu6m0}gP2103K02ROh14sd`29JuX3uh6Rk3Mim}0tzUgfC36^C~*7v!}ssX zbkHFM?7$A}z#VXa103K02ROh14sd`29N@rd2YOXr6;MC{1r$&~0R z{kt+9bVva^umd}A2OQu42ROh14sd`29N+*4IB?p5UX@n`6i`3`1r$&~0Rvc6c=#T<-UK4miL84sd`29N+*4IKTl8aNxEBy+W@FD4>7>3Mim}0tzUw zp}_6u58uBl(?N$6umd}=19!jy4sd`29N+*4IKTl8aDW4+9q3hgRX_m+6i`3`1r$&~ zfei&tKTr62U5ySpq<|gRfgQL54sd`29N+*4IKTl8aDW3Gxa~l%(5nIpD4>7>3Mim} z0t##>aQpeg_wUMd&>;ovzz*!d9dLjH9N+*4IKTl8aDW3G;J|4IdR1N(P(T3%6i`3` z1r$(VLxI!J6TV(oqk|49U7> z3Mim}0vih4e*W7>3Min!h61;rKYahLOa~oOzz*!d4%`6;IKTl8aDW3G-~b0WzyS`NcA!_~RRIMQ zP(T3%6i`3`1vV5o{XF68bu~KZkOFpK2X^2NIKTl8aDW3G-~b0WzyS_$;I;$3Laz!a zpnw7jD4>7>3MjCl!0qP`-@hxGyaDW3G-~b0WzyS_$fCC)3?Le>4 zs{#rrpnw7jD4>7>3T!BF`}xE7@5*%0AqDKf4(z}kaDW3G-~b0WzyS_$fCC)hz-b41 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 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00;ixP8|yX0000C^0(eQh=Rxi9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> X9`Jw%Jm3Kjc)$Z5@PG$A;DJ97O#rsC literal 0 HcmV?d00001 diff --git a/docs/getting_started/example_stimulus.png b/docs/normalization/example_stimulus.png similarity index 100% rename from docs/getting_started/example_stimulus.png rename to docs/normalization/example_stimulus.png diff --git a/docs/normalization/example_stimulus_mask.npy b/docs/normalization/example_stimulus_mask.npy new file mode 100644 index 0000000000000000000000000000000000000000..0ea9b61987c46f21a7f7d1b76c79a0043c1112fd GIT binary patch literal 8388736 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 zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7e?*A5+!000002>SoJ9f3~JfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8Fn4Aoqb< AO8@`> literal 0 HcmV?d00001 diff --git a/demo/explore_FLODOG_parameters.py b/docs/normalization/explore_FLODOG_parameters.py similarity index 100% rename from demo/explore_FLODOG_parameters.py rename to docs/normalization/explore_FLODOG_parameters.py diff --git a/demo/explore_LODOG_parameter.py b/docs/normalization/explore_LODOG_parameter.py similarity index 100% rename from demo/explore_LODOG_parameter.py rename to docs/normalization/explore_LODOG_parameter.py diff --git a/demo/normalization_FLODOG.py b/docs/normalization/normalization_FLODOG.py similarity index 100% rename from demo/normalization_FLODOG.py rename to docs/normalization/normalization_FLODOG.py diff --git a/demo/normalization_LODOG.py b/docs/normalization/normalization_LODOG.py similarity index 100% rename from demo/normalization_LODOG.py rename to docs/normalization/normalization_LODOG.py diff --git a/demo/normalization_ODOG.py b/docs/normalization/normalization_ODOG.py similarity index 100% rename from demo/normalization_ODOG.py rename to docs/normalization/normalization_ODOG.py diff --git a/docs/topic_guides/topic_guides.md b/docs/normalization/topic_guides.md similarity index 83% rename from docs/topic_guides/topic_guides.md rename to docs/normalization/topic_guides.md index 7bb910b..513e170 100644 --- a/docs/topic_guides/topic_guides.md +++ b/docs/normalization/topic_guides.md @@ -7,4 +7,10 @@ or whether a model attempts to more generally model early vision such, that brightness perception phenomena emerge but are not explicitly modeled. A (super)family of models of the latter kind that have been particularly successful, -are multiscale spatial filtering models, examplified in the -ODOG genus of models. \ No newline at end of file +are multiscale spatial filtering models, examplified in the -ODOG genus of models. + +```{toctree} +normalization_ODOG +normalization_LODOG +normalization_FLODOG +``` \ No newline at end of file diff --git a/docs/quickstart/example_stimulus.npy b/docs/quickstart/example_stimulus.npy new file mode 100644 index 0000000000000000000000000000000000000000..a602b2dd23304fd9afacd943e686290f4f10005b GIT binary patch literal 8388736 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%0AqDKf4(z}kaDW3G-~b0WzyS_$ zfCC)hz-b41RbCZPKmi35P(T3%6i{G8fz!_uzFt?OgAOTR2X7>8w%Wh{_y>~G97eC0Xwh*J8%aa-~b0WzyS_$ zfCC)h00%g5+JRn`R|OPMKmi35P(T3%6xdMU^z($T*VX8tLkie|9oT_8-~b0WzyS_$ zfCC)h00%h0f!hxB3cV_zfC36Apnw7jD4@WG0=J((eE+UY2OUzt4(z}V+yMtTzyS_$ zfCC)h00%h00S=sYpjYKp0RxjfC36Apnw7jD6pZx?dK2Qzbn&0hZL{_JFo+HzyS_$ zfCC)h00%h00S<701E(G6Re4oF0R0tzUgfC36Apnw7!3fzAF@cp|o9dt+mJFo*g za0eXV00%h00S<70103K02RLxrfnJqY1r$&~0R1=MUe%E7L)T z6tDw3umg9%0S<70103K02ROh14sd`2ryb~3c~w9G1r$&~0RhU zI;4Od*nu6m0}gP2103K02ROh14sd`29JuX3uh6Rk3Mim}0tzUgfC36^C~*7v!}ssX zbkHFM?7$A}z#VXa103K02ROh14sd`29N@rd2YOXr6;MC{1r$&~0R z{kt+9bVva^umd}A2OQu42ROh14sd`29N+*4IB?p5UX@n`6i`3`1r$&~0Rvc6c=#T<-UK4miL84sd`29N+*4IKTl8aNxEBy+W@FD4>7>3Mim}0tzUw zp}_6u58uBl(?N$6umd}=19!jy4sd`29N+*4IKTl8aDW4+9q3hgRX_m+6i`3`1r$&~ zfei&tKTr62U5ySpq<|gRfgQL54sd`29N+*4IKTl8aDW3Gxa~l%(5nIpD4>7>3Mim} z0t##>aQpeg_wUMd&>;ovzz*!d9dLjH9N+*4IKTl8aDW3G;J|4IdR1N(P(T3%6i`3` z1r$(VLxI!J6TV(oqk|49U7> z3Mim}0vih4e*W7>3Min!h61;rKYahLOa~oOzz*!d4%`6;IKTl8aDW3G-~b0WzyS`NcA!_~RRIMQ zP(T3%6i`3`1vV5o{XF68bu~KZkOFpK2X^2NIKTl8aDW3G-~b0WzyS_$;I;$3Laz!a zpnw7jD4>7>3MjCl!0qP`-@hxGyaDW3G-~b0WzyS_$fCC)3?Le>4 zs{#rrpnw7jD4>7>3T!BF`}xE7@5*%0AqDKf4(z}kaDW3G-~b0WzyS_$fCC)hz-b41 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 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00;ixP8|yX0000C^0(eQh=Rxi9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> z9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kj zc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A z-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzylue zfCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b< z10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b z4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw% zJm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5 z@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVK zzyluefCoI_0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_ z0S|b<10L{z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z z2Rz^b4|u=>9`Jw%Jm3Kjc)$Z5@PG$A-~kVKzyluefCoI_0S|b<10L{z2Rz^b4|u=> X9`Jw%Jm3Kjc)$Z5@PG$A;DJ97O#rsC literal 0 HcmV?d00001 diff --git a/docs/quickstart/example_stimulus.png b/docs/quickstart/example_stimulus.png new file mode 100644 index 0000000000000000000000000000000000000000..5d4ddca126f13f1f90eabdc05cd87baa57f7b9ea GIT binary patch literal 6627 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGl*C0wmxUYJKQV)n{%B?Z zg~DhtFnmi!NM-;=@`vrCwE!rwk7jvLD2!(L;hW_hKoLCJs2gq6jpp;wYyhwB>TYdh v2A6q`|E@L1NC24!8o&hwBUqFfJUq(AG{fJZDb6?C9VF%H>gTe~DWM4fGU;kU literal 0 HcmV?d00001 diff --git a/docs/quickstart/example_stimulus_mask.npy b/docs/quickstart/example_stimulus_mask.npy new file mode 100644 index 0000000000000000000000000000000000000000..0ea9b61987c46f21a7f7d1b76c79a0043c1112fd GIT binary patch literal 8388736 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 zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7e?*A5+!000002>SoJ9f3~JfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8Fn4Aoqb< AO8@`> literal 0 HcmV?d00001 diff --git a/docs/getting_started/installation.md b/docs/quickstart/installation.md similarity index 99% rename from docs/getting_started/installation.md rename to docs/quickstart/installation.md index 70cec56..46f1b85 100644 --- a/docs/getting_started/installation.md +++ b/docs/quickstart/installation.md @@ -1,4 +1,4 @@ -# Installing +# Install `multyscale` `multyscale` can be installed in several ways. For most usecases, i.e., to _use_ the functions in `multyscale`, diff --git a/docs/quickstart/quickstart.md b/docs/quickstart/quickstart.md new file mode 100644 index 0000000..6677120 --- /dev/null +++ b/docs/quickstart/quickstart.md @@ -0,0 +1,14 @@ +# Quick start with `multyscale` + +The following pages serve as a quickstart quide +to working with the `multyscale` library. +They are meant to introduce the core functionalities of the library, +and assume knowledge of and familiarity with +the concepts of multiscale spatial filtering models. + +The rest of this "book" is a more detailed introduction to the topic. + +```{toctree} +run_(F)(L)ODOG +construct_new +``` \ No newline at end of file diff --git a/demo/howto_run_(F)(L)ODOG.py b/docs/quickstart/run_(F)(L)ODOG.py similarity index 99% rename from demo/howto_run_(F)(L)ODOG.py rename to docs/quickstart/run_(F)(L)ODOG.py index 0a11d79..05114b0 100644 --- a/demo/howto_run_(F)(L)ODOG.py +++ b/docs/quickstart/run_(F)(L)ODOG.py @@ -1,5 +1,5 @@ # %% [markdown] -# # How-to: Run the existing -ODOG models +# # Run an existing (-ODOG) model # This Recipe describes how to run the existing -ODOG models # (Blakeslee & McCourt, 1999; Robinson, Hammon, & de Sa, 2007) # as implemented in the module. diff --git a/docs/topic_guides/overview.md b/docs/topic_guides/overview.md new file mode 100644 index 0000000..d30f186 --- /dev/null +++ b/docs/topic_guides/overview.md @@ -0,0 +1 @@ +# Overview of `multyscale` \ No newline at end of file From 6b7e7da8dc4d9dae5bc4d05a64a1e435f0958323 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Mon, 25 Dec 2023 17:23:04 +0100 Subject: [PATCH 46/79] docs: cache Notebooks --- docs/_config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_config.yml b/docs/_config.yml index 1f1747d..590f3c7 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -5,10 +5,10 @@ title: multyscale author: Joris Vincent logo: logo.png -# Force re-execution of notebooks on each build. +# Cache execution of notebooks # See https://jupyterbook.org/content/execute.html execute: - execute_notebooks: force + execute_notebooks: cache timeout: 300 # Define the name of the latex output file for PDF builds From 64ef992ab7aaf753cbb221177f26848c4fc632e3 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Mon, 25 Dec 2023 17:46:56 +0100 Subject: [PATCH 47/79] docs: update description of docs formats --- docs/contributing/docs.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/contributing/docs.md b/docs/contributing/docs.md index da209dc..1810930 100644 --- a/docs/contributing/docs.md +++ b/docs/contributing/docs.md @@ -6,12 +6,6 @@ and consists of a collection of Markdown (`.md`) files, specifically in the [MyST Markdown](https://jupyterbook.org/en/stable/content/myst.html) flavor (as well as some ReStructured Text). -The documentation is generally organized along [The Documentation System](https://documentation.divio.com/), -consisting of the following categories: -- learning-oriented tutorials, under -- understanding-oriented -- information-oriented - ## Building the documentation `multyscale`'s documentation is build using [Jupyter Book](https://jupyterbook.org/en/stable/intro.html) @@ -24,13 +18,25 @@ which will then provide an output message on how to view the locally-built docum ## Executable content Some pages are pure (MyST) Markdown files; -others (e.g. the tutorials) are executable notebooks, in the same MyST Markdown format. -These MyST Notebooks can contain both Markdown syntax, +others (e.g. the tutorials) are executable notebooks, +either in the same MyST format, +or in `.py` Python files. +Both of these are automatically executed and converted +to Jupyter Notebook style documents +using [JupyText](https://jupytext.readthedocs.io/en/latest/). + +The MyST Notebooks can contain both Markdown syntax, as well as `code-cell` blocks which are executed during the build and their output is "woven" into the resulting page. -These MyST Notebooks have a YAML frontmatter -directing [JupyText](https://jupytext.readthedocs.io/en/latest/) -how to convert them. +They also have a YAML frontmatter specifying how they should be converted. + +The `.py`-files have a similar structure, +although here all cells are specified with the +["percent format"](https://jupytext.readthedocs.io/en/latest/formats-scripts.html#the-percent-format) +`# %%` demarking the beginning of a new cell. +Markdown cells starting with `# %% [markdown]` will be interpreted entirely +as containing only markdown, no executable code. + ## Contributing back to `multyscale` From feebd94573d21a6819583341d37fe973119a1f6f Mon Sep 17 00:00:00 2001 From: RavidHausmann Date: Thu, 9 Dec 2021 14:07:23 +0100 Subject: [PATCH 48/79] docs(normalization): start notebook unifying normalization steps --- demo/re3.ipynb | 209 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 demo/re3.ipynb diff --git a/demo/re3.ipynb b/demo/re3.ipynb new file mode 100644 index 0000000..ace8a31 --- /dev/null +++ b/demo/re3.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dcbfab0e", + "metadata": {}, + "source": [ + "# Rewrite Reuse Refactor\n", + "## Reproducing Unit Tests using refactored ODOG formulation\n", + "This Notebook introduces a new way of formulating the normalization step of the ODOG family of Models, and shows that this formulation is numerically equivalent to the original paper implementation, to establish continuity" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4e3519d", + "metadata": {}, + "outputs": [], + "source": [ + "# Third party libraries\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from PIL import Image\n", + "import scipy\n", + "\n", + "# Import local module\n", + "import multyscale" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b0ec8f2", + "metadata": {}, + "outputs": [], + "source": [ + "# %% Load example stimulus\n", + "stimulus = np.asarray(Image.open(\"image_source/example_stimulus.png\").convert(\"L\"))\n", + "\n", + "# %% Parameters of image\n", + "# visual extent, same convention as pyplot:\n", + "visextent = (-16, 16, -16, 16)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7655c54", + "metadata": {}, + "outputs": [], + "source": [ + "# Frontend filterbank of ODOG implementation by Robinson et al. (2007)\n", + "filterbank = multyscale.filterbank.RHS2007(filtershape=stimulus.shape, visextent=visextent)\n", + "\n", + "# Filter the example stimulus\n", + "filters_output = filterbank.apply(stimulus)" + ] + }, + { + "cell_type": "markdown", + "id": "73904479", + "metadata": {}, + "source": [ + "This preamble defines a filterbank output which is not already normalized by any function. The Traditional ODOG normalization function is multyscale/models.py ODOG, shown below" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b519b55f", + "metadata": {}, + "outputs": [], + "source": [ + "def normalize_outputs(self, filters_output):\n", + " # TODO: docstring\n", + " normalizers = self.normalizers(filters_output)\n", + "\n", + " normalizer_RMS = self.normalizers_to_RMS(normalizers)\n", + "\n", + " normalized_outputs = np.ndarray(filters_output.shape)\n", + " for o, s in np.ndindex(filters_output.shape[:2]):\n", + " normalized_outputs[o, s] = filters_output[o, s] / normalizer_RMS[o, s]\n", + "\n", + " return normalized_outputs\n" + ] + }, + { + "cell_type": "markdown", + "id": "a46964ec", + "metadata": {}, + "source": [ + "We're comparing this to a new implementation which replaces the math of the original function by a more general form. \n", + "The traditional ODOG formulation looks like this:\n", + "\n", + "$$ODOG: \\frac{f_{o^*,s^*}}{\\sqrt{\\frac{1}{1024^2}\\sum_{x,y=0}^{1024,1024}(\\sum_{o,s=1}^{O, S}{w_{o,s} f_{o,s})^2}}}$$\n", + "where $w_{o,s} = \\begin{cases} \n", + " 1/S & o = o^* \\\\\n", + " 0 & else\n", + " \\end{cases} \n", + "$ \n", + "This is equivalent to an equation that implements the averaging in the denominator as a linear filter, that scales the filter aggregate produced by the sum over scales/orientations. This changes the dimensionality of the denominator, but not actually the result of the division, since image by image division is executed pixel-wise\n", + "\n", + "$$ODOG: \\frac{f_{o^*,s^*}}{\\sqrt{F_{x,y}*(\\sum_{o,s=0}^{5,6}{w_{o,s} f_{o,s})^2}}}$$\n", + "$w_{o,s}$ same as above, \n", + "$F_{x,y} = \\frac{1}{1024^2}$ everywhere.\n", + "\n", + "By renaming the remaining sum in the denominator, we can find an abstract formulation of the RMS-Norm as a sequence of linear filters\n", + "\n", + " $$\\frac{f_{o^*,s^*}}{\\sqrt{F_{x,y}*(\\sum_{o,s=0}^{5,6}{w_{o,s} f_{o,s})^2}}}=\\frac{f_{o,s}}{\\sqrt{G * N^2}} = \\frac{f_{o,s}}{\\sqrt{G * (W \\cdot f)^2}}$$\n", + " \n", + " Which helps us develop terminology. The entire fraction is called the divisive norm on the filterbank-output $f$. \n", + " $G * N^2$ is the local energy, calculated as the convolution of $G$ the spatial weighting, and $N^2$ the Normcoefficient image squared. The Normcoefficient Image can be calculated in turn by computing the Normpool $W$ dotproduct against the same $f$ that we are normalizing.\n", + " \n", + "An implementation of this formulation can be found here:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97891411", + "metadata": {}, + "outputs": [], + "source": [ + "def divisive_norm(filter_outputs, o, s):\n", + " G = spatial_weighting(filter_outputs.shape[-2:])\n", + " N = normcoeff(filter_outputs, o, s)**2\n", + " \n", + " z = scipy.signal.convolve(G, N, mode=\"same\")\n", + " normed_filter = filter_outputs[o,s,:] / np.sqrt(z)\n", + " return normed_filter\n", + "\n", + "def normcoeff(filter_outputs, o_0, s_0): \n", + " w = normpool(filter_outputs.shape[0],filter_outputs.shape[1], o_0, s_0)\n", + " coeffs = np.tensordot(filter_outputs, w, axes=([0, 1], [0, 1]))\n", + " return coeffs\n", + "\n", + "def normpool(S, O, o_0, s_0): # w_{o,s}\n", + " ODOG = True\n", + " LODOG, FLODOG = False, False\n", + " w = np.ones(shape=(S,O))\n", + " for o, s in zip(range(O), range(S)):\n", + " if ODOG or LODOG:\n", + " if o==o_0:\n", + " w[o,s] = 1/S\n", + " else:\n", + " w[o,s] = 0\n", + "\n", + " if FLODOG:\n", + " if o==o_0:\n", + " w[o,s] = 1 #gaussian(s-s_0)\n", + " else:\n", + " w[o,s] = 0\n", + " return w\n", + "\n", + "def spatial_weighting(filter_shape):\n", + " ODOG = True\n", + " LODOG, FLODOG = False, False\n", + " if ODOG:\n", + " G = np.ones(filter_shape) / np.prod(filter_shape)\n", + " \n", + " if LODOG:\n", + " G = 1 #gaussian(filter_shape, sigma)\n", + "\n", + " if FLODOG:\n", + " G = 1 #gaussian(filter_shape, sigma=k*s)\n", + " return G" + ] + }, + { + "cell_type": "markdown", + "id": "bdb3590a", + "metadata": {}, + "source": [ + "Our goal is now to establish numerical equivalence between the traditional formulation and the result of the newly reformatted and mathematically motivated code\n", + "\n", + "The original implementation is already tested against the matlab implementation of " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a27717c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 925a4a85dc43ca7bfc65fc4bd95a744dbeb0707e Mon Sep 17 00:00:00 2001 From: RavidHausmann Date: Tue, 18 Jan 2022 12:03:56 +0100 Subject: [PATCH 49/79] docs(normalization): move towards non-comparative unit testing --- demo/re3.ipynb | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index ace8a31..36585fc 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "b4e3519d", "metadata": {}, "outputs": [], @@ -109,14 +109,15 @@ " $$\\frac{f_{o^*,s^*}}{\\sqrt{F_{x,y}*(\\sum_{o,s=0}^{5,6}{w_{o,s} f_{o,s})^2}}}=\\frac{f_{o,s}}{\\sqrt{G * N^2}} = \\frac{f_{o,s}}{\\sqrt{G * (W \\cdot f)^2}}$$\n", " \n", " Which helps us develop terminology. The entire fraction is called the divisive norm on the filterbank-output $f$. \n", - " $G * N^2$ is the local energy, calculated as the convolution of $G$ the spatial weighting, and $N^2$ the Normcoefficient image squared. The Normcoefficient Image can be calculated in turn by computing the Normpool $W$ dotproduct against the same $f$ that we are normalizing.\n", + " $G * N^2$ is the local energy, calculated as the convolution of $G$ the spatial weighting, and $N^2$ the Normcoefficient image squared. \n", + "The Normcoefficient Image can be calculated in turn by computing the Normpool $W$ dotproduct against the same $f$ that we are normalizing.\n", " \n", "An implementation of this formulation can be found here:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "97891411", "metadata": {}, "outputs": [], @@ -173,15 +174,52 @@ "source": [ "Our goal is now to establish numerical equivalence between the traditional formulation and the result of the newly reformatted and mathematically motivated code\n", "\n", - "The original implementation is already tested against the matlab implementation of " + "The original implementation is already tested against the matlab implementation of RHS_2007, so we know it is sound.\n", + " \n", + "We will use this notebook to figure out: 1) if all of the units of our new implementation act as expected and 2) if the integration of those units is again comparable to RHS_2007" + ] + }, + { + "cell_type": "markdown", + "id": "a4e9fd5b", + "metadata": {}, + "source": [ + "Note that in the module, all of the below functions will appear in multyscale/test, separated into fixtures and tests." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "3a27717c", "metadata": {}, "outputs": [], + "source": [ + "def filtershape():\n", + " return (1024,1024)\n", + "\n", + "def test_unit_spatial_weighting(filter_shape):\n", + " G = spatial_weighting(filter_shape)\n", + " assert np.allclose(G, 1/np.prod(filter_shape))\n", + " \n", + "test_unit_spatial_weighting(filtershape())\n", + "\n", + "def filteroutput():\n", + " return np.random.random(shape=(6,7,1024,1024))\n", + "\n", + "def test_unit_normcoeff(filter_outputs):\n", + " # use sum over scales formulation instead of linear filter\n", + " N = np.zeros(shape=(6,7,1024,1024))\n", + " for o, s in np.ndindex(filters_output.shape[:2]):\n", + " N[o, s] = \n", + " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s]) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "896ccc0d", + "metadata": {}, + "outputs": [], "source": [] } ], From b7e9a8e3fbd4b11874d3405d253b218295ea105f Mon Sep 17 00:00:00 2001 From: RavidHausmann Date: Tue, 18 Jan 2022 16:31:17 +0100 Subject: [PATCH 50/79] docs(normalization): unit test for normcoeff troubles --- demo/re3.ipynb | 49 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index 36585fc..f5e5fec 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -181,7 +181,7 @@ }, { "cell_type": "markdown", - "id": "a4e9fd5b", + "id": "bf4f8648", "metadata": {}, "source": [ "Note that in the module, all of the below functions will appear in multyscale/test, separated into fixtures and tests." @@ -189,10 +189,37 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "3a27717c", + "execution_count": null, + "id": "399d759d", "metadata": {}, "outputs": [], + "source": [] + }, + { + "cell_type": "raw", + "id": "d8aaf826", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "3a27717c", + "metadata": {}, + "outputs": [ + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_5988/4106493988.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 21\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 23\u001b[0;31m \u001b[0mtest_unit_normcoeff\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilteroutput\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/tmp/ipykernel_5988/4106493988.py\u001b[0m in \u001b[0;36mtest_unit_normcoeff\u001b[0;34m(filter_outputs)\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndindex\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndindex\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 19\u001b[0;31m \u001b[0mN\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 20\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mallclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnormcoeff\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mN\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 21\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] + } + ], "source": [ "def filtershape():\n", " return (1024,1024)\n", @@ -204,20 +231,26 @@ "test_unit_spatial_weighting(filtershape())\n", "\n", "def filteroutput():\n", - " return np.random.random(shape=(6,7,1024,1024))\n", + " return np.random.random(size=(6,7,1024,1024))\n", + "\n", "\n", "def test_unit_normcoeff(filter_outputs):\n", " # use sum over scales formulation instead of linear filter\n", " N = np.zeros(shape=(6,7,1024,1024))\n", - " for o, s in np.ndindex(filters_output.shape[:2]):\n", - " N[o, s] = \n", - " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s]) " + " for o, s in np.ndindex(filter_outputs.shape[:2]):\n", + " for x,y in np.ndindex(filter_outputs.shape[2:]):\n", + " N[o, s, :, :] += 1/filter_outputs.shape[1] * np.sum(filter_outputs[o,:,x,y])\n", + " print(f\"di)\n", + " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s]) \n", + " \n", + " \n", + "test_unit_normcoeff(filteroutput())" ] }, { "cell_type": "code", "execution_count": null, - "id": "896ccc0d", + "id": "e743a729", "metadata": {}, "outputs": [], "source": [] From 435a44895d8347c9421715efeea06420fb5f813c Mon Sep 17 00:00:00 2001 From: ravidhausmann Date: Fri, 21 Jan 2022 13:53:29 +0000 Subject: [PATCH 51/79] docs(normalization): rest of the unit tests --- demo/re3.ipynb | 88 +++++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index f5e5fec..3ec1eb2 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -29,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "1b0ec8f2", "metadata": {}, "outputs": [], @@ -44,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "e7655c54", "metadata": {}, "outputs": [], @@ -117,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 13, "id": "97891411", "metadata": {}, "outputs": [], @@ -135,22 +135,23 @@ " coeffs = np.tensordot(filter_outputs, w, axes=([0, 1], [0, 1]))\n", " return coeffs\n", "\n", - "def normpool(S, O, o_0, s_0): # w_{o,s}\n", + "def normpool(O, S, o_0, s_0): # w_{o,s}\n", " ODOG = True\n", " LODOG, FLODOG = False, False\n", - " w = np.ones(shape=(S,O))\n", - " for o, s in zip(range(O), range(S)):\n", - " if ODOG or LODOG:\n", - " if o==o_0:\n", - " w[o,s] = 1/S\n", - " else:\n", - " w[o,s] = 0\n", + " w = np.ones(shape=(O,S))\n", + " for o in range(O):\n", + " for s in range(S):\n", + " if ODOG or LODOG:\n", + " if o==o_0:\n", + " w[o,s] = 1/S\n", + " else:\n", + " w[o,s] = 0\n", "\n", - " if FLODOG:\n", - " if o==o_0:\n", - " w[o,s] = 1 #gaussian(s-s_0)\n", - " else:\n", - " w[o,s] = 0\n", + " if FLODOG:\n", + " if o==o_0:\n", + " w[o,s] = 1 #gaussian(s-s_0)\n", + " else:\n", + " w[o,s] = 0\n", " return w\n", "\n", "def spatial_weighting(filter_shape):\n", @@ -181,7 +182,7 @@ }, { "cell_type": "markdown", - "id": "bf4f8648", + "id": "309a9c28", "metadata": {}, "source": [ "Note that in the module, all of the below functions will appear in multyscale/test, separated into fixtures and tests." @@ -190,33 +191,33 @@ { "cell_type": "code", "execution_count": null, - "id": "399d759d", + "id": "4807ee1c", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "raw", - "id": "d8aaf826", + "id": "48124545", "metadata": {}, "source": [] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 16, "id": "3a27717c", "metadata": {}, "outputs": [ { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/tmp/ipykernel_5988/4106493988.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 21\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 23\u001b[0;31m \u001b[0mtest_unit_normcoeff\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilteroutput\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/tmp/ipykernel_5988/4106493988.py\u001b[0m in \u001b[0;36mtest_unit_normcoeff\u001b[0;34m(filter_outputs)\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndindex\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndindex\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 19\u001b[0;31m \u001b[0mN\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 20\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mallclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnormcoeff\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilter_outputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mN\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 21\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + "name": "stdout", + "output_type": "stream", + "text": [ + "Run full 6x7 analysis? [y/n]n\n", + "dimension 3,5 is sound\n", + "dimension 1,3 is sound\n", + "dimension 4,6 is sound\n", + "dimension 5,2 is sound\n", + "dimension 2,5 is sound\n" ] } ], @@ -236,21 +237,34 @@ "\n", "def test_unit_normcoeff(filter_outputs):\n", " # use sum over scales formulation instead of linear filter\n", - " N = np.zeros(shape=(6,7,1024,1024))\n", - " for o, s in np.ndindex(filter_outputs.shape[:2]):\n", - " for x,y in np.ndindex(filter_outputs.shape[2:]):\n", - " N[o, s, :, :] += 1/filter_outputs.shape[1] * np.sum(filter_outputs[o,:,x,y])\n", - " print(f\"di)\n", - " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s]) \n", - " \n", + " if input(\"Run full 6x7 analysis? [y/n] \")==\"y\":\n", + " N = np.zeros(shape=(6,7,1024,1024))\n", + " for o, s in np.ndindex(filter_outputs.shape[:2]):\n", + " for x,y in np.ndindex(filter_outputs.shape[2:]):\n", + " N[o, s, x, y] = np.sum(filter_outputs[o,:,x,y]) / filter_outputs.shape[1]\n", + " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s])\n", + "\n", + " print(f\"dimension {o},{s} is sound\")\n", + " else:\n", + " print(\"only doing 5 dimensions chosen at random\")\n", + " N = np.zeros(shape=(6,7,1024,1024))\n", + " for i in range(5):\n", + " o, s = np.random.randint(0,6), np.random.randint(0,7)\n", + " for x,y in np.ndindex(filter_outputs.shape[2:]):\n", + " N[o, s, x, y] = np.sum(filter_outputs[o,:,x,y]) / filter_outputs.shape[1]\n", + " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s])\n", + "\n", + " print(f\"dimension {o},{s} is sound\")\n", " \n", + "\n", + " \n", "test_unit_normcoeff(filteroutput())" ] }, { "cell_type": "code", "execution_count": null, - "id": "e743a729", + "id": "cc491b3f", "metadata": {}, "outputs": [], "source": [] From ed1f804b330ac963bc20841d4ef4b83a4134fc38 Mon Sep 17 00:00:00 2001 From: RavidHausmann Date: Mon, 24 Jan 2022 22:41:45 +0100 Subject: [PATCH 52/79] docs(normalization): integration test for divisive norm --- demo/re3.ipynb | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index 3ec1eb2..05bc4b5 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -53,7 +53,7 @@ "filterbank = multyscale.filterbank.RHS2007(filtershape=stimulus.shape, visextent=visextent)\n", "\n", "# Filter the example stimulus\n", - "filters_output = filterbank.apply(stimulus)" + "#filters_output = filterbank.apply(stimulus)" ] }, { @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "b519b55f", "metadata": {}, "outputs": [], @@ -117,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 18, "id": "97891411", "metadata": {}, "outputs": [], @@ -126,7 +126,7 @@ " G = spatial_weighting(filter_outputs.shape[-2:])\n", " N = normcoeff(filter_outputs, o, s)**2\n", " \n", - " z = scipy.signal.convolve(G, N, mode=\"same\")\n", + " z = scipy.signal.convolve(G, N, mode=\"valid\")\n", " normed_filter = filter_outputs[o,s,:] / np.sqrt(z)\n", " return normed_filter\n", "\n", @@ -204,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 20, "id": "3a27717c", "metadata": {}, "outputs": [ @@ -212,12 +212,18 @@ "name": "stdout", "output_type": "stream", "text": [ - "Run full 6x7 analysis? [y/n]n\n", - "dimension 3,5 is sound\n", - "dimension 1,3 is sound\n", - "dimension 4,6 is sound\n", + "Run full 6x7 analysis? [y/n] n\n", + "only doing 5 dimensions chosen at random\n", + "dimension 2,0 is sound\n", + "dimension 4,3 is sound\n", + "dimension 4,2 is sound\n", + "dimension 0,1 is sound\n", + "dimension 1,1 is sound\n", + "dimension 4,0 is sound\n", "dimension 5,2 is sound\n", - "dimension 2,5 is sound\n" + "dimension 4,1 is sound\n", + "dimension 0,4 is sound\n", + "dimension 3,3 is sound\n" ] } ], @@ -258,7 +264,20 @@ " \n", "\n", " \n", - "test_unit_normcoeff(filteroutput())" + "test_unit_normcoeff(filteroutput())\n", + "\n", + "\n", + "def test_unit_divisive(filter_outputs):\n", + " for i in range(5):\n", + " o, s = np.random.randint(0,6), np.random.randint(0,7)\n", + " G = spatial_weighting(filter_outputs.shape[-2:])\n", + " N = normcoeff(filter_outputs, o, s)**2\n", + " z = np.mean(N) \n", + " normed_filter = filter_outputs[o,s,:] / np.sqrt(z)\n", + " assert np.allclose(divisive_norm(filter_outputs, o, s), normed_filter)\n", + "\n", + " print(f\"dimension {o},{s} is sound\")\n", + "test_unit_divisive(filteroutput())" ] }, { From c236a18b5df1872b9468dc15a4751ef66392b616 Mon Sep 17 00:00:00 2001 From: RavidHausmann Date: Tue, 25 Jan 2022 14:14:11 +0100 Subject: [PATCH 53/79] docs(normalization): integration tests does not (yet) recreate previous output --- demo/re3.ipynb | 109 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 32 deletions(-) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index 05bc4b5..8b729a5 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 14, "id": "b519b55f", "metadata": {}, "outputs": [], @@ -106,7 +106,7 @@ "\n", "By renaming the remaining sum in the denominator, we can find an abstract formulation of the RMS-Norm as a sequence of linear filters\n", "\n", - " $$\\frac{f_{o^*,s^*}}{\\sqrt{F_{x,y}*(\\sum_{o,s=0}^{5,6}{w_{o,s} f_{o,s})^2}}}=\\frac{f_{o,s}}{\\sqrt{G * N^2}} = \\frac{f_{o,s}}{\\sqrt{G * (W \\cdot f)^2}}$$\n", + " $$\\frac{f_{o^*,s^*}}{\\sqrt{F_{x,y}*(\\sum_{o,s=0}^{5,6}{w_{o,s} f_{o,s})^2}}}=\\frac{f_{o^*,s^*}}{\\sqrt{G * N^2}} = \\frac{f_{o^*,s^*}}{\\sqrt{G * (W \\cdot f)^2}}$$\n", " \n", " Which helps us develop terminology. The entire fraction is called the divisive norm on the filterbank-output $f$. \n", " $G * N^2$ is the local energy, calculated as the convolution of $G$ the spatial weighting, and $N^2$ the Normcoefficient image squared. \n", @@ -117,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 3, "id": "97891411", "metadata": {}, "outputs": [], @@ -190,21 +190,7 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "4807ee1c", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "raw", - "id": "48124545", - "metadata": {}, - "source": [] - }, - { - "cell_type": "code", - "execution_count": 20, + "execution_count": 5, "id": "3a27717c", "metadata": {}, "outputs": [ @@ -212,18 +198,20 @@ "name": "stdout", "output_type": "stream", "text": [ + "unit test normcoeff\n", "Run full 6x7 analysis? [y/n] n\n", "only doing 5 dimensions chosen at random\n", - "dimension 2,0 is sound\n", - "dimension 4,3 is sound\n", - "dimension 4,2 is sound\n", - "dimension 0,1 is sound\n", - "dimension 1,1 is sound\n", - "dimension 4,0 is sound\n", - "dimension 5,2 is sound\n", - "dimension 4,1 is sound\n", + "dimension 2,1 is sound\n", + "dimension 4,5 is sound\n", + "dimension 1,2 is sound\n", + "dimension 5,1 is sound\n", + "dimension 4,5 is sound\n", + "unit test divisive\n", "dimension 0,4 is sound\n", - "dimension 3,3 is sound\n" + "dimension 3,6 is sound\n", + "dimension 1,1 is sound\n", + "dimension 0,5 is sound\n", + "dimension 3,0 is sound\n" ] } ], @@ -257,22 +245,22 @@ " for i in range(5):\n", " o, s = np.random.randint(0,6), np.random.randint(0,7)\n", " for x,y in np.ndindex(filter_outputs.shape[2:]):\n", - " N[o, s, x, y] = np.sum(filter_outputs[o,:,x,y]) / filter_outputs.shape[1]\n", + " N[o, s, x, y] = np.sum(filter_outputs[o,:,x,y]) / filter_outputs.shape[1] # Does W.f properly produce sum_o,s[w_o,s . f_o,s]\n", " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s])\n", "\n", " print(f\"dimension {o},{s} is sound\")\n", " \n", "\n", - " \n", + "print(\"unit test normcoeff\")\n", "test_unit_normcoeff(filteroutput())\n", - "\n", + "print(\"unit test divisive\")\n", "\n", "def test_unit_divisive(filter_outputs):\n", " for i in range(5):\n", " o, s = np.random.randint(0,6), np.random.randint(0,7)\n", " G = spatial_weighting(filter_outputs.shape[-2:])\n", " N = normcoeff(filter_outputs, o, s)**2\n", - " z = np.mean(N) \n", + " z = np.mean(N) # does N*G properly calculate mean(N)?\n", " normed_filter = filter_outputs[o,s,:] / np.sqrt(z)\n", " assert np.allclose(divisive_norm(filter_outputs, o, s), normed_filter)\n", "\n", @@ -280,10 +268,67 @@ "test_unit_divisive(filteroutput())" ] }, + { + "cell_type": "markdown", + "id": "afc4623f", + "metadata": {}, + "source": [ + "So as one can see, all of the steps agree with the mathematical formulation of ODOG. The next step will be, to analyze whether the new formulation agrees not only with the math, but also with the already verified original implementation that is present in the current codebase." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "56300fe0", + "metadata": {}, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_3280/416394366.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;31m#assert np.allclose(output, output_odog_matlab)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 19\u001b[0;31m \u001b[0mtest_integration_odog_reform\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstimulus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/tmp/ipykernel_3280/416394366.py\u001b[0m in \u001b[0;36mtest_integration_odog_reform\u001b[0;34m(output_odog_matlab, stimulus)\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnormalize_outputs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnormalize_multiple_outputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0moutput\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstimulus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mallclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moutput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moutput2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 17\u001b[0m \u001b[0;31m#assert np.allclose(output, output_odog_matlab)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAssertionError\u001b[0m: " + ] + } + ], + "source": [ + "import scipy\n", + "\n", + "def normalize_multiple_outputs(filters_output):\n", + "\n", + " normalized_outputs = np.ones(shape=filters_output.shape)\n", + " for o, s in np.ndindex(filters_output.shape[:2]):\n", + " normalized_outputs[o,s] = divisive_norm(filters_output, o,s)\n", + "\n", + " return normalized_outputs\n", + "\n", + "def test_integration_odog_reform(output_odog_matlab, stimulus):\n", + " model = multyscale.models.ODOG_RHS2007(stimulus.shape, visextent)\n", + " output2 = model.apply(stimulus)\n", + " model.normalize_outputs = normalize_multiple_outputs\n", + " output = model.apply(stimulus)\n", + " assert np.allclose(output, output2)\n", + " #assert np.allclose(output, output_odog_matlab)\n", + " \n", + "test_integration_odog_reform(\"\", stimulus)" + ] + }, + { + "cell_type": "markdown", + "id": "be557be3", + "metadata": {}, + "source": [ + "This code takes a while to complete, and has a very dissatisfying ending. The formulation right now cannot reproduce traditional output" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "cc491b3f", + "id": "a5b77b5f", "metadata": {}, "outputs": [], "source": [] From 3448c66772a856e12da2a3bd0cd2008fef18a4a3 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 17 Feb 2022 15:55:43 +0100 Subject: [PATCH 54/79] docs(normalization): dividing by "images" produces ODOG normalization ..where "image" is 1024x1024 times the RMS --- demo/re3.ipynb | 215 +++++++++++++++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 78 deletions(-) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index 8b729a5..e7f982c 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "b4e3519d", "metadata": {}, "outputs": [], @@ -29,13 +29,13 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "1b0ec8f2", "metadata": {}, "outputs": [], "source": [ "# %% Load example stimulus\n", - "stimulus = np.asarray(Image.open(\"image_source/example_stimulus.png\").convert(\"L\"))\n", + "stimulus = np.asarray(Image.open(\"example_stimulus.png\").convert(\"L\"))\n", "\n", "# %% Parameters of image\n", "# visual extent, same convention as pyplot:\n", @@ -44,16 +44,45 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, + "id": "a1e9446d", + "metadata": {}, + "outputs": [], + "source": [ + "model = multyscale.models.ODOG_RHS2007(shape=stimulus.shape, visextent=visextent)" + ] + }, + { + "cell_type": "code", + "execution_count": null, "id": "e7655c54", "metadata": {}, "outputs": [], "source": [ "# Frontend filterbank of ODOG implementation by Robinson et al. (2007)\n", - "filterbank = multyscale.filterbank.RHS2007(filtershape=stimulus.shape, visextent=visextent)\n", - "\n", + "O, S = model.bank.filters.shape[:2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7e7f6b5", + "metadata": {}, + "outputs": [], + "source": [ "# Filter the example stimulus\n", - "#filters_output = filterbank.apply(stimulus)" + "filters_output = model.bank.apply(stimulus)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "436e1ca2", + "metadata": {}, + "outputs": [], + "source": [ + "# Weight individual filter(outputs) according to spatial size (frequency)\n", + "filters_output = model.weight_outputs(filters_output)" ] }, { @@ -66,22 +95,12 @@ }, { "cell_type": "code", - "execution_count": 14, - "id": "b519b55f", + "execution_count": null, + "id": "7c119c91", "metadata": {}, "outputs": [], "source": [ - "def normalize_outputs(self, filters_output):\n", - " # TODO: docstring\n", - " normalizers = self.normalizers(filters_output)\n", - "\n", - " normalizer_RMS = self.normalizers_to_RMS(normalizers)\n", - "\n", - " normalized_outputs = np.ndarray(filters_output.shape)\n", - " for o, s in np.ndindex(filters_output.shape[:2]):\n", - " normalized_outputs[o, s] = filters_output[o, s] / normalizer_RMS[o, s]\n", - "\n", - " return normalized_outputs\n" + "normalized_outputs = model.normalize_outputs(filters_output)" ] }, { @@ -90,26 +109,108 @@ "metadata": {}, "source": [ "We're comparing this to a new implementation which replaces the math of the original function by a more general form. \n", - "The traditional ODOG formulation looks like this:\n", - "\n", - "$$ODOG: \\frac{f_{o^*,s^*}}{\\sqrt{\\frac{1}{1024^2}\\sum_{x,y=0}^{1024,1024}(\\sum_{o,s=1}^{O, S}{w_{o,s} f_{o,s})^2}}}$$\n", - "where $w_{o,s} = \\begin{cases} \n", - " 1/S & o = o^* \\\\\n", + "The traditional formulation for the normalization schema of ODOG can be formulated as:\n", + "$$ F' = \\frac{f_{o',s',x,y}}{\\sqrt{\\frac{1}{XY}\\sum_{y=1}^{Y} \\sum_{x=1}^{X}(\\sum_{o=1}^{O}\\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}}$$\n", + "where $w_{o', s', o, s} = \\begin{cases} \n", + " 1 & o = o' \\\\\n", " 0 & else\n", " \\end{cases} \n", - "$ \n", + "$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce890e25", + "metadata": {}, + "outputs": [], + "source": [ + "normalizers = model.normalizers(filters_output)\n", + "\n", + "RMS = model.normalizers_to_RMS(normalizers)\n", + "\n", + "normed2 = np.ndarray(filters_output.shape)\n", + "for o,s in np.ndindex(filters_output.shape[:2]):\n", + " normed2[o,s] = filters_output[o,s] / RMS[o,s]\n", + "\n", + "assert(np.allclose(normalized_outputs,normed2))" + ] + }, + { + "cell_type": "markdown", + "id": "66718ec2", + "metadata": {}, + "source": [ + "#### Can also divide by \"images\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c9b348d", + "metadata": {}, + "outputs": [], + "source": [ + "i_RMS = np.tile(RMS.reshape(6,7,1,1),(1,1,1024,1024))\n", + "\n", + "norm_i_outputs = np.ndarray(filters_output.shape)\n", + "for o, s in np.ndindex(filters_output.shape[:2]):\n", + " f = filters_output[o,s,...]\n", + " n = i_RMS[o,s]\n", + " norm_i_outputs[o, s] = f / n\n", + "\n", + "assert(np.allclose(normalized_outputs, norm_i_outputs))" + ] + }, + { + "cell_type": "markdown", + "id": "97f53a2f", + "metadata": {}, + "source": [ "This is equivalent to an equation that implements the averaging in the denominator as a linear filter, that scales the filter aggregate produced by the sum over scales/orientations. This changes the dimensionality of the denominator, but not actually the result of the division, since image by image division is executed pixel-wise\n", "\n", - "$$ODOG: \\frac{f_{o^*,s^*}}{\\sqrt{F_{x,y}*(\\sum_{o,s=0}^{5,6}{w_{o,s} f_{o,s})^2}}}$$\n", + "$$ODOG: \\frac{f_{o^*,s^*}}{\\sqrt{G_{x,y}*(\\sum_{o=1}^{O}\\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}}$$\n", "$w_{o,s}$ same as above, \n", - "$F_{x,y} = \\frac{1}{1024^2}$ everywhere.\n", + "$G_{x,y} = \\frac{1}{XY} = \\frac{1}{1024^2}$ everywhere." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "274ab70c", + "metadata": {}, + "outputs": [], + "source": [ + "filter = np.ones((1024,1024)) / 1024**2\n", + "\n", + "iRMS2 = np.ones(filters_output.shape)\n", + "#for o,s in np.ndindex(filters_output.shape[:2]):\n", + "o=3\n", + "s=4\n", + "# calculate RMS using the filter\n", + "norm = normalizers[o,s].copy()\n", + "norm = norm ** 2\n", + "#mean = multyscale.filters.apply(norm, filter, padval=norm.mean())\n", + "#mean = norm.mean()\n", + "mean = scipy.signal.convolve2d(norm, filter, mode=\"same\", boundary=\"symm\")\n", + "iRMS2[o,s] = iRMS2[o,s]*np.sqrt(mean)\n", "\n", + "plt.imshow(mean)\n", + "mean.shape\n", + "#assert(np.allclose(iRMS2, iRMS))" + ] + }, + { + "cell_type": "markdown", + "id": "c597a480", + "metadata": {}, + "source": [ "By renaming the remaining sum in the denominator, we can find an abstract formulation of the RMS-Norm as a sequence of linear filters\n", "\n", - " $$\\frac{f_{o^*,s^*}}{\\sqrt{F_{x,y}*(\\sum_{o,s=0}^{5,6}{w_{o,s} f_{o,s})^2}}}=\\frac{f_{o^*,s^*}}{\\sqrt{G * N^2}} = \\frac{f_{o^*,s^*}}{\\sqrt{G * (W \\cdot f)^2}}$$\n", + " $$\\frac{f_{o^*,s^*}}{\\sqrt{G_{x,y}*(\\sum_{o=1}^{O}\\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}}=\\frac{f_{o^*,s^*}}{\\sqrt{G * N^2}} = \\frac{f_{o^*,s^*}}{\\sqrt{G * (W \\cdot f)^2}}$$\n", " \n", " Which helps us develop terminology. The entire fraction is called the divisive norm on the filterbank-output $f$. \n", - " $G * N^2$ is the local energy, calculated as the convolution of $G$ the spatial weighting, and $N^2$ the Normcoefficient image squared. \n", + " $G * N^2$ is the (local) energy, calculated as the convolution of $G$ the spatial weighting, and $N^2$ the Normcoefficient image squared. \n", "The Normcoefficient Image can be calculated in turn by computing the Normpool $W$ dotproduct against the same $f$ that we are normalizing.\n", " \n", "An implementation of this formulation can be found here:" @@ -117,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "97891411", "metadata": {}, "outputs": [], @@ -190,31 +291,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "3a27717c", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "unit test normcoeff\n", - "Run full 6x7 analysis? [y/n] n\n", - "only doing 5 dimensions chosen at random\n", - "dimension 2,1 is sound\n", - "dimension 4,5 is sound\n", - "dimension 1,2 is sound\n", - "dimension 5,1 is sound\n", - "dimension 4,5 is sound\n", - "unit test divisive\n", - "dimension 0,4 is sound\n", - "dimension 3,6 is sound\n", - "dimension 1,1 is sound\n", - "dimension 0,5 is sound\n", - "dimension 3,0 is sound\n" - ] - } - ], + "outputs": [], "source": [ "def filtershape():\n", " return (1024,1024)\n", @@ -278,23 +358,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "56300fe0", "metadata": {}, - "outputs": [ - { - "ename": "AssertionError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/tmp/ipykernel_3280/416394366.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;31m#assert np.allclose(output, output_odog_matlab)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 19\u001b[0;31m \u001b[0mtest_integration_odog_reform\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstimulus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/tmp/ipykernel_3280/416394366.py\u001b[0m in \u001b[0;36mtest_integration_odog_reform\u001b[0;34m(output_odog_matlab, stimulus)\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnormalize_outputs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnormalize_multiple_outputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0moutput\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstimulus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mallclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moutput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moutput2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 17\u001b[0m \u001b[0;31m#assert np.allclose(output, output_odog_matlab)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAssertionError\u001b[0m: " - ] - } - ], + "outputs": [], "source": [ "import scipy\n", "\n", @@ -324,14 +391,6 @@ "source": [ "This code takes a while to complete, and has a very dissatisfying ending. The formulation right now cannot reproduce traditional output" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a5b77b5f", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -350,7 +409,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.8.10" } }, "nbformat": 4, From d88529bb69a46dd9ece2542235173f8df6d1ece5 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 17 Feb 2022 16:01:06 +0100 Subject: [PATCH 55/79] docs(normalization): spatial mean (ODOG) should be possible as filter --- demo/re3.ipynb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index e7f982c..afce106 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -136,6 +136,14 @@ "assert(np.allclose(normalized_outputs,normed2))" ] }, + { + "cell_type": "markdown", + "id": "eb2b2064", + "metadata": {}, + "source": [ + "## Implement RMS as a spatial filter" + ] + }, { "cell_type": "markdown", "id": "66718ec2", @@ -174,6 +182,14 @@ "$G_{x,y} = \\frac{1}{XY} = \\frac{1}{1024^2}$ everywhere." ] }, + { + "cell_type": "markdown", + "id": "973d9d93", + "metadata": {}, + "source": [ + "### Implement spatial mean as spatial filter" + ] + }, { "cell_type": "code", "execution_count": null, @@ -269,6 +285,14 @@ " return G" ] }, + { + "cell_type": "markdown", + "id": "601f1394", + "metadata": {}, + "source": [ + "# Regresssion test new formulation" + ] + }, { "cell_type": "markdown", "id": "bdb3590a", From 8e6b2cc1088f8ecbff8cd43ae7d40aa42f792c60 Mon Sep 17 00:00:00 2001 From: RavidHausmann Date: Tue, 22 Feb 2022 15:15:15 +0100 Subject: [PATCH 56/79] docs(normalization): convolution using `multyscale.filters` --- demo/re3.ipynb | 108 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index afce106..1e05a78 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "b4e3519d", "metadata": {}, "outputs": [], @@ -29,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "id": "1b0ec8f2", "metadata": {}, "outputs": [], @@ -44,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "id": "a1e9446d", "metadata": {}, "outputs": [], @@ -54,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "id": "e7655c54", "metadata": {}, "outputs": [], @@ -65,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "id": "c7e7f6b5", "metadata": {}, "outputs": [], @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "id": "436e1ca2", "metadata": {}, "outputs": [], @@ -95,7 +95,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "id": "7c119c91", "metadata": {}, "outputs": [], @@ -120,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "id": "ce890e25", "metadata": {}, "outputs": [], @@ -154,7 +154,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "id": "0c9b348d", "metadata": {}, "outputs": [], @@ -192,28 +192,46 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 40, "id": "274ab70c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", + " 48.6861275],\n", + " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", + " 48.6861275],\n", + " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", + " 48.6861275],\n", + " ...,\n", + " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", + " 48.6861275],\n", + " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", + " 48.6861275],\n", + " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", + " 48.6861275]])" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "filter = np.ones((1024,1024)) / 1024**2\n", + "G = np.ones((1024*2,1024*2)) / 1024**2\n", "\n", "iRMS2 = np.ones(filters_output.shape)\n", - "#for o,s in np.ndindex(filters_output.shape[:2]):\n", "o=3\n", "s=4\n", "# calculate RMS using the filter\n", "norm = normalizers[o,s].copy()\n", "norm = norm ** 2\n", - "#mean = multyscale.filters.apply(norm, filter, padval=norm.mean())\n", - "#mean = norm.mean()\n", - "mean = scipy.signal.convolve2d(norm, filter, mode=\"same\", boundary=\"symm\")\n", + "mean = multyscale.filters.apply(norm, G, padval=0)\n", "iRMS2[o,s] = iRMS2[o,s]*np.sqrt(mean)\n", - "\n", - "plt.imshow(mean)\n", - "mean.shape\n", - "#assert(np.allclose(iRMS2, iRMS))" + "# this replicates the just verified \n", + "assert(np.allclose(iRMS2[o,s], i_RMS[o,s]))" ] }, { @@ -234,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "97891411", "metadata": {}, "outputs": [], @@ -243,7 +261,7 @@ " G = spatial_weighting(filter_outputs.shape[-2:])\n", " N = normcoeff(filter_outputs, o, s)**2\n", " \n", - " z = scipy.signal.convolve(G, N, mode=\"valid\")\n", + " z = multyscale.filters.apply(N, G, padval=0)\n", " normed_filter = filter_outputs[o,s,:] / np.sqrt(z)\n", " return normed_filter\n", "\n", @@ -275,7 +293,7 @@ " ODOG = True\n", " LODOG, FLODOG = False, False\n", " if ODOG:\n", - " G = np.ones(filter_shape) / np.prod(filter_shape)\n", + " G = np.ones([dim*2 for dim in filter_shape]) / np.prod(filter_shape)\n", " \n", " if LODOG:\n", " G = 1 #gaussian(filter_shape, sigma)\n", @@ -315,10 +333,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "3a27717c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "unit test normcoeff\n", + "Run full 6x7 analysis? [y/n] n\n", + "only doing 5 dimensions chosen at random\n", + "dimension 2,6 is sound\n", + "dimension 3,0 is sound\n", + "dimension 4,5 is sound\n", + "dimension 1,3 is sound\n", + "dimension 0,2 is sound\n", + "unit test divisive\n", + "dimension 2,5 is sound\n", + "dimension 0,2 is sound\n", + "dimension 3,0 is sound\n", + "dimension 4,0 is sound\n", + "dimension 5,4 is sound\n" + ] + } + ], "source": [ "def filtershape():\n", " return (1024,1024)\n", @@ -382,10 +421,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "56300fe0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_2666/416394366.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;31m#assert np.allclose(output, output_odog_matlab)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 19\u001b[0;31m \u001b[0mtest_integration_odog_reform\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstimulus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/tmp/ipykernel_2666/416394366.py\u001b[0m in \u001b[0;36mtest_integration_odog_reform\u001b[0;34m(output_odog_matlab, stimulus)\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnormalize_outputs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnormalize_multiple_outputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0moutput\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstimulus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mallclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moutput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moutput2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 17\u001b[0m \u001b[0;31m#assert np.allclose(output, output_odog_matlab)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAssertionError\u001b[0m: " + ] + } + ], "source": [ "import scipy\n", "\n", @@ -433,7 +485,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.7.3" } }, "nbformat": 4, From c46dd6b38fbbb86dd6714fb50a9e3ee93b6e22bf Mon Sep 17 00:00:00 2001 From: RavidHausmann Date: Wed, 23 Feb 2022 12:17:55 +0100 Subject: [PATCH 57/79] docs(normalization): explanatory text towards filter averaging --- demo/re3.ipynb | 51 ++++++++++++++++++-------------------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/demo/re3.ipynb b/demo/re3.ipynb index 1e05a78..f8926cf 100644 --- a/demo/re3.ipynb +++ b/demo/re3.ipynb @@ -192,51 +192,28 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, "id": "274ab70c", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", - " 48.6861275],\n", - " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", - " 48.6861275],\n", - " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", - " 48.6861275],\n", - " ...,\n", - " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", - " 48.6861275],\n", - " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", - " 48.6861275],\n", - " [48.6861275, 48.6861275, 48.6861275, ..., 48.6861275, 48.6861275,\n", - " 48.6861275]])" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "G = np.ones((1024*2,1024*2)) / 1024**2\n", + "G = np.ones((1024*2,1024*2)) / 1024**2 # have doubly sized kernel so that pixel reaches each pixel in the average\n", "\n", - "iRMS2 = np.ones(filters_output.shape)\n", - "o=3\n", + "i_RMS2 = np.ones(filters_output.shape)\n", + "o=3 # choose sample dimension\n", "s=4\n", "# calculate RMS using the filter\n", "norm = normalizers[o,s].copy()\n", "norm = norm ** 2\n", "mean = multyscale.filters.apply(norm, G, padval=0)\n", - "iRMS2[o,s] = iRMS2[o,s]*np.sqrt(mean)\n", - "# this replicates the just verified \n", - "assert(np.allclose(iRMS2[o,s], i_RMS[o,s]))" + "i_RMS2[o,s] = i_RMS2[o,s]*np.sqrt(mean)\n", + "# this replicates the just verified normalizing image\n", + "assert(np.allclose(i_RMS2[o,s], i_RMS[o,s]))" ] }, { "cell_type": "markdown", - "id": "c597a480", + "id": "82795b62", "metadata": {}, "source": [ "By renaming the remaining sum in the denominator, we can find an abstract formulation of the RMS-Norm as a sequence of linear filters\n", @@ -245,7 +222,15 @@ " \n", " Which helps us develop terminology. The entire fraction is called the divisive norm on the filterbank-output $f$. \n", " $G * N^2$ is the (local) energy, calculated as the convolution of $G$ the spatial weighting, and $N^2$ the Normcoefficient image squared. \n", - "The Normcoefficient Image can be calculated in turn by computing the Normpool $W$ dotproduct against the same $f$ that we are normalizing.\n", + "The Normcoefficient Image can be calculated in turn by computing the Normpool $W$ dotproduct against the same $f$ that we are normalizing." + ] + }, + { + "cell_type": "markdown", + "id": "c597a480", + "metadata": {}, + "source": [ + "Crucially the spatial weighting Filter G is $1/XY$ at each of its (2X,2Y) entries to assure that each pixel will take its average from the entire image. This requires that all edges be padded with the value 0, so that no unnecessary information gets included into the averaging\n", " \n", "An implementation of this formulation can be found here:" ] From 1ab5d4ebf0fb35cab95d216f964b22f4289645ba Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 15 Feb 2024 17:09:20 +0100 Subject: [PATCH 58/79] docs(normalization): move & rename as `.py` file --- demo/re3.ipynb => docs/normalization/normalization_generalized.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename demo/re3.ipynb => docs/normalization/normalization_generalized.py (100%) diff --git a/demo/re3.ipynb b/docs/normalization/normalization_generalized.py similarity index 100% rename from demo/re3.ipynb rename to docs/normalization/normalization_generalized.py From 3dd09fda7ea12f6c4322f3eb5ed4bfcdf4c0aedd Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 15 Feb 2024 17:09:47 +0100 Subject: [PATCH 59/79] docs(normalization): remove execution info --- .../normalization_generalized.py | 64 +++++-------------- 1 file changed, 15 insertions(+), 49 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index f8926cf..6ce351c 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "b4e3519d", "metadata": {}, "outputs": [], @@ -29,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "id": "1b0ec8f2", "metadata": {}, "outputs": [], @@ -44,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "a1e9446d", "metadata": {}, "outputs": [], @@ -54,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "id": "e7655c54", "metadata": {}, "outputs": [], @@ -65,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "c7e7f6b5", "metadata": {}, "outputs": [], @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "id": "436e1ca2", "metadata": {}, "outputs": [], @@ -95,7 +95,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "id": "7c119c91", "metadata": {}, "outputs": [], @@ -120,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "id": "ce890e25", "metadata": {}, "outputs": [], @@ -154,7 +154,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "id": "0c9b348d", "metadata": {}, "outputs": [], @@ -192,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "id": "274ab70c", "metadata": {}, "outputs": [], @@ -237,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "97891411", "metadata": {}, "outputs": [], @@ -318,31 +318,10 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "3a27717c", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "unit test normcoeff\n", - "Run full 6x7 analysis? [y/n] n\n", - "only doing 5 dimensions chosen at random\n", - "dimension 2,6 is sound\n", - "dimension 3,0 is sound\n", - "dimension 4,5 is sound\n", - "dimension 1,3 is sound\n", - "dimension 0,2 is sound\n", - "unit test divisive\n", - "dimension 2,5 is sound\n", - "dimension 0,2 is sound\n", - "dimension 3,0 is sound\n", - "dimension 4,0 is sound\n", - "dimension 5,4 is sound\n" - ] - } - ], + "outputs": [], "source": [ "def filtershape():\n", " return (1024,1024)\n", @@ -406,23 +385,10 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "56300fe0", "metadata": {}, - "outputs": [ - { - "ename": "AssertionError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/tmp/ipykernel_2666/416394366.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;31m#assert np.allclose(output, output_odog_matlab)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 19\u001b[0;31m \u001b[0mtest_integration_odog_reform\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstimulus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/tmp/ipykernel_2666/416394366.py\u001b[0m in \u001b[0;36mtest_integration_odog_reform\u001b[0;34m(output_odog_matlab, stimulus)\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnormalize_outputs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnormalize_multiple_outputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0moutput\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstimulus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mallclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moutput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moutput2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 17\u001b[0m \u001b[0;31m#assert np.allclose(output, output_odog_matlab)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAssertionError\u001b[0m: " - ] - } - ], + "outputs": [], "source": [ "import scipy\n", "\n", From 5e1c6cad1bf7fb311e74d7c9cb14fa5ec3bb134d Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Fri, 2 Feb 2024 12:53:50 +0100 Subject: [PATCH 60/79] docs(normalization): cleanup generalization of normalization step as `.py`-script --- .../normalization_generalized.py | 842 +++++++++--------- 1 file changed, 398 insertions(+), 444 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index 6ce351c..5ab11b3 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -1,444 +1,398 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "dcbfab0e", - "metadata": {}, - "source": [ - "# Rewrite Reuse Refactor\n", - "## Reproducing Unit Tests using refactored ODOG formulation\n", - "This Notebook introduces a new way of formulating the normalization step of the ODOG family of Models, and shows that this formulation is numerically equivalent to the original paper implementation, to establish continuity" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b4e3519d", - "metadata": {}, - "outputs": [], - "source": [ - "# Third party libraries\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from PIL import Image\n", - "import scipy\n", - "\n", - "# Import local module\n", - "import multyscale" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1b0ec8f2", - "metadata": {}, - "outputs": [], - "source": [ - "# %% Load example stimulus\n", - "stimulus = np.asarray(Image.open(\"example_stimulus.png\").convert(\"L\"))\n", - "\n", - "# %% Parameters of image\n", - "# visual extent, same convention as pyplot:\n", - "visextent = (-16, 16, -16, 16)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1e9446d", - "metadata": {}, - "outputs": [], - "source": [ - "model = multyscale.models.ODOG_RHS2007(shape=stimulus.shape, visextent=visextent)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e7655c54", - "metadata": {}, - "outputs": [], - "source": [ - "# Frontend filterbank of ODOG implementation by Robinson et al. (2007)\n", - "O, S = model.bank.filters.shape[:2]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c7e7f6b5", - "metadata": {}, - "outputs": [], - "source": [ - "# Filter the example stimulus\n", - "filters_output = model.bank.apply(stimulus)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "436e1ca2", - "metadata": {}, - "outputs": [], - "source": [ - "# Weight individual filter(outputs) according to spatial size (frequency)\n", - "filters_output = model.weight_outputs(filters_output)" - ] - }, - { - "cell_type": "markdown", - "id": "73904479", - "metadata": {}, - "source": [ - "This preamble defines a filterbank output which is not already normalized by any function. The Traditional ODOG normalization function is multyscale/models.py ODOG, shown below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7c119c91", - "metadata": {}, - "outputs": [], - "source": [ - "normalized_outputs = model.normalize_outputs(filters_output)" - ] - }, - { - "cell_type": "markdown", - "id": "a46964ec", - "metadata": {}, - "source": [ - "We're comparing this to a new implementation which replaces the math of the original function by a more general form. \n", - "The traditional formulation for the normalization schema of ODOG can be formulated as:\n", - "$$ F' = \\frac{f_{o',s',x,y}}{\\sqrt{\\frac{1}{XY}\\sum_{y=1}^{Y} \\sum_{x=1}^{X}(\\sum_{o=1}^{O}\\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}}$$\n", - "where $w_{o', s', o, s} = \\begin{cases} \n", - " 1 & o = o' \\\\\n", - " 0 & else\n", - " \\end{cases} \n", - "$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce890e25", - "metadata": {}, - "outputs": [], - "source": [ - "normalizers = model.normalizers(filters_output)\n", - "\n", - "RMS = model.normalizers_to_RMS(normalizers)\n", - "\n", - "normed2 = np.ndarray(filters_output.shape)\n", - "for o,s in np.ndindex(filters_output.shape[:2]):\n", - " normed2[o,s] = filters_output[o,s] / RMS[o,s]\n", - "\n", - "assert(np.allclose(normalized_outputs,normed2))" - ] - }, - { - "cell_type": "markdown", - "id": "eb2b2064", - "metadata": {}, - "source": [ - "## Implement RMS as a spatial filter" - ] - }, - { - "cell_type": "markdown", - "id": "66718ec2", - "metadata": {}, - "source": [ - "#### Can also divide by \"images\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0c9b348d", - "metadata": {}, - "outputs": [], - "source": [ - "i_RMS = np.tile(RMS.reshape(6,7,1,1),(1,1,1024,1024))\n", - "\n", - "norm_i_outputs = np.ndarray(filters_output.shape)\n", - "for o, s in np.ndindex(filters_output.shape[:2]):\n", - " f = filters_output[o,s,...]\n", - " n = i_RMS[o,s]\n", - " norm_i_outputs[o, s] = f / n\n", - "\n", - "assert(np.allclose(normalized_outputs, norm_i_outputs))" - ] - }, - { - "cell_type": "markdown", - "id": "97f53a2f", - "metadata": {}, - "source": [ - "This is equivalent to an equation that implements the averaging in the denominator as a linear filter, that scales the filter aggregate produced by the sum over scales/orientations. This changes the dimensionality of the denominator, but not actually the result of the division, since image by image division is executed pixel-wise\n", - "\n", - "$$ODOG: \\frac{f_{o^*,s^*}}{\\sqrt{G_{x,y}*(\\sum_{o=1}^{O}\\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}}$$\n", - "$w_{o,s}$ same as above, \n", - "$G_{x,y} = \\frac{1}{XY} = \\frac{1}{1024^2}$ everywhere." - ] - }, - { - "cell_type": "markdown", - "id": "973d9d93", - "metadata": {}, - "source": [ - "### Implement spatial mean as spatial filter" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "274ab70c", - "metadata": {}, - "outputs": [], - "source": [ - "G = np.ones((1024*2,1024*2)) / 1024**2 # have doubly sized kernel so that pixel reaches each pixel in the average\n", - "\n", - "i_RMS2 = np.ones(filters_output.shape)\n", - "o=3 # choose sample dimension\n", - "s=4\n", - "# calculate RMS using the filter\n", - "norm = normalizers[o,s].copy()\n", - "norm = norm ** 2\n", - "mean = multyscale.filters.apply(norm, G, padval=0)\n", - "i_RMS2[o,s] = i_RMS2[o,s]*np.sqrt(mean)\n", - "# this replicates the just verified normalizing image\n", - "assert(np.allclose(i_RMS2[o,s], i_RMS[o,s]))" - ] - }, - { - "cell_type": "markdown", - "id": "82795b62", - "metadata": {}, - "source": [ - "By renaming the remaining sum in the denominator, we can find an abstract formulation of the RMS-Norm as a sequence of linear filters\n", - "\n", - " $$\\frac{f_{o^*,s^*}}{\\sqrt{G_{x,y}*(\\sum_{o=1}^{O}\\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}}=\\frac{f_{o^*,s^*}}{\\sqrt{G * N^2}} = \\frac{f_{o^*,s^*}}{\\sqrt{G * (W \\cdot f)^2}}$$\n", - " \n", - " Which helps us develop terminology. The entire fraction is called the divisive norm on the filterbank-output $f$. \n", - " $G * N^2$ is the (local) energy, calculated as the convolution of $G$ the spatial weighting, and $N^2$ the Normcoefficient image squared. \n", - "The Normcoefficient Image can be calculated in turn by computing the Normpool $W$ dotproduct against the same $f$ that we are normalizing." - ] - }, - { - "cell_type": "markdown", - "id": "c597a480", - "metadata": {}, - "source": [ - "Crucially the spatial weighting Filter G is $1/XY$ at each of its (2X,2Y) entries to assure that each pixel will take its average from the entire image. This requires that all edges be padded with the value 0, so that no unnecessary information gets included into the averaging\n", - " \n", - "An implementation of this formulation can be found here:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "97891411", - "metadata": {}, - "outputs": [], - "source": [ - "def divisive_norm(filter_outputs, o, s):\n", - " G = spatial_weighting(filter_outputs.shape[-2:])\n", - " N = normcoeff(filter_outputs, o, s)**2\n", - " \n", - " z = multyscale.filters.apply(N, G, padval=0)\n", - " normed_filter = filter_outputs[o,s,:] / np.sqrt(z)\n", - " return normed_filter\n", - "\n", - "def normcoeff(filter_outputs, o_0, s_0): \n", - " w = normpool(filter_outputs.shape[0],filter_outputs.shape[1], o_0, s_0)\n", - " coeffs = np.tensordot(filter_outputs, w, axes=([0, 1], [0, 1]))\n", - " return coeffs\n", - "\n", - "def normpool(O, S, o_0, s_0): # w_{o,s}\n", - " ODOG = True\n", - " LODOG, FLODOG = False, False\n", - " w = np.ones(shape=(O,S))\n", - " for o in range(O):\n", - " for s in range(S):\n", - " if ODOG or LODOG:\n", - " if o==o_0:\n", - " w[o,s] = 1/S\n", - " else:\n", - " w[o,s] = 0\n", - "\n", - " if FLODOG:\n", - " if o==o_0:\n", - " w[o,s] = 1 #gaussian(s-s_0)\n", - " else:\n", - " w[o,s] = 0\n", - " return w\n", - "\n", - "def spatial_weighting(filter_shape):\n", - " ODOG = True\n", - " LODOG, FLODOG = False, False\n", - " if ODOG:\n", - " G = np.ones([dim*2 for dim in filter_shape]) / np.prod(filter_shape)\n", - " \n", - " if LODOG:\n", - " G = 1 #gaussian(filter_shape, sigma)\n", - "\n", - " if FLODOG:\n", - " G = 1 #gaussian(filter_shape, sigma=k*s)\n", - " return G" - ] - }, - { - "cell_type": "markdown", - "id": "601f1394", - "metadata": {}, - "source": [ - "# Regresssion test new formulation" - ] - }, - { - "cell_type": "markdown", - "id": "bdb3590a", - "metadata": {}, - "source": [ - "Our goal is now to establish numerical equivalence between the traditional formulation and the result of the newly reformatted and mathematically motivated code\n", - "\n", - "The original implementation is already tested against the matlab implementation of RHS_2007, so we know it is sound.\n", - " \n", - "We will use this notebook to figure out: 1) if all of the units of our new implementation act as expected and 2) if the integration of those units is again comparable to RHS_2007" - ] - }, - { - "cell_type": "markdown", - "id": "309a9c28", - "metadata": {}, - "source": [ - "Note that in the module, all of the below functions will appear in multyscale/test, separated into fixtures and tests." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a27717c", - "metadata": {}, - "outputs": [], - "source": [ - "def filtershape():\n", - " return (1024,1024)\n", - "\n", - "def test_unit_spatial_weighting(filter_shape):\n", - " G = spatial_weighting(filter_shape)\n", - " assert np.allclose(G, 1/np.prod(filter_shape))\n", - " \n", - "test_unit_spatial_weighting(filtershape())\n", - "\n", - "def filteroutput():\n", - " return np.random.random(size=(6,7,1024,1024))\n", - "\n", - "\n", - "def test_unit_normcoeff(filter_outputs):\n", - " # use sum over scales formulation instead of linear filter\n", - " if input(\"Run full 6x7 analysis? [y/n] \")==\"y\":\n", - " N = np.zeros(shape=(6,7,1024,1024))\n", - " for o, s in np.ndindex(filter_outputs.shape[:2]):\n", - " for x,y in np.ndindex(filter_outputs.shape[2:]):\n", - " N[o, s, x, y] = np.sum(filter_outputs[o,:,x,y]) / filter_outputs.shape[1]\n", - " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s])\n", - "\n", - " print(f\"dimension {o},{s} is sound\")\n", - " else:\n", - " print(\"only doing 5 dimensions chosen at random\")\n", - " N = np.zeros(shape=(6,7,1024,1024))\n", - " for i in range(5):\n", - " o, s = np.random.randint(0,6), np.random.randint(0,7)\n", - " for x,y in np.ndindex(filter_outputs.shape[2:]):\n", - " N[o, s, x, y] = np.sum(filter_outputs[o,:,x,y]) / filter_outputs.shape[1] # Does W.f properly produce sum_o,s[w_o,s . f_o,s]\n", - " assert np.allclose(normcoeff(filter_outputs, o, s), N[o, s])\n", - "\n", - " print(f\"dimension {o},{s} is sound\")\n", - " \n", - "\n", - "print(\"unit test normcoeff\")\n", - "test_unit_normcoeff(filteroutput())\n", - "print(\"unit test divisive\")\n", - "\n", - "def test_unit_divisive(filter_outputs):\n", - " for i in range(5):\n", - " o, s = np.random.randint(0,6), np.random.randint(0,7)\n", - " G = spatial_weighting(filter_outputs.shape[-2:])\n", - " N = normcoeff(filter_outputs, o, s)**2\n", - " z = np.mean(N) # does N*G properly calculate mean(N)?\n", - " normed_filter = filter_outputs[o,s,:] / np.sqrt(z)\n", - " assert np.allclose(divisive_norm(filter_outputs, o, s), normed_filter)\n", - "\n", - " print(f\"dimension {o},{s} is sound\")\n", - "test_unit_divisive(filteroutput())" - ] - }, - { - "cell_type": "markdown", - "id": "afc4623f", - "metadata": {}, - "source": [ - "So as one can see, all of the steps agree with the mathematical formulation of ODOG. The next step will be, to analyze whether the new formulation agrees not only with the math, but also with the already verified original implementation that is present in the current codebase." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "56300fe0", - "metadata": {}, - "outputs": [], - "source": [ - "import scipy\n", - "\n", - "def normalize_multiple_outputs(filters_output):\n", - "\n", - " normalized_outputs = np.ones(shape=filters_output.shape)\n", - " for o, s in np.ndindex(filters_output.shape[:2]):\n", - " normalized_outputs[o,s] = divisive_norm(filters_output, o,s)\n", - "\n", - " return normalized_outputs\n", - "\n", - "def test_integration_odog_reform(output_odog_matlab, stimulus):\n", - " model = multyscale.models.ODOG_RHS2007(stimulus.shape, visextent)\n", - " output2 = model.apply(stimulus)\n", - " model.normalize_outputs = normalize_multiple_outputs\n", - " output = model.apply(stimulus)\n", - " assert np.allclose(output, output2)\n", - " #assert np.allclose(output, output_odog_matlab)\n", - " \n", - "test_integration_odog_reform(\"\", stimulus)" - ] - }, - { - "cell_type": "markdown", - "id": "be557be3", - "metadata": {}, - "source": [ - "This code takes a while to complete, and has a very dissatisfying ending. The formulation right now cannot reproduce traditional output" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} +# %% [markdown] +# # Generalized -ODOG normalization +# Here we introduce a generalized way of formulating the normalization step of the -ODOG family of models, +# such that the F-, L-, and ODOG models differ only in parameterization of this step, +# and show that this formulation is numerically equivalent to the original formulation. + +# %% +# Third party libraries +import numpy as np +from PIL import Image + +# Import local module +import multyscale + +# %% [markdown] +# ## Frontend + +# %% +# %% Load example stimulus +stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) + +# %% Parameters of image +# visual extent, same convention as pyplot: +visextent = (-16, 16, -16, 16) + +# %% +ODOG = multyscale.models.ODOG_RHS2007(shape=stimulus.shape, visextent=visextent) + +# %% +# Frontend filterbank of ODOG implementation by Robinson et al. (2007) +O, S = ODOG.bank.filters.shape[:2] + +# %% +# Filter the example stimulus +filters_output = ODOG.bank.apply(stimulus) + +# %% +# Weight individual filter(outputs) according to spatial size (frequency) +filters_output = ODOG.weight_outputs(filters_output) + +# %% [markdown] +# This preamble defines a filterbank output which is not already normalized by any function. + +# %% [markdown] +# ## Normalization steps + +# %% [markdown] +# ### ODOG +# The ODOG normalization step can be formulated as: +# $$ +# f'_{o',s',x,y} = \frac{f_{o',s',x,y}} +# {\sqrt{\frac{1}{XY}\sum_{y=1}^{Y} \sum_{x=1}^{X}n_{o',s'}^2}} +# $$ +# where $w_{o', s', o, s} = \begin{cases} +# 1 & o = o' \\ +# 0 & else +# \end{cases}$ +# and +# $$ +# n_{o',s'} = \sum_{o=1}^{O}\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y}} +# $$ + +# %% +ODOG_outputs = ODOG.normalize_outputs(filters_output) + +# %% [markdown] +# #### Can also divide by 2D "image" arrays +# In the original formulation of the ODOG normalization step, +# the denominator is a single scalar +# -- the root mean square of the normalizer image $n_{o',s'}$ + +# %% +ODOG_normalizers = ODOG.normalizers(filters_output) +ODOG_RMS = ODOG.normalizers_to_RMS(ODOG_normalizers) +print(ODOG_RMS.shape) + +# %% [markdown] +# However, it makes conceptual more sense +# to have a denominator that is the same 2 dimensions +# as the filter that is being normalized. + +# %% +i_RMS = np.tile(ODOG_RMS.reshape(6, 7, 1, 1), (1, 1, 1024, 1024)) + +norm_i_outputs = np.ndarray(filters_output.shape) +for o, s in np.ndindex(filters_output.shape[:2]): + norm_i_outputs[o, s] = filters_output[o, s, ...] / i_RMS[o, s] + +assert np.allclose(ODOG_outputs, norm_i_outputs) + +# %% [markdown] +# This is equivalent to implementing the averaging in the denominator +# as a multiplication with a matrix where each component is the RMS of the normalizer image. +# +# $$ +# f'_{o',s'} = \frac{f_{o',s'}}{\sqrt{\mathbf{M}}} +# $$ +# where +# $$ +# m_{x,y} = \frac{1}{XY}\sum_{y=1}^{Y} \sum_{x=1}^{X}n_{o',s'}^2 +# $$ +# +# This changes the dimensionality of the denominator, +# but not actually the result of the division, +# since image-by-image division is executed pixel-wise. + +# %% [markdown] +# ### (F)LODOG +# The (F)LODOG normalization step can be formulated as: +# $$ +# f'_{o',s'} = \frac{f_{o',s'}} +# {\sqrt{G(\sigma) * (\sum_{o=1}^{O}\sum_{s=1}^{S} w_{o',s',o,s}f_{o,s})^2}} +# $$ +# where $w_{o', s', o, s} = \begin{cases} +# 1 & o = o' \\ +# 0 & else +# \end{cases}$ +# and $G({\sigma}) * ...$ means convolution with a 2D ($X,Y$) kernel +# -- in this case, a Gaussian with standard deviation $\sigma$ in both directions. +# +# NOTE: FLODOG and LODOG differ just in the _values_ for $\sigma$ and $\mathbf{W}$ + +# %% +LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +FLODOG = multyscale.models.FLODOG_RHS2007(shape=stimulus.shape, visextent=visextent) + +# %% +LODOG_outputs = LODOG.normalize_outputs(filters_output) +FLODOG_outputs = FLODOG.normalize_outputs(filters_output) + +# %% [markdown] +# ### Difference(s) +# The difference between (F)LODOG and ODOG normalization is purely in the denominator $\mathbf{\sqrt{M}}$: +# $$ +# \begin{aligned} +# ODOG: \mathbf{M} &= \frac{1}{XY}\sum_{y=1}^{Y} \sum_{x=1}^{X} n_{o',s'}^2 \\ +# +# (F)LODOG: \mathbf{M} &= G(\sigma) * n_{o',s'}^2 +# \end{aligned} +# $$ + +# %% [markdown] +# For ODOG and LODOG, the weights $\mathbf{W}$ are identical, +# and therefore so are the normalizer images $\mathbf{N}$. + +# %% +assert np.allclose(ODOG.normalizers(filters_output), LODOG.normalizers(filters_output)) + +# %% [markdown] +# ### Implement global image averaging as a spatial filter + +# %% [markdown] +# The question then is whether global averaging +# $$ +# \mathbf{M} = \frac{1}{XY}\sum_{y=1}^{Y}\sum_{x=1}^{X} ... +# $$ +# can be reformulated as a convolution with 2D kernel +# $$ +# \mathbf{A}(..) * ... +# $$ +# A convolution is essentially a repeated weighted sum, +# where the weight is the value in the filter +# and this sum is repeated for centering the filter +# on each pixel in the input image. +# If we can construct a kernel $\mathbf{A}$ ensures that for every pixel +# it weights all pixels in the input image by $\frac{1}{XY}$, +# then the output of the convolution is simply the global image average. + +# %% +# have doubly sized kernel so that pixel reaches each pixel in the average +A = np.ones((1024 * 2, 1024 * 2)) / 1024**2 + +img = filters_output[3, 4] +mean_filtered = multyscale.filters.apply(img, A, padval=0) + +assert np.allclose(mean_filtered, img.mean()) + +# %% [markdown] +# With this kind of spatial filtering, +# we run into the question of how to avoid edge artefects. +# For the pixels on the edges of the image, +# the kernel, when centered on one of those pixels, +# will extend beyond the edges of the image. +# In our case, this causes 2 problems: +# - if the kernel were the same size as the input image, +# then some pixels on the _other_ edge of the image +# will no longer fall in our image. +# Thus the spatial kernel $\mathbf{A}$ has $(2X,2Y)$ entries +# to assure that each pixel will take its average from the entire image. +# - what to do at the edges, to ensure that the filter has values to filter? +# We pad all the edges with the value $0$, +# so that no unnecessary information gets included into the averaging. + +# %% [markdown] +# The spatial averaging step in the ODOG normalization then becomes: +# $$ +# \mathbf{M} = \mathbf{A} * (...) +# $$ +# and thus the RMS step: +# $$ +# \sqrt{\mathbf{M}} = \sqrt{\mathbf{A} * n_{o',s'}^2} +# $$ + +# %% +A = np.ones((1024 * 2, 1024 * 2)) / 1024**2 + +# calculate RMS using the kernel +i_RMS2 = np.ndarray(filters_output.shape) +for o, s in np.ndindex(filters_output.shape[:2]): + norm = ODOG_normalizers[o, s, ...] ** 2 + mean = multyscale.filters.apply(norm, A, padval=0) + i_RMS2[o, s] = np.sqrt(mean) + +# this replicates the just verified normalizing image +assert np.allclose(i_RMS2, i_RMS) + +# %% [markdown] +# Thus, we can now reformulate the ODOG normalization as: +# $$ +# f'_{o',s'} = \frac{f_{o',s'}} +# {\sqrt{\mathbf{A} * (\sum_{o=1}^{O}\sum_{s=1}^{S} w_{o',s',o,s}f_{o,s})^2}} +# $$ +# where $w_{o', s', o, s} = \begin{cases} +# 1 & o = o' \\ +# 0 & else +# \end{cases}$ +# and $\mathbf{A} * ...$ means the convolution with our 2D ($X,Y$) kernel +# that implements global averaging. + +# %% +A = np.ones((1024 * 2, 1024 * 2)) / 1024**2 + +normed = np.ndarray(filters_output.shape) +for o, s in np.ndindex(filters_output.shape[:2]): + norm = ODOG_normalizers[o, s, ...] ** 2 + mean = multyscale.filters.apply(norm, A, padval=0) + normed[o, s] = filters_output[ + o, + s, + ] / np.sqrt(mean) + +assert np.allclose(normed, ODOG_outputs) + +# %% [markdown] +# ## Generalization + +# %% [markdown] +# The -ODOG normalization step can now generally be formulated as: +# $$ f'_{o',s'} = +# \frac{f_{o',s'}} +# {\sqrt{\mathbf{A}*(\sum_{o=1}^{O}\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}} +# $$ +# where +# $$ +# w_{o', s', o, s} = +# \begin{cases} +# 1 & o = o' & (L)ODOG \\ +# G(\sigma, s', s) & o = o' & FLODOG \\ +# 0 & else +# \end{cases} +# $$ +# and +# $$ +# \mathbf{A} = +# \begin{cases} +# \frac{1}{XY} & ODOG \\ +# G(\sigma) & LODOG \\ +# G(\sigma * s') & FLODOG +# \end{cases} +# $$ + +# %% [markdown] +# This identifies two parts in which the three models differ, +# which we name as follows: +# - the _normalization weights_ $\mathbf{W}$ +# - the _spatial averaging kernel_ $\mathbf{A}$ +# +# These are both featured in the _normalization coefficient_ +# that is the denominator of the _divisive normalization_ function. + +# %% [markdown] +# +# An implementation of this formulation can be found here: + + +# %% +def divisive_normalization(filter_output, norm_coeff): + return filter_output / norm_coeff + + +def norm_coeff(normalizer, spatial_kernel): + norm = normalizer**2 + spatial_average = multyscale.filters.apply(norm, spatial_kernel, padval=0) + coeff = np.sqrt(spatial_average + 1e-6) + return coeff + + +def spatial_kernel_ODOG(x, y): + return np.ones([dim * 2 for dim in x.shape]) / np.prod(x.shape) + + +def spatial_kernel_LODOG(x, y, sigmas=[0, 0]): + kernel = multyscale.filters.gaussian2d(x, y, sigmas) + kernel /= kernel.sum() + return kernel + + +# %% [markdown] +# ## Testing + +# %% [markdown] +# ### Unit tests + +# %% +ODOG_kernel = spatial_kernel_ODOG(ODOG.bank.x, ODOG.bank.y) +assert np.all(ODOG_kernel == A) + +# %% +LODOG_kernel = spatial_kernel_LODOG( + ODOG.bank.x, ODOG.bank.y, sigmas=(LODOG.window_sigma, LODOG.window_sigma) +) +LODOG_kernels = multyscale.normalization.spatial_avg_windows_gaussian( + ODOG.bank.x, ODOG.bank.y, LODOG.window_sigmas +) + +assert np.allclose(LODOG_kernel, LODOG_kernels[0, 0]) + +# %% +img = filters_output[3, 4] +kernel = spatial_kernel_ODOG(ODOG.bank.x, ODOG.bank.y) +mean_filtered = multyscale.filters.apply(img, A, padval=0) + +assert np.allclose(mean_filtered, img.mean()) + +# %% +coeffs = np.ndarray(filters_output.shape) +for o, s in np.ndindex(filters_output.shape[:2]): + coeffs[o, s, ...] = norm_coeff(ODOG_normalizers[o, s, ...], A) + +assert np.allclose(coeffs, i_RMS2) + +# %% [markdown] +# ### ODOG + +# %% +ODOG_normalizers = ODOG.normalizers(filters_output) + +ODOG_outputs = ODOG.normalize_outputs(filters_output) + +kernel = spatial_kernel_ODOG(ODOG.bank.x, ODOG.bank.y) + +new_normed = np.ndarray(filters_output.shape) +for o_prime, s_prime in np.ndindex(filters_output.shape[:2]): + normalizer = ODOG_normalizers[o_prime, s_prime] + coeff = norm_coeff(normalizer, kernel) + new_normed[o_prime, s_prime] = divisive_normalization(filters_output[o_prime, s_prime], coeff) + +assert np.allclose(new_normed, ODOG_outputs) + +# %% [markdown] +# ### LODOG + +# %% +ODOG_normalizers = ODOG.normalizers(filters_output) + +LODOG_outputs = LODOG.normalize_outputs(filters_output) + +kernel = spatial_kernel_LODOG( + ODOG.bank.x, ODOG.bank.y, sigmas=(LODOG.window_sigma, LODOG.window_sigma) +) + +new_normed = np.ndarray(filters_output.shape) +for o_prime, s_prime in np.ndindex(filters_output.shape[:2]): + normalizer = ODOG_normalizers[o_prime, s_prime] + coeff = norm_coeff(normalizer, kernel) + new_normed[o_prime, s_prime] = divisive_normalization(filters_output[o_prime, s_prime], coeff) + +assert np.allclose(new_normed, LODOG_outputs) + +# %% [markdown] +# ### FLODOG + +# %% +FLODOG_normalizers = FLODOG.normalizers(filters_output) + +FLODOG_outputs = FLODOG.normalize_outputs(filters_output) + +kernels = multyscale.normalization.spatial_avg_windows_gaussian( + ODOG.bank.x, ODOG.bank.y, FLODOG.window_sigmas +) + +new_normed = np.ndarray(filters_output.shape) +for o_prime, s_prime in np.ndindex(filters_output.shape[:2]): + normalizer = FLODOG_normalizers[o_prime, s_prime] + coeff = norm_coeff(normalizer, kernels[o_prime, s_prime]) + 1e-6 + new_normed[o_prime, s_prime] = divisive_normalization(filters_output[o_prime, s_prime], coeff) + +assert np.allclose(new_normed, FLODOG_outputs) From 1a940496e7cfff92757eece7c742ec567a8c8470 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 6 Feb 2024 13:07:03 +0100 Subject: [PATCH 61/79] docs(normalization): introduction to generalization Brings more in line with other doc pages --- .../normalization_generalized.py | 112 ++++++++++++++---- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index 5ab11b3..e1d78ee 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -1,45 +1,107 @@ # %% [markdown] # # Generalized -ODOG normalization -# Here we introduce a generalized way of formulating the normalization step of the -ODOG family of models, -# such that the F-, L-, and ODOG models differ only in parameterization of this step, -# and show that this formulation is numerically equivalent to the original formulation. +# Here we introduce a generalized formulation of the normalization step +# of the -ODOG family of models. +# In this generalized formulation, +# the F-, L-, and ODOG models differ only in parameterization of this step. +# We also show that this formulation is numerically equivalent to the original formulation. +# This is pointed to, and inspired by, a note and Figure 4e +# in Robinson, Hammon & de Sa (2007) -# %% +# %% [markdown] +# ## Generalizing normalization steps +# Generally, the -ODOG normalization step consists of three parts: +# +# 1. The _normalizing coefficients_: weighted combinations of all filter outputs +# 2. Energy-calculating (as spatial averaging) of the normalizing coefficients +# 3. Divisive normalization, where a filter output is divided by the energy (2) +# of its normalizing coefficient (1) +# +# However, the different (F)(L)ODOG models differ in their exact implementation, +# particularly in steps (1) and (2). +# Initially, these differences may seem structural, +# different models using different calculations. +# Yet, as we show here, these can be expressed as parametric differences: +# the same structural components for each step, +# but with different parameter values. +# +# `multyscale` thus implements a _generalized_ version of the -ODOG normalization, +# such that the individual models are different parameterizations of this generalization. +# This formulation is intriguing because +# it exposes the possibility of other parameter values, +# but also because other forms of normalization +# (from other models of early vision) +# may map onto this or an even more generalized version. + +# %% Setup # Third party libraries +import matplotlib.pyplot as plt import numpy as np -from PIL import Image # Import local module import multyscale # %% [markdown] # ## Frontend +# The three models differ in their normalization step, +# but share the same filterbank frontend, +# so it's only necessary to apply this bank +# (and weight the filteroutputs) +# once. -# %% -# %% Load example stimulus -stimulus = np.asarray(Image.open("example_stimulus.png").convert("L")) - -# %% Parameters of image -# visual extent, same convention as pyplot: -visextent = (-16, 16, -16, 16) +# %% [markdown] +# The example stimulus used for this exploration +# is a version of White's (1979) classic illusion, +# as also used by Robinson, Hammon, & de Sa (2007) as `WE_thick`. +# +# This stimulus is provided here +# as an NumPy `.npy` file, +# so it can be loaded in directly as a NumPy ndarray. +# +# The image of $1024 \times 1024$ pixels represent $32° \times 32°$ of the visual field; +# if centered, the visual extent of this stimulus subtends +# from $-16°$ on the left, to $16°$ on the right, +# and from $-16°$ on top, to $16°$ on the bottom. -# %% -ODOG = multyscale.models.ODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +# %% Load example stimulus +stimulus = np.load("example_stimulus.npy") -# %% -# Frontend filterbank of ODOG implementation by Robinson et al. (2007) -O, S = ODOG.bank.filters.shape[:2] +# visual extent, in degrees visual angle, +# same convention as pyplot (left, right, top, bottom): +# +# NOTE that Robinson, Hammon, & de Sa (2007) actually implement +# a visual extent slightly smaller: (1023/32) +visextent = tuple(np.asarray((-0.5, 0.5, -0.5, 0.5)) * (1023 / 32)) + +# Visualise +plt.subplot(1, 2, 1) +plt.imshow(stimulus, cmap="gray", extent=visextent) +plt.axhline(y=0, color="black", dashes=(1, 1)) +plt.subplot(1, 2, 2) +plt.plot( + np.linspace(visextent[2], visextent[3], stimulus.shape[1])[256:768], + stimulus[512, 256:768], + color="black", +) +plt.show() -# %% -# Filter the example stimulus -filters_output = ODOG.bank.apply(stimulus) +# %% [markdown] +# In the stimulus image on the left, the left gray patch appears brighter than the right gray patch. +# On the right, the pixel intensity/gray scale values along the horizontal cut +# (indicated by the dashed line in the image) are shown. +# These reveal that, in fact, the two gray patches are identical in their physical intensity. -# %% -# Weight individual filter(outputs) according to spatial size (frequency) -filters_output = ODOG.weight_outputs(filters_output) +# %% Initialize models +ODOG = multyscale.models.ODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +FLODOG = multyscale.models.FLODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +assert np.array_equal(LODOG.bank.filters, ODOG.bank.filters) +assert np.array_equal(FLODOG.bank.filters, LODOG.bank.filters) -# %% [markdown] -# This preamble defines a filterbank output which is not already normalized by any function. +# %% Apply filterbank +# And weight outputs +filters_output = FLODOG.bank.apply(stimulus) +filters_output = FLODOG.weight_outputs(filters_output) # %% [markdown] # ## Normalization steps From d0e3659051ddf7821367423d5a6ae524a80a03e5 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 6 Feb 2024 13:29:29 +0100 Subject: [PATCH 62/79] docs(normalization): start with formalization of normalization step --- .../normalization_generalized.py | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index e1d78ee..4175d51 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -104,7 +104,48 @@ filters_output = FLODOG.weight_outputs(filters_output) # %% [markdown] -# ## Normalization steps +# ## Normalization +# Generally, the -ODOG normalization step consists of three parts: +# +# 1. The _normalizing coefficients_: weighted combinations of all filter outputs +# 2. Energy-calculating (as spatial averaging) of the normalizing coefficients +# 3. Divisive normalization, where a filter output is divided by the energy (2) +# of its normalizing coefficient (1) +# +# This can be formalized as: +# +# 1. $ n_{o, s, y, x} := w_{o, s} \cdot \mathbf{F} $, where: +# - $n_{o, s, y, x}$ is a pixel in the _normalizing coefficient_ for filter $o, s$. +# - $\mathbf{F}$ is the whole set of filteroutputs, +# and each f_{o, s, y, x} is a specific pixel ($y, x$) +# in the output of filter with specific orientation and spatial scale ($o, s$). +# Thus, $\mathbf{F}$ is a 4D tensor ($O, S, Y, X$) +# - $\mathbf{w}$ is a set of interaction weights, indicating for each $(o, s)$ filter +# how all other $O, S$ filters combine. +# Thus, this is a 4D tensor ($O, S, O, S$). +# - $\cdot$ is a tensor dot-product operation +# +# 2. $ e_{o, s, y, x} := \sqrt{\mathrm{avg_{xy}}(n_{o, s}^2)} $, where: +# - $e_{o, s, y, x} is a pixel in the _energy estimate_ for filter $o, s$ +# - $\mathrm{avg_{xy}}$ is a spatial averaging function, +# taking some average over pixels ($X, Y$) in the _normalizing coefficient_ $n_{o, s}$. +# +# 3. $ f'_{o, s, y, x} := \frac{f_{o, s, y, x}}{e_{o, s, y, x}} $, where: +# - $\mathbf{F'}$ is the normalized set of filteroutputs; +# a 4D tensor of same dimensions and size as $\mathbf{F}$ +# +# Combined, this gives: +# $$ +# f'_{o, s, y, x} := +# \frac{f_{o, s, y, x}} +# {\sqrt{\mathrm{avg_{xy}}( +# (\mathbf{w}\cdot\mathbf{F})^2 +# )}} +# $$ +# +# All three (F)(L)ODOG models can be expressed in this form, +# by changing the implementation of parts (1) and (2), specifically. + # %% [markdown] # ### ODOG @@ -303,48 +344,9 @@ assert np.allclose(normed, ODOG_outputs) -# %% [markdown] -# ## Generalization - -# %% [markdown] -# The -ODOG normalization step can now generally be formulated as: -# $$ f'_{o',s'} = -# \frac{f_{o',s'}} -# {\sqrt{\mathbf{A}*(\sum_{o=1}^{O}\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y})^2}}} -# $$ -# where -# $$ -# w_{o', s', o, s} = -# \begin{cases} -# 1 & o = o' & (L)ODOG \\ -# G(\sigma, s', s) & o = o' & FLODOG \\ -# 0 & else -# \end{cases} -# $$ -# and -# $$ -# \mathbf{A} = -# \begin{cases} -# \frac{1}{XY} & ODOG \\ -# G(\sigma) & LODOG \\ -# G(\sigma * s') & FLODOG -# \end{cases} -# $$ - -# %% [markdown] -# This identifies two parts in which the three models differ, -# which we name as follows: -# - the _normalization weights_ $\mathbf{W}$ -# - the _spatial averaging kernel_ $\mathbf{A}$ -# -# These are both featured in the _normalization coefficient_ -# that is the denominator of the _divisive normalization_ function. # %% [markdown] -# -# An implementation of this formulation can be found here: - - +# ## Testing # %% def divisive_normalization(filter_output, norm_coeff): return filter_output / norm_coeff @@ -367,9 +369,6 @@ def spatial_kernel_LODOG(x, y, sigmas=[0, 0]): return kernel -# %% [markdown] -# ## Testing - # %% [markdown] # ### Unit tests From 7e9d707f24df576e4b115afbadadce8546b5f1d2 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 6 Feb 2024 13:58:23 +0100 Subject: [PATCH 63/79] docs(normalization): comparison of interaction weights --- .../normalization_generalized.py | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index 4175d51..f688ddc 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -147,6 +147,196 @@ # by changing the implementation of parts (1) and (2), specifically. +# %% [markdown] +# ## Normalizing coefficients +# The first step in normalization is to define +# the normalizing coefficient ($n_{o, s}$) for each filter ($f_{o, s}$). +# This normalizing coefficient is made up of (a subset of) +# the responses in all $\mathbf{F}$ filter outputs. +# Thus, the tensor of normalizing coefficients $\mathbf{N}$ +# contains $O \times S$ 2D ($Y \times X$): +# one normalizing coefficient $n_{o',s'}$ per filter $f_{o',s'}$ to normalize. +# +# In `multyscale`, we calculate these normalizing coefficients +# as a tensor dot-product beween a set of weights $\mathbf{w}$ +# and all filter outputs $\mathbf{F}$. +# The 4D tensor ($O, S, O, S$) of interaction weights $\mathbf{w}$ +# can be constructed by combined separately defined sets of weights +# for how different orientations interact, +# and how different spatial scale interact. + +# %% [markdown] +# In all -ODOG models, filters only normalize other filters with the same orientation, +# i.e., when $o'=o$. Thus, this forms a diagonal matrix of orientation interaction weights + +# %% Orientation normalization weights +orientation_norm_weights = multyscale.normalization.orientation_norm_weights(6) +plt.pcolor( + orientation_norm_weights[::-1, :], cmap="Greens", edgecolors="k", linewidths=1, vmin=0, vmax=1 +) +plt.xlabel("Orientation $o'$") +plt.ylabel("Orientation $o$") +plt.show() + +# %% [markdown] +# In the base ODOG model, all scales influence all (other) scales equally, +# thus the matrix of scale interaction weights is all $1$s. + +# %% Scale normalization weights +scale_norm_weights = multyscale.normalization.scale_norm_weights_equal(7) +plt.pcolor(scale_norm_weights, cmap="Greens", edgecolors="k", linewidths=1, vmin=0, vmax=1) +plt.xlabel("Spatial scale/freq. $s'$") +plt.ylabel("Spatial scale/freq. $s$") +plt.show() + +# %% [markdown] +# These normalization weights along each dimension +# are then combined into a single +# $(O' \times S' \times O \times S)$ matrix (tensor) of normalization weights. +# +# This tensor $w_{o',s',o,s}$ can be produced +# using the function `multyscale.normalization.create_normalization_weights()` +# from the separate sets of weights for orientations and scales. +# +# For the example where $(o'=3, s'=4)$, +# this means that all weights $w_{3,4,o,s}=1$ if $o==3$, regardless of $s$. + +# %% ODOG Normalization weights +interaction_weights = multyscale.normalization.create_normalization_weights( + *filters_output.shape[:2], scale_norm_weights, orientation_norm_weights +) + +# Visualize weights +fig, axs = plt.subplots(*interaction_weights.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(interaction_weights.shape[:2]): + axs[o, s].pcolor( + interaction_weights[interaction_weights.shape[0] - o - 1, s], + cmap="Greens", + edgecolors="k", + linewidths=1, + vmin=0, + vmax=1, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# These interaction weights then are used to combine all filter outputs $\mathbf{F}$ +# to create the normalizing coefficients $\mathbf{n}$ + +# %% Normalizing coefficients +normalizing_coefficients = multyscale.normalization.normalizers(filters_output, interaction_weights) + +# Visualize each normalizing coefficient n_{o,s}, i.e. +# the normalizer image for each individual filter f_{o,s} +fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients.shape[:2]): + axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# The LODOG model uses the same interaction weights as the base ODOG model. + +# %% [markdown] +# The FLODOG model uses a different set of interaction weights. +# Specifically, it does not weight all spatial scales equally. +# (It does weigh the orientations in the same manner as (L)ODOG). +# Instead of the equal weighting, +# the FLODOG model uses a 1D Gaussian as the weights profile: +# centered on the spatial scale of the filter being normalized +# and dropping off as a Gaussian function of the relative index the other spatial scales. + +# %% Define weights +scale_norm_weights_FLODOG = multyscale.normalization.scale_norm_weights_gaussian(7, sdmix=0.5) +assert np.array_equal(scale_norm_weights_FLODOG, FLODOG.scale_norm_weights) + +# %% +fig, axs = plt.subplots(2, 2, sharex="row", sharey="row") +axs[0, 0].pcolor( + LODOG.scale_norm_weights, + cmap="Greens", + edgecolors="k", + linewidths=1, + vmin=0, + vmax=1, +) +axs[0, 0].set_ylabel("scale of filter to normalize (idx)") +axs[0, 0].set_title("LODOG weights") + +axs[0, 1].pcolor( + FLODOG.scale_norm_weights, + cmap="Greens", + edgecolors="k", + linewidths=1, + vmin=0, + vmax=1, +) +axs[0, 1].set_xlabel("scale of filter to normalize (idx)") +axs[0, 1].set_title("FLODOG weights") + +axs[1, 0].plot(LODOG.scale_norm_weights[3, :], color="black") +axs[1, 0].set_xlabel("scale of other filter (idx)") +axs[1, 0].set_ylabel("weight") + +axs[1, 1].plot(FLODOG.scale_norm_weights[3, :], color="black") +axs[1, 1].set_xlabel("scale of other filter (idx)") + +plt.show() + +# %% [markdown] +# Since these weights are strongly biased towards same/similar spatial scales, +# the resulting normalizing coefficients also more strongly resemble +# the filter outputs at these spatial scales. + +# %% Normalizing coefficients +normalizing_coefficients_LODOG = LODOG.normalizers(filters_output) +normalizing_coefficients_FLODOG = FLODOG.normalizers(filters_output) + +# Visualize each norm. coeff. +vmin = min(np.min(normalizing_coefficients_LODOG), np.min(normalizing_coefficients_FLODOG)) +vmax = max(np.max(normalizing_coefficients_LODOG), np.max(normalizing_coefficients_FLODOG)) + +fig, axs = plt.subplots(*normalizing_coefficients_LODOG.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients_LODOG.shape[:2]): + axs[o, s].imshow( + normalizing_coefficients_LODOG[o, s], + cmap="coolwarm", + extent=visextent, + vmin=vmin, + vmax=vmax, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +fig.suptitle("LODOG") +plt.show() + +fig, axs = plt.subplots(*normalizing_coefficients_FLODOG.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalizing_coefficients_FLODOG.shape[:2]): + axs[o, s].imshow( + normalizing_coefficients_FLODOG[o, s], + cmap="coolwarm", + extent=visextent, + vmin=vmin, + vmax=vmax, + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +fig.suptitle("FLODOG") +plt.show() + +# %% [markdown] +# ### Summary +# In the first step of normalization, +# the construction of normalizing coefficients $\mathbf{n}$ +# as weighted combinations of all filter outputs $\mathbf{F}$, +# we can express the three models as using different sets of interaction weights $\mathbf{w}$. +# `multyscale` makes it easy and straightforward to implement these different weights, +# as well as to explore even further with different weights. + + # %% [markdown] # ### ODOG # The ODOG normalization step can be formulated as: From 113ee12251f8b382c5ec367da6311f378deb4fd7 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 6 Feb 2024 15:20:18 +0100 Subject: [PATCH 64/79] docs(normalization): ODOG normalization divides by matrices --- .../normalization_generalized.py | 121 +++++++++--------- 1 file changed, 64 insertions(+), 57 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index f688ddc..a40bcb0 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -336,90 +336,97 @@ # `multyscale` makes it easy and straightforward to implement these different weights, # as well as to explore even further with different weights. +# %% [markdown] +# ## Energy estimate through (localized) averaging +# The second step in -ODOG normalization, +# is to use an energy-estimate of, rather than the raw, normalizing coefficients. +# Rather than normalizing by this weighted sum of all filter outputs at each pixel location, +# instead the -ODOG models normalize by +# the _energy_ of the normalizing coefficient. +# Energy here is expressed as the (spatial) root-mean-square of the signal. +# +# 1. Square each ($X \times Y = 1024 \times 1024$) pixel of the normalizing coefficient $n_{o, s}$ +# 2. Average \mathrm{avg_{yx}} across pixels +# 3. Square-root of this average +# +# $$ e_{o, s, y, x} := \sqrt{\mathrm{avg_{yx}}(n_{o, s}^2)} $$ +# +# NOTE: this is the _quadratic mean_ of the normalizing coefficient. # %% [markdown] -# ### ODOG -# The ODOG normalization step can be formulated as: -# $$ -# f'_{o',s',x,y} = \frac{f_{o',s',x,y}} -# {\sqrt{\frac{1}{XY}\sum_{y=1}^{Y} \sum_{x=1}^{X}n_{o',s'}^2}} -# $$ -# where $w_{o', s', o, s} = \begin{cases} -# 1 & o = o' \\ -# 0 & else -# \end{cases}$ -# and -# $$ -# n_{o',s'} = \sum_{o=1}^{O}\sum_{s=1}^{S} {w_{o',s',o,s} f_{o,s,x,y}} -# $$ +# The base ODOG normalization uses the _global image mean_ as the spatial average: +# +# $$ \mathrm{avg_{yx}}(n_{o, s, x, y}) = \frac{1}{YX} n_{o, s, x, y} $$ +# +# This results in a single energy estimate for each $(o, s)$. -# %% -ODOG_outputs = ODOG.normalize_outputs(filters_output) +# %% Global image RMS +ODOG_normalizers = ODOG.normalizers(filters_output) +ODOG_energies = ODOG.normalizers_to_RMS(ODOG_normalizers) +print(ODOG_energies.shape) + +# Visualise +plt.pcolor(ODOG_energies[::-1, :], cmap="coolwarm", edgecolors="k", linewidths=1) +plt.ylabel("Orientation $o'$") +plt.xlabel("Spatial scale $s'$") +plt.show() # %% [markdown] -# #### Can also divide by 2D "image" arrays -# In the original formulation of the ODOG normalization step, -# the denominator is a single scalar -# -- the root mean square of the normalizer image $n_{o',s'}$ +# These energy estimates are then used as the denominator in the divisive normalization step (3) + +# %% Divisive normalization +ODOG_outputs = np.ndarray(filters_output.shape) +for o, s in np.ndindex(filters_output.shape[:2]): + f = filters_output[o, s, ...] + n = ODOG_energies[o, s] + ODOG_outputs[o, s] = f / n + +# Visualize each normalized f'_{o',s'} +fig, axs = plt.subplots(*ODOG_outputs.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(ODOG_outputs.shape[:2]): + axs[o, s].imshow( + ODOG_outputs[o, s], + cmap="coolwarm", + extent=visextent, + vmin=ODOG_outputs.min(), + vmax=ODOG_outputs.max(), + ) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() -# %% -ODOG_normalizers = ODOG.normalizers(filters_output) -ODOG_RMS = ODOG.normalizers_to_RMS(ODOG_normalizers) -print(ODOG_RMS.shape) # %% [markdown] +# ### Energy estimates as matrices ("images") # However, it makes conceptual more sense -# to have a denominator that is the same 2 dimensions +# to have a denominator that is the same dimensions (Y, X) # as the filter that is being normalized. -# %% -i_RMS = np.tile(ODOG_RMS.reshape(6, 7, 1, 1), (1, 1, 1024, 1024)) - -norm_i_outputs = np.ndarray(filters_output.shape) -for o, s in np.ndindex(filters_output.shape[:2]): - norm_i_outputs[o, s] = filters_output[o, s, ...] / i_RMS[o, s] - -assert np.allclose(ODOG_outputs, norm_i_outputs) +# %% Energies as tensor +ODOG_energies_i = np.tile(ODOG_energies.reshape(6, 7, 1, 1), (1, 1, 1024, 1024)) +print(ODOG_energies_i.shape) # %% [markdown] # This is equivalent to implementing the averaging in the denominator -# as a multiplication with a matrix where each component is the RMS of the normalizer image. +# as a multiplication with a matrix where each component is the energy of the normalizer image. # # $$ -# f'_{o',s'} = \frac{f_{o',s'}}{\sqrt{\mathbf{M}}} +# f'_{o',s'} = \frac{f_{o',s'}}{\mathbf{E}} # $$ # where # $$ -# m_{x,y} = \frac{1}{XY}\sum_{y=1}^{Y} \sum_{x=1}^{X}n_{o',s'}^2 +# e_{o', s', y,x} = \sqrt{\frac{1}{YX}\sum_{y=1}^{Y} \sum_{x=1}^{X}n_{o',s'}^2} # $$ # # This changes the dimensionality of the denominator, # but not actually the result of the division, # since image-by-image division is executed pixel-wise. -# %% [markdown] -# ### (F)LODOG -# The (F)LODOG normalization step can be formulated as: -# $$ -# f'_{o',s'} = \frac{f_{o',s'}} -# {\sqrt{G(\sigma) * (\sum_{o=1}^{O}\sum_{s=1}^{S} w_{o',s',o,s}f_{o,s})^2}} -# $$ -# where $w_{o', s', o, s} = \begin{cases} -# 1 & o = o' \\ -# 0 & else -# \end{cases}$ -# and $G({\sigma}) * ...$ means convolution with a 2D ($X,Y$) kernel -# -- in this case, a Gaussian with standard deviation $\sigma$ in both directions. -# -# NOTE: FLODOG and LODOG differ just in the _values_ for $\sigma$ and $\mathbf{W}$ +# %% Divisive normalization +norm_i_outputs = filters_output / ODOG_energies_i -# %% -LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent) -FLODOG = multyscale.models.FLODOG_RHS2007(shape=stimulus.shape, visextent=visextent) +assert np.allclose(ODOG_outputs, norm_i_outputs) -# %% -LODOG_outputs = LODOG.normalize_outputs(filters_output) -FLODOG_outputs = FLODOG.normalize_outputs(filters_output) # %% [markdown] # ### Difference(s) From 9f4163755506613e4a372882aef396e6eaebc09d Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 6 Feb 2024 15:20:43 +0100 Subject: [PATCH 65/79] docs(normalization): global image averaging as filter operation --- .../normalization_generalized.py | 85 ++++++++++++++----- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index a40bcb0..a4937c2 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -429,22 +429,69 @@ # %% [markdown] -# ### Difference(s) -# The difference between (F)LODOG and ODOG normalization is purely in the denominator $\mathbf{\sqrt{M}}$: +# ### Localized spatial averaging +# # Instead of the global image mean, +# the (F)LODOG model uses a Gaussian window $G$ of some with $\sigma$ +# to average over pixels, +# giving the _local_ (estimate of) energy: +# +# $$ e_\mathrm{local}(n_{o',s'},\sigma) = \sqrt{G(\sigma) * (n_{o',s',x,y})^2}$$ +# +# Thus, difference between (F)LODOG and ODOG normalization is purely in the denominator $e$: # $$ # \begin{aligned} -# ODOG: \mathbf{M} &= \frac{1}{XY}\sum_{y=1}^{Y} \sum_{x=1}^{X} n_{o',s'}^2 \\ +# ODOG: e &= \sqrt{\frac{1}{YX}\sum_{y=1}^{Y} \sum_{x=1}^{X} n_{o',s'}^2} \\ # -# (F)LODOG: \mathbf{M} &= G(\sigma) * n_{o',s'}^2 +# (F)LODOG: e &= \sqrt{G(\sigma) * n_{o',s',x,y}^2} # \end{aligned} # $$ +# %% Spatial Gaussian +sigmas = LODOG.window_sigmas +G = multyscale.normalization.spatial_avg_windows_gaussian(LODOG.bank.x, LODOG.bank.y, sigmas) + # %% [markdown] -# For ODOG and LODOG, the weights $\mathbf{W}$ are identical, -# and therefore so are the normalizer images $\mathbf{N}$. +# Applying this Gaussian window gives the _local_ (estimate of) of energy -# %% -assert np.allclose(ODOG.normalizers(filters_output), LODOG.normalizers(filters_output)) +# %% Local RMS +normalization_local_energy = np.square(normalizing_coefficients.copy()) +for o, s in np.ndindex(normalizing_coefficients.shape[:2]): + normalization_local_energy[o, s] = multyscale.filters.apply( + G[o, s, :], normalization_local_energy[o, s], padval=0 + ) + +normalization_local_energy = ( + np.sqrt(normalization_local_energy + 1e-6) + 1e-6 +) # minor offset to avoid negatives/0's + +assert np.array_equal(normalization_local_energy, LODOG.normalizers_to_RMS(normalizing_coefficients)) + +# Visualize each local RMS +fig, axs = plt.subplots(*normalization_local_energy.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalization_local_energy.shape[:2]): + axs[o, s].imshow(normalization_local_energy[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() + +# %% [markdown] +# Since these local energy estimates are not the same across the "image", +# here expressing the estimates as 2D matrices is essential. +# Therefore, it makes sense to also do this for the base ODOG model above, +# to make the comparison clearer. + +# %% Divisive normalization +# Since the local energies tensor is the same (O, S, X, Y) shape as the filter outputs +# we can simply divide +LODOG_outputs = filters_output / normalization_local_energy + +# Visualize each normalized f'_{o',s'} +fig, axs = plt.subplots(*LODOG_outputs.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(LODOG_outputs.shape[:2]): + axs[o, s].imshow(LODOG_outputs[o, s], cmap="coolwarm", extent=visextent) +fig.supxlabel("Spatial scale/freq. $s'$") +fig.supylabel("Orientation $o'$") +plt.show() # %% [markdown] # ### Implement global image averaging as a spatial filter @@ -452,7 +499,7 @@ # %% [markdown] # The question then is whether global averaging # $$ -# \mathbf{M} = \frac{1}{XY}\sum_{y=1}^{Y}\sum_{x=1}^{X} ... +# \frac{1}{YX}\sum_{y=1}^{Y}\sum_{x=1}^{X} ... # $$ # can be reformulated as a convolution with 2D kernel # $$ @@ -463,7 +510,7 @@ # and this sum is repeated for centering the filter # on each pixel in the input image. # If we can construct a kernel $\mathbf{A}$ ensures that for every pixel -# it weights all pixels in the input image by $\frac{1}{XY}$, +# it weights all pixels in the input image by $\frac{1}{YX}$, # then the output of the convolution is simply the global image average. # %% @@ -485,7 +532,7 @@ # - if the kernel were the same size as the input image, # then some pixels on the _other_ edge of the image # will no longer fall in our image. -# Thus the spatial kernel $\mathbf{A}$ has $(2X,2Y)$ entries +# Thus the spatial kernel $\mathbf{A}$ has $(2Y,2X)$ entries # to assure that each pixel will take its average from the entire image. # - what to do at the edges, to ensure that the filter has values to filter? # We pad all the edges with the value $0$, @@ -494,25 +541,25 @@ # %% [markdown] # The spatial averaging step in the ODOG normalization then becomes: # $$ -# \mathbf{M} = \mathbf{A} * (...) +# \mathbf{A} * (...) # $$ -# and thus the RMS step: +# and thus the energy step: # $$ -# \sqrt{\mathbf{M}} = \sqrt{\mathbf{A} * n_{o',s'}^2} +# e = \sqrt{\mathbf{A} * n_{o',s'}^2} # $$ -# %% +# %% Global image averaging as filter A = np.ones((1024 * 2, 1024 * 2)) / 1024**2 -# calculate RMS using the kernel -i_RMS2 = np.ndarray(filters_output.shape) +# calculate energy using the kernel +ODOG_energies_i2 = np.ndarray(filters_output.shape) for o, s in np.ndindex(filters_output.shape[:2]): norm = ODOG_normalizers[o, s, ...] ** 2 mean = multyscale.filters.apply(norm, A, padval=0) - i_RMS2[o, s] = np.sqrt(mean) + ODOG_energies_i2[o, s] = np.sqrt(mean) # this replicates the just verified normalizing image -assert np.allclose(i_RMS2, i_RMS) +assert np.allclose(ODOG_energies_i2, ODOG_energies_i) # %% [markdown] # Thus, we can now reformulate the ODOG normalization as: From f696fe57deaf2bd91f877ebadeedaa1a3013e75b Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 6 Feb 2024 15:23:23 +0100 Subject: [PATCH 66/79] docs(normalization): summarize generalized normalization (and pseudo-implementation) --- .../normalization_generalized.py | 180 ++++++------------ 1 file changed, 62 insertions(+), 118 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index a4937c2..cbff88a 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -562,142 +562,86 @@ assert np.allclose(ODOG_energies_i2, ODOG_energies_i) # %% [markdown] +# ### Summary # Thus, we can now reformulate the ODOG normalization as: # $$ # f'_{o',s'} = \frac{f_{o',s'}} -# {\sqrt{\mathbf{A} * (\sum_{o=1}^{O}\sum_{s=1}^{S} w_{o',s',o,s}f_{o,s})^2}} +# {\sqrt{\mathbf{A} * (\mathbf{w} \cdot \mathbf{F})^2}} # $$ -# where $w_{o', s', o, s} = \begin{cases} -# 1 & o = o' \\ -# 0 & else -# \end{cases}$ -# and $\mathbf{A} * ...$ means the convolution with our 2D ($X,Y$) kernel -# that implements global averaging. - -# %% -A = np.ones((1024 * 2, 1024 * 2)) / 1024**2 - -normed = np.ndarray(filters_output.shape) -for o, s in np.ndindex(filters_output.shape[:2]): - norm = ODOG_normalizers[o, s, ...] ** 2 - mean = multyscale.filters.apply(norm, A, padval=0) - normed[o, s] = filters_output[ - o, - s, - ] / np.sqrt(mean) - -assert np.allclose(normed, ODOG_outputs) - - -# %% [markdown] -# ## Testing -# %% -def divisive_normalization(filter_output, norm_coeff): - return filter_output / norm_coeff - - -def norm_coeff(normalizer, spatial_kernel): - norm = normalizer**2 - spatial_average = multyscale.filters.apply(norm, spatial_kernel, padval=0) - coeff = np.sqrt(spatial_average + 1e-6) - return coeff - - -def spatial_kernel_ODOG(x, y): - return np.ones([dim * 2 for dim in x.shape]) / np.prod(x.shape) - - -def spatial_kernel_LODOG(x, y, sigmas=[0, 0]): - kernel = multyscale.filters.gaussian2d(x, y, sigmas) - kernel /= kernel.sum() - return kernel - +# +# where: +# - $w_{o', s', o, s} = \begin{cases} +# 1 & o = o' \\ +# 0 & else +# \end{cases}$ +# - $\cdot$ is a tensor dot-product +# - $\mathbf{A} * ...$ means the convolution with our 2D ($Y,X$) kernel +# that implements global averaging. +# # %% [markdown] -# ### Unit tests - -# %% -ODOG_kernel = spatial_kernel_ODOG(ODOG.bank.x, ODOG.bank.y) -assert np.all(ODOG_kernel == A) - -# %% -LODOG_kernel = spatial_kernel_LODOG( - ODOG.bank.x, ODOG.bank.y, sigmas=(LODOG.window_sigma, LODOG.window_sigma) -) -LODOG_kernels = multyscale.normalization.spatial_avg_windows_gaussian( - ODOG.bank.x, ODOG.bank.y, LODOG.window_sigmas -) - -assert np.allclose(LODOG_kernel, LODOG_kernels[0, 0]) - -# %% -img = filters_output[3, 4] -kernel = spatial_kernel_ODOG(ODOG.bank.x, ODOG.bank.y) -mean_filtered = multyscale.filters.apply(img, A, padval=0) - -assert np.allclose(mean_filtered, img.mean()) - -# %% -coeffs = np.ndarray(filters_output.shape) -for o, s in np.ndindex(filters_output.shape[:2]): - coeffs[o, s, ...] = norm_coeff(ODOG_normalizers[o, s, ...], A) - -assert np.allclose(coeffs, i_RMS2) +# ## Generalized formulation +# +# All three (F)(L)ODOG can thus be expressed as parameteric variations +# of the same overall divisive normalization structure: +# $$ +# f'_{o, s, y, x} := +# \frac{f_{o, s, y, x}} +# {\sqrt{ +# \mathbf{A} * +# (\mathbf{w} \cdot \mathbf{F})^2 +# } +# } +# $$ +# +# Both $\mathbf{w}$ and $\mathbf{A}$ depend on the specific model: +# - $\mathbf{w}$ is the same for LODOG and ODOG, where +# $$ w_{o', s', o, s} = \begin{cases} +# 1 & o = o' \\ +# 0 & else +# \end{cases} $$ +# For FLODOG, the $w_{o', s'}$ also depends on the relative index of $(s', s)$ +# - $\mathbf{A}$ is Gaussian filter $\mathbf{G(\sigma)}$ for LODOG and FLODOG, +# where $\sigma = k$ and $\sigma = ks$, respectively. +# For ODOG, $\mathbf{A}$ is a (larger) constant filter. +# However, this could even be understood as an infinite-width Gaussian $\mathbf{G(\infty)}$ # %% [markdown] -# ### ODOG +# ### Pseudo-implementation # %% -ODOG_normalizers = ODOG.normalizers(filters_output) - -ODOG_outputs = ODOG.normalize_outputs(filters_output) +def divisive_normalization(filter_output, norm_energy): + return filter_output / norm_energy -kernel = spatial_kernel_ODOG(ODOG.bank.x, ODOG.bank.y) -new_normed = np.ndarray(filters_output.shape) -for o_prime, s_prime in np.ndindex(filters_output.shape[:2]): - normalizer = ODOG_normalizers[o_prime, s_prime] - coeff = norm_coeff(normalizer, kernel) - new_normed[o_prime, s_prime] = divisive_normalization(filters_output[o_prime, s_prime], coeff) +def norm_energy(norm_coeff, spatial_kernel): + norm = norm_coeff**2 + spatial_average = multyscale.filters.apply(norm, spatial_kernel, padval=0) + energy = np.sqrt(spatial_average) + return energy -assert np.allclose(new_normed, ODOG_outputs) -# %% [markdown] -# ### LODOG +def norm_coeff(filter_outputs, normalization_weights): + coeffs = np.zeros_like(filter_outputs) + for o, s in np.ndindex(filter_outputs.shape[:2]): + weights = normalization_weights[o, s] -# %% -ODOG_normalizers = ODOG.normalizers(filters_output) + # Tensor dot: multiply filters_output by weights, then sum over axes [0,1] + coeff = np.tensordot(filter_outputs, weights, axes=([0, 1], [0, 1])) -LODOG_outputs = LODOG.normalize_outputs(filters_output) + # Accumulate + coeffs[o, s, ...] = coeff -kernel = spatial_kernel_LODOG( - ODOG.bank.x, ODOG.bank.y, sigmas=(LODOG.window_sigma, LODOG.window_sigma) -) + return coeffs -new_normed = np.ndarray(filters_output.shape) -for o_prime, s_prime in np.ndindex(filters_output.shape[:2]): - normalizer = ODOG_normalizers[o_prime, s_prime] - coeff = norm_coeff(normalizer, kernel) - new_normed[o_prime, s_prime] = divisive_normalization(filters_output[o_prime, s_prime], coeff) -assert np.allclose(new_normed, LODOG_outputs) - -# %% [markdown] -# ### FLODOG - -# %% -FLODOG_normalizers = FLODOG.normalizers(filters_output) - -FLODOG_outputs = FLODOG.normalize_outputs(filters_output) - -kernels = multyscale.normalization.spatial_avg_windows_gaussian( - ODOG.bank.x, ODOG.bank.y, FLODOG.window_sigmas -) +def spatial_kernel_ODOG(shape): + kernel = np.ones([dim * 2 for dim in shape]) + kernel /= kernel.sum() + return kernel -new_normed = np.ndarray(filters_output.shape) -for o_prime, s_prime in np.ndindex(filters_output.shape[:2]): - normalizer = FLODOG_normalizers[o_prime, s_prime] - coeff = norm_coeff(normalizer, kernels[o_prime, s_prime]) + 1e-6 - new_normed[o_prime, s_prime] = divisive_normalization(filters_output[o_prime, s_prime], coeff) -assert np.allclose(new_normed, FLODOG_outputs) +def spatial_kernel_LODOG(x, y, sigmas=[1.0, 1.0]): + kernel = multyscale.filters.gaussian2d(x, y, sigmas) + kernel /= kernel.sum() + return kernel From ff06d7ec3f10d78cceda8fc82d97e5096d78fe2c Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 15 Feb 2024 17:03:18 +0100 Subject: [PATCH 67/79] docs(normalization): typesetting --- .../normalization_generalized.py | 114 ++++++++---------- 1 file changed, 51 insertions(+), 63 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index cbff88a..9224811 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -114,33 +114,31 @@ # # This can be formalized as: # -# 1. $ n_{o, s, y, x} := w_{o, s} \cdot \mathbf{F} $, where: -# - $n_{o, s, y, x}$ is a pixel in the _normalizing coefficient_ for filter $o, s$. +# 1. $ n_{o', s', y, x} := w_{o', s'} \cdot \mathbf{F} $, where: # - $\mathbf{F}$ is the whole set of filteroutputs, -# and each f_{o, s, y, x} is a specific pixel ($y, x$) +# and each $f_{o, s, y, x}$ is a specific pixel ($y, x$) # in the output of filter with specific orientation and spatial scale ($o, s$). # Thus, $\mathbf{F}$ is a 4D tensor ($O, S, Y, X$) -# - $\mathbf{w}$ is a set of interaction weights, indicating for each $(o, s)$ filter +# - $n_{o', s', y, x}$ is a pixel in the _normalizing coefficient_ for filter $o', s'$. +# - $\mathbf{w}$ is a set of interaction weights, indicating for each $(o', s')$ filter # how all other $O, S$ filters combine. # Thus, this is a 4D tensor ($O, S, O, S$). # - $\cdot$ is a tensor dot-product operation # -# 2. $ e_{o, s, y, x} := \sqrt{\mathrm{avg_{xy}}(n_{o, s}^2)} $, where: -# - $e_{o, s, y, x} is a pixel in the _energy estimate_ for filter $o, s$ +# 2. $ e_{o', s', y, x} := \sqrt{\mathrm{avg_{xy}}(n_{o, s}^2)} $, where: +# - $e_{o', s', y, x}$ is a pixel in the _energy estimate_ for filter $o', s'$ # - $\mathrm{avg_{xy}}$ is a spatial averaging function, -# taking some average over pixels ($X, Y$) in the _normalizing coefficient_ $n_{o, s}$. +# taking some average over pixels ($X, Y$) in the _normalizing coefficient_ $n_{o', s'}$. # -# 3. $ f'_{o, s, y, x} := \frac{f_{o, s, y, x}}{e_{o, s, y, x}} $, where: +# 3. $ f'_{o', s', y, x} := \frac{f_{o', s', y, x}}{e_{o', s', y, x}} $, where: # - $\mathbf{F'}$ is the normalized set of filteroutputs; # a 4D tensor of same dimensions and size as $\mathbf{F}$ # # Combined, this gives: -# $$ -# f'_{o, s, y, x} := -# \frac{f_{o, s, y, x}} -# {\sqrt{\mathrm{avg_{xy}}( -# (\mathbf{w}\cdot\mathbf{F})^2 -# )}} +# +# $$ f'_{o, s, y, x} := +# \frac{f_{o, s, y, x}} +# {\sqrt{\mathrm{avg_{xy}}((\mathbf{w}\cdot\mathbf{F})^2)}} # $$ # # All three (F)(L)ODOG models can be expressed in this form, @@ -150,7 +148,7 @@ # %% [markdown] # ## Normalizing coefficients # The first step in normalization is to define -# the normalizing coefficient ($n_{o, s}$) for each filter ($f_{o, s}$). +# the normalizing coefficient ($n_{o', s'}$) for each filter ($f_{o, s}$). # This normalizing coefficient is made up of (a subset of) # the responses in all $\mathbf{F}$ filter outputs. # Thus, the tensor of normalizing coefficients $\mathbf{N}$ @@ -199,7 +197,7 @@ # from the separate sets of weights for orientations and scales. # # For the example where $(o'=3, s'=4)$, -# this means that all weights $w_{3,4,o,s}=1$ if $o==3$, regardless of $s$. +# this means that all weights $w_{3,4,o,s}=1$ if $o=3$, regardless of $s$. # %% ODOG Normalization weights interaction_weights = multyscale.normalization.create_normalization_weights( @@ -356,9 +354,9 @@ # %% [markdown] # The base ODOG normalization uses the _global image mean_ as the spatial average: # -# $$ \mathrm{avg_{yx}}(n_{o, s, x, y}) = \frac{1}{YX} n_{o, s, x, y} $$ +# $$ \mathrm{avg_{yx}}(n_{o', s', x, y}) = \frac{1}{YX} n_{o', s', x, y} $$ # -# This results in a single energy estimate for each $(o, s)$. +# This results in a single energy estimate for each $(o', s')$. # %% Global image RMS ODOG_normalizers = ODOG.normalizers(filters_output) @@ -410,13 +408,11 @@ # This is equivalent to implementing the averaging in the denominator # as a multiplication with a matrix where each component is the energy of the normalizer image. # -# $$ -# f'_{o',s'} = \frac{f_{o',s'}}{\mathbf{E}} -# $$ +# $$ f'_{o',s'} = \frac{f_{o',s'}}{e_{o', s', y,x}} $$ +# # where -# $$ -# e_{o', s', y,x} = \sqrt{\frac{1}{YX}\sum_{y=1}^{Y} \sum_{x=1}^{X}n_{o',s'}^2} -# $$ +# +# $$ e_{o', s', y,x} = \sqrt{\frac{1}{YX}\sum_{y=1}^{Y} \sum_{x=1}^{X}n_{o',s'}^2} $$ # # This changes the dimensionality of the denominator, # but not actually the result of the division, @@ -430,21 +426,21 @@ # %% [markdown] # ### Localized spatial averaging -# # Instead of the global image mean, +# Instead of the global image mean, # the (F)LODOG model uses a Gaussian window $G$ of some with $\sigma$ # to average over pixels, # giving the _local_ (estimate of) energy: # -# $$ e_\mathrm{local}(n_{o',s'},\sigma) = \sqrt{G(\sigma) * (n_{o',s',x,y})^2}$$ +# $$ e_\mathrm{local}(n_{o',s'},\sigma) = \sqrt{G(\sigma) * (n_{o',s',x,y})^2} $$ # # Thus, difference between (F)LODOG and ODOG normalization is purely in the denominator $e$: +# # $$ # \begin{aligned} -# ODOG: e &= \sqrt{\frac{1}{YX}\sum_{y=1}^{Y} \sum_{x=1}^{X} n_{o',s'}^2} \\ -# -# (F)LODOG: e &= \sqrt{G(\sigma) * n_{o',s',x,y}^2} -# \end{aligned} +# ODOG: e &= \sqrt{\frac{1}{YX}\sum_{y=1}^{Y} \sum_{x=1}^{X} n_{o',s'}^2} \\ +# (F)LODOG: e &= \sqrt{G(\sigma) * n_{o',s',x,y}^2} \end{aligned} # $$ +# # %% Spatial Gaussian sigmas = LODOG.window_sigmas @@ -464,7 +460,7 @@ np.sqrt(normalization_local_energy + 1e-6) + 1e-6 ) # minor offset to avoid negatives/0's -assert np.array_equal(normalization_local_energy, LODOG.normalizers_to_RMS(normalizing_coefficients)) +# assert np.array_equal(normalization_local_energy, LODOG.normalizers_to_RMS(normalizing_coefficients)) # Visualize each local RMS fig, axs = plt.subplots(*normalization_local_energy.shape[:2], sharex="all", sharey="all") @@ -481,7 +477,7 @@ # to make the comparison clearer. # %% Divisive normalization -# Since the local energies tensor is the same (O, S, X, Y) shape as the filter outputs +# Since the local energies tensor is the same $(O, S, X, Y)$ shape as the filter outputs # we can simply divide LODOG_outputs = filters_output / normalization_local_energy @@ -498,13 +494,13 @@ # %% [markdown] # The question then is whether global averaging -# $$ -# \frac{1}{YX}\sum_{y=1}^{Y}\sum_{x=1}^{X} ... -# $$ +# +# $$ \frac{1}{YX}\sum_{y=1}^{Y}\sum_{x=1}^{X} \dots $$ +# # can be reformulated as a convolution with 2D kernel -# $$ -# \mathbf{A}(..) * ... -# $$ +# +# $$ \mathbf{A}(..) * \dots $$ +# # A convolution is essentially a repeated weighted sum, # where the weight is the value in the filter # and this sum is repeated for centering the filter @@ -540,13 +536,13 @@ # %% [markdown] # The spatial averaging step in the ODOG normalization then becomes: -# $$ -# \mathbf{A} * (...) -# $$ +# +# $$ \mathbf{A} * (\dots) $$ +# # and thus the energy step: -# $$ -# e = \sqrt{\mathbf{A} * n_{o',s'}^2} -# $$ +# +# $$ e = \sqrt{\mathbf{A} * n_{o',s'}^2} $$ +# # %% Global image averaging as filter A = np.ones((1024 * 2, 1024 * 2)) / 1024**2 @@ -564,10 +560,8 @@ # %% [markdown] # ### Summary # Thus, we can now reformulate the ODOG normalization as: -# $$ -# f'_{o',s'} = \frac{f_{o',s'}} -# {\sqrt{\mathbf{A} * (\mathbf{w} \cdot \mathbf{F})^2}} -# $$ +# +# $$ f'_{o',s'} = \frac{f_{o',s'}}{\sqrt{\mathbf{A} * (\mathbf{w} \cdot \mathbf{F})^2}} $$ # # where: # - $w_{o', s', o, s} = \begin{cases} @@ -584,31 +578,25 @@ # # All three (F)(L)ODOG can thus be expressed as parameteric variations # of the same overall divisive normalization structure: -# $$ -# f'_{o, s, y, x} := -# \frac{f_{o, s, y, x}} -# {\sqrt{ -# \mathbf{A} * -# (\mathbf{w} \cdot \mathbf{F})^2 -# } -# } -# $$ +# +# $$ f'_{o, s, y, x} := \frac{f_{o, s, y, x}}{\sqrt{\mathbf{A} * (\mathbf{w} \cdot \mathbf{F})^2}} $$ # # Both $\mathbf{w}$ and $\mathbf{A}$ depend on the specific model: # - $\mathbf{w}$ is the same for LODOG and ODOG, where -# $$ w_{o', s', o, s} = \begin{cases} +# $ w_{o', s', o, s} = \begin{cases} # 1 & o = o' \\ # 0 & else -# \end{cases} $$ -# For FLODOG, the $w_{o', s'}$ also depends on the relative index of $(s', s)$ -# - $\mathbf{A}$ is Gaussian filter $\mathbf{G(\sigma)}$ for LODOG and FLODOG, +# \end{cases} $ +# For FLODOG, the $ w_{o', s'} $ also depends on the relative index of $ (s', s) $ +# - $ \mathbf{A} $ is Gaussian filter $\mathbf{G(\sigma)}$ for LODOG and FLODOG, # where $\sigma = k$ and $\sigma = ks$, respectively. -# For ODOG, $\mathbf{A}$ is a (larger) constant filter. -# However, this could even be understood as an infinite-width Gaussian $\mathbf{G(\infty)}$ +# For ODOG, $ \mathbf{A} $ is a (larger) constant filter. +# However, this could even be understood as an infinite-width Gaussian $ \mathbf{G(\infty)} $ # %% [markdown] # ### Pseudo-implementation + # %% def divisive_normalization(filter_output, norm_energy): return filter_output / norm_energy From 084ab9223e7a869f1d310c7b6820b85cb98f6f3f Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 15 Feb 2024 17:03:30 +0100 Subject: [PATCH 68/79] docs: add generalized normalization to TOC --- docs/_toc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_toc.yml b/docs/_toc.yml index fa9b526..940731b 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -29,6 +29,7 @@ parts: - file: normalization/normalization_FLODOG sections: - file: normalization/explore_FLODOG_parameters + - file: normalization/normalization_generalized # - caption: Decoding # numbered: False - caption: Reference From e5effee5229707ef862a160a6cb0f4279681ee5e Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 1 Feb 2024 14:48:14 +0100 Subject: [PATCH 69/79] feat(normalization): generalized normalization functions Reorganizes and removes some existing normalization Better specification of shape for spatial normalization --- multyscale/normalization.py | 262 +++++++++++++++++++++++++++--------- 1 file changed, 199 insertions(+), 63 deletions(-) diff --git a/multyscale/normalization.py b/multyscale/normalization.py index c9d8fd7..619fbb4 100644 --- a/multyscale/normalization.py +++ b/multyscale/normalization.py @@ -1,41 +1,200 @@ +from collections.abc import Sequence + # Third party imports import numpy as np # Local application imports from . import filters -# DOG_BM1997 normalization +# Generalized (F)(L)ODOG normalization + + +def normalizers(multioutput: np.ndarray, normalization_weights: np.ndarray) -> np.ndarray: + """Construct all normalizers: weighted combination of all fitler outputs, for each filter + + Parameters + ---------- + multioutput : numpy.ndarray + all (O, S, X, Y) filter outputs + normalization_weights : numpy.ndarray + full tensor (O, S, O, S) of normalization weights + + Returns + ------- + numpy.ndarray + all (O, S, X, Y) normalizers: + a single 2D (X,Y) weighted combination of all filter outputs + per (O, S) filter-to-normalize + """ + + # Create normalizing images from weighted combination of filter outputs + norms: np.ndarray = np.ndarray(shape=multioutput.shape) + for o, s in np.ndindex(multioutput.shape[:2]): + weights = normalization_weights[o, s] + + # Tensor dot: multiply filters_output by weights, then sum over axes [0,1] + normalizer = np.tensordot(multioutput, weights, axes=([0, 1], [0, 1])) + # Accumulate + norms[o, s, ...] = normalizer + + return norms + + +def norm_coeff(normalizer: np.ndarray, spatial_kernel: np.ndarray) -> np.ndarray: + """Construct normalization coefficient: denominator for divisive normalization + + Parameters + ---------- + normalizer : numpy.ndarray + single 2D normalizer image; weighted combination of all filter outputs + spatial_kernel : numpy.ndarray + single kernel to spatially average (2D; over x,y) the normalizer + + Returns + ------- + numpy.ndarray + single normalization coefficient: denominator for divisive normalization + """ + norm = normalizer**2 + spatial_average = filters.apply(norm, spatial_kernel, padval=0) + coeff = np.sqrt(spatial_average) + return coeff + + +def divisive_normalization(filter_output: np.ndarray, norm_coeff: np.ndarray) -> np.ndarray: + """Apply divisive normalization to a single filter output + + Parameters + ---------- + filter_output : np.ndarray + output from a single filter, 2D of shape (x,y) + norm_coeff : np.ndarray + single normalization coefficient: denominator for divisive normalization + + Returns + ------- + np.ndarray + normalized filter output, 2D of shape (x, y) + """ + return filter_output / norm_coeff + + +def spatial_kernel_globalmean(shape: Sequence[int]) -> np.ndarray: + """Create spatial averaging kernel, calculating the global image mean + + Parameters + ---------- + shape : (int, int) + shape in pixels (height, width) of image to average + + Returns + ------- + numpy.ndarray + spatial averaging kernel + """ + return np.ones([dim * 2 for dim in shape]) / np.prod(shape) + + +def spatial_kernel_gaussian(x: np.ndarray, y: np.ndarray, sigmas: Sequence[float]) -> np.ndarray: + """Create spatial averaging kernel, in a 2D Gaussian shape + + Parameters + ---------- + x : numpy.ndarray + x coordinates of each pixel + y : numpy.ndarray + y coordinates of each pixel + sigmas : (float, float) + standard deviation along each axis (2-vector) or both axes (scalar) + + Returns + ------- + numpy.ndarray + spatial averaging kernel + """ + kernel = filters.gaussian2d(x, y, sigmas) + kernel /= kernel.sum() + return kernel -# ODOG_BM1999 normalization -# Each filter is normalized by other filters of same orientation, -# but not by filters at other orientations -# Different scales are weighted equally -# so for filter (o,s), all filters (O!=o, :) get weight 0, -# and all filters (O=o, S) get weight 1/len(S) def create_normalization_weights( - n_orientations, n_scales, scale_norm_weights, orientation_norm_weights -): - # Each filter can, in theory, normalized by every (other) filter - # But the weight for how much every filter normalizes, can vary - # For each filter, create a tensor with weights for each other filter - normalization_weights = np.ndarray(shape=(n_orientations, n_scales, n_orientations, n_scales)) - - for o, s in np.ndindex(n_orientations, n_scales): - normalization_weights[o, s, ...] = np.outer( - orientation_norm_weights[o], scale_norm_weights[s] + n_orientations: int, + n_scales: int, + scale_norm_weights: np.ndarray, + orientation_norm_weights: np.ndarray, +) -> np.ndarray: + """Combine normalization weights across dimensions into single tensor of weights + + Each filter can, in theory, normalized by every (other) filter, + but the weight for how much every filter normalizes, can vary. + For each filter, create a tensor with weights for each other filter. + + Parameters + ---------- + n_orientations : int + number of orientations to weight + n_scales : int + number of spatial scales/frequencies to weight + scale_norm_weights : numpy.ndarray + matrix of normalization weights across scales, of shape (n_scales, n_scales) + orientation_norm_weights : numpy.ndarray + matrix of normalization weights across orientations, of shape (n_orientations, n_orientations) + + Returns + ------- + numpy.ndarray + tensor of combined normalization weights, + of shape (n_orientations, n_scales, n_orientations, n_scales) -- + for each filter $(o', s')$, a matrix of $(O, S)$ normalization weights + """ + normalization_weights: np.ndarray = np.ndarray( + shape=(n_orientations, n_scales, n_orientations, n_scales) + ) + for o_prime, s_prime in np.ndindex(n_orientations, n_scales): + normalization_weights[o_prime, s_prime, ...] = np.outer( + orientation_norm_weights[o_prime], scale_norm_weights[s_prime] ) return normalization_weights -def scale_norm_weights_equal(n_scales): - scale_norm_weights = np.ones((n_scales, n_scales)) - # scale_norm_weights = scale_norm_weights / scale_norm_weights.sum((0)) - return scale_norm_weights +def scale_norm_weights_equal(n_scales: int) -> np.ndarray: + """Equal weighting for all scales + + All filters $(o=o', s)$ get some weight; + in (L)ODOG, that weight is $1/len(S)$, + + Parameters + ---------- + n_scales : int + number of scales to create weights for + + Returns + ------- + numpy.ndarray + matrix of normalization weights, of shape (n_scales, n_scales) + """ + return np.ones((n_scales, n_scales)) -def scale_norm_weights_gaussian(n_scales, sdmix): - scale_norm_weights = np.ndarray((n_scales, n_scales)) +def scale_norm_weights_gaussian(n_scales: int, sdmix: float) -> np.ndarray: + """Gaussian weighting for all scales + + All filters $(o=o', s)$ get some weight; + in FLODOG, that weight is Gaussian dependent on $s-s'$ + + Parameters + ---------- + n_scales : int + number of scales to create weights for + sdmix : float + standard deviation (SD) of Gaussian + + Returns + ------- + numpy.ndarray + matrix of normalization weights, of shape (n_scales, n_scales) + """ + scale_norm_weights: np.ndarray = np.ndarray(shape=(n_scales, n_scales)) for s in range(n_scales): rel_i = np.asarray(range(n_scales)) - s @@ -47,46 +206,23 @@ def scale_norm_weights_gaussian(n_scales, sdmix): return scale_norm_weights -def orientation_norm_weights(n_orientations): - orientation_norm_weights = np.eye(n_orientations) - orientation_norm_weights /= orientation_norm_weights.sum(0) - return orientation_norm_weights - - -def normalizers(filters_output, normalization_weights): - # Create normalizing images from weighted combination of filter outputs - normalizers = np.ndarray(filters_output.shape) - - for o, s in np.ndindex(filters_output.shape[:2]): - weights = normalization_weights[o, s] - - # Tensor dot: multiply filters_output by weights, then sum over axes [0,1] - normalizer = np.tensordot(filters_output, weights, axes=([0, 1], [0, 1])) +def orientation_norm_weights(n_orientations: int) -> np.ndarray: + """Self-normalization weight for each orientation - # Normalize normalizer... - # area = weights.sum() - # normalizer = normalizer / area - - # Accumulate - normalizers[o, s, ...] = normalizer - - return normalizers - - -def spatial_avg_windows_globalmean(normalizers): - filts = np.ndarray(normalizers.shape) - for o, s in np.ndindex(filts.shape[:2]): - filts[o, s] = filters.global_avg(normalizers.shape[2:]) - return filts + Each filter is normalized by other filters of same orientation, + but not by filters at other orientations; + so for filter $(o',s')$, all filters $(o=o', ...)$ get weight $1$, else $0$. + Parameters + ---------- + n_orientations : int + number of orientations to create weights for -def spatial_avg_windows_gaussian(x, y, sigmas): - filts = np.ndarray(sigmas.shape[:2] + x.shape) - for o, s in np.ndindex(filts.shape[:2]): - # Create Gaussian window - window = filters.gaussian2d(x, y, sigmas[o, s]) - - # Normalize window to unit-sum (== spatial averaging filter) - window = window / window.sum() - filts[o, s, ...] = window - return filts + Returns + ------- + numpy.ndarray + matrix of normalization weights, of shape (n_orientations, n_orientations) + """ + orientation_norm_weights = np.eye(n_orientations) + orientation_norm_weights /= orientation_norm_weights.sum(0) + return orientation_norm_weights From e3029e7b379fef4730315dd71aac6ca30662a64a Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 28 Jul 2022 19:01:39 +0200 Subject: [PATCH 70/79] test: normalization Closes #3 (Gitlab) --- test/test_normalization.py | 85 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 test/test_normalization.py diff --git a/test/test_normalization.py b/test/test_normalization.py new file mode 100644 index 0000000..f655b9a --- /dev/null +++ b/test/test_normalization.py @@ -0,0 +1,85 @@ +import numpy as np +import pytest + +from multyscale import filters, normalization + +shape = (1024, 1024) +visextent = (-16, 16, -16, 16) +O, S = (6, 7) + + +def test_scale_norm_weights_equal(): + norm_weights = normalization.scale_norm_weights_equal(S) + + assert np.all(norm_weights == np.ones((S, S))) + + +@pytest.mark.xfail +def test_scale_norm_weights_gaussian(): + raise NotImplementedError() + + +def test_orientation_norm_weights(): + norm_weights = normalization.orientation_norm_weights(O) + + assert np.all(norm_weights == np.identity(O)) + + +def test_norm_weights_combine(): + orientation_norm_weights = normalization.orientation_norm_weights(O) + scale_norm_weights = normalization.scale_norm_weights_equal(S) + norm_weights = normalization.create_normalization_weights( + O, + S, + scale_norm_weights, + orientation_norm_weights, + ) + + ground_truth = np.zeros((O, S, O, S)) + for o_prime, s_prime in np.ndindex(ground_truth.shape[:2]): + ground_truth[o_prime, s_prime, o_prime, :] = np.ones((1, S)) + + assert np.all(norm_weights == ground_truth) + +@pytest.mark.xfail +def test_normalizers(): + raise NotImplementedError() + + +def test_spatial_kernel_ODOG(): + ODOG_kernel = normalization.spatial_kernel_globalmean(shape) + A = np.ones((shape[0] * 2, shape[0] * 2)) / (shape[0] * shape[0]) + assert np.all(ODOG_kernel == A) + + +def test_spatial_avg_kernel(): + ODOG_kernel = normalization.spatial_kernel_globalmean(shape) + + img = np.random.rand(shape[0], shape[1]) + + filtered = filters.apply(img, ODOG_kernel, padval=0) + + assert np.allclose(filtered, img.mean()) + + +def test_norm_coeff(): + filters_output = np.random.rand(O, S, shape[0], shape[1]) + + scale_norm_weights = normalization.scale_norm_weights_equal(S) + orientation_norm_weights = normalization.orientation_norm_weights(O) + normalization_weights = normalization.create_normalization_weights( + O, S, scale_norm_weights, orientation_norm_weights + ) + + normalizers = normalization.normalizers(filters_output, normalization_weights) + + coeffs = np.ndarray(filters_output.shape) + kernel = normalization.spatial_kernel_globalmean(shape) + + for o, s in np.ndindex(filters_output.shape[:2]): + coeffs[o, s, ...] = normalization.norm_coeff(normalizers[o, s, ...], kernel) + + ground_truth = np.sqrt((normalizers**2).mean(axis=(2, 3)) + 1e-6) + ground_truth = np.tile(np.expand_dims(ground_truth, [2, 3]), (1, 1, shape[0], shape[1])) + + assert np.allclose(coeffs, ground_truth) From fc67d2138111cb1c31d6a2cba0bc754dbcea4c97 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 1 Feb 2024 15:19:46 +0100 Subject: [PATCH 71/79] fix(normalization): offset `eps`ilon to avoid `0`s in division, `sqrt` Since it is used to prevent DivideByZero errors, makes sense to use it in divisive_normalization() Default eps=0.0 Takes on different values in F/L/ODOG tests --- multyscale/normalization.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/multyscale/normalization.py b/multyscale/normalization.py index 619fbb4..86e274b 100644 --- a/multyscale/normalization.py +++ b/multyscale/normalization.py @@ -40,7 +40,7 @@ def normalizers(multioutput: np.ndarray, normalization_weights: np.ndarray) -> n return norms -def norm_coeff(normalizer: np.ndarray, spatial_kernel: np.ndarray) -> np.ndarray: +def norm_coeff(normalizer: np.ndarray, spatial_kernel: np.ndarray, eps=0.0) -> np.ndarray: """Construct normalization coefficient: denominator for divisive normalization Parameters @@ -49,6 +49,8 @@ def norm_coeff(normalizer: np.ndarray, spatial_kernel: np.ndarray) -> np.ndarray single 2D normalizer image; weighted combination of all filter outputs spatial_kernel : numpy.ndarray single kernel to spatially average (2D; over x,y) the normalizer + eps : float, optional + precision offset, used to avoid square-root of negative numbers, by default 0.0 Returns ------- @@ -57,11 +59,13 @@ def norm_coeff(normalizer: np.ndarray, spatial_kernel: np.ndarray) -> np.ndarray """ norm = normalizer**2 spatial_average = filters.apply(norm, spatial_kernel, padval=0) - coeff = np.sqrt(spatial_average) + coeff = np.sqrt(spatial_average + eps) return coeff -def divisive_normalization(filter_output: np.ndarray, norm_coeff: np.ndarray) -> np.ndarray: +def divisive_normalization( + filter_output: np.ndarray, norm_coeff: np.ndarray, eps=0.0 +) -> np.ndarray: """Apply divisive normalization to a single filter output Parameters @@ -70,13 +74,15 @@ def divisive_normalization(filter_output: np.ndarray, norm_coeff: np.ndarray) -> output from a single filter, 2D of shape (x,y) norm_coeff : np.ndarray single normalization coefficient: denominator for divisive normalization + eps : float, optional + precision offset, used to avoid DivideByZero errors, by default 0.0 Returns ------- np.ndarray normalized filter output, 2D of shape (x, y) """ - return filter_output / norm_coeff + return filter_output / (norm_coeff + eps) def spatial_kernel_globalmean(shape: Sequence[int]) -> np.ndarray: From b3751d4d102a0efcf31aacfb3e79b78d4a8a9f5d Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 22 Feb 2024 15:46:08 +0100 Subject: [PATCH 72/79] refactor(normalization): more consistently name normalization components Closes #4 --- .../explore_FLODOG_parameters.py | 2 +- docs/normalization/explore_LODOG_parameter.py | 22 ++++++---- docs/normalization/normalization_FLODOG.py | 21 ++++++---- docs/normalization/normalization_LODOG.py | 29 ++++++++----- docs/normalization/normalization_ODOG.py | 16 +++---- .../normalization_generalized.py | 14 +++---- multyscale/normalization.py | 42 +++++++++++-------- test/test_normalization.py | 21 ++++------ 8 files changed, 93 insertions(+), 74 deletions(-) diff --git a/docs/normalization/explore_FLODOG_parameters.py b/docs/normalization/explore_FLODOG_parameters.py index 51d66be..a909956 100644 --- a/docs/normalization/explore_FLODOG_parameters.py +++ b/docs/normalization/explore_FLODOG_parameters.py @@ -127,7 +127,7 @@ # FLODOG.normalization_weights = normalization_weights # %% Determine normalizing coefficients -normalizing_coefficients_3 = multyscale.normalization.normalizers( +normalizing_coefficients_3 = multyscale.normalization.norm_coeffs( filters_output, normalization_weights ) diff --git a/docs/normalization/explore_LODOG_parameter.py b/docs/normalization/explore_LODOG_parameter.py index bf984ed..8966e2c 100644 --- a/docs/normalization/explore_LODOG_parameter.py +++ b/docs/normalization/explore_LODOG_parameter.py @@ -109,7 +109,7 @@ # and thus also only need to be calculated once for all the parameterizations explored here. # %% -normalizing_coefficients = LODOG.normalizers(filters_output) +normalizing_coefficients = LODOG.norm_coeffs(filters_output) # %% Gaussian spatial averaging window [markdown] # The spatial averaging window in the LODOG model is @@ -128,9 +128,11 @@ assert np.all(window_sigmas == window_sigma) -spatial_filters = multyscale.normalization.spatial_avg_windows_gaussian( - LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas -) +spatial_filters = np.ndarray(filters_output.shape) +for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]): + spatial_filters[o, s, :] = multyscale.normalization.spatial_kernel_gaussian( + LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s, :] + ) plt.subplot(1, 2, 1) plt.imshow(spatial_filters[0, 0, ...], cmap="coolwarm", extent=visextent) plt.colorbar() @@ -203,10 +205,14 @@ assert np.all(LODOG.window_sigmas == LODOG.window_sigma) # %% -spatial_filters = multyscale.normalization.spatial_avg_windows_gaussian( - LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas -) -plt.subplot(1, 2, 1) +spatial_filters = np.ndarray(filters_output.shape) +for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]): + spatial_filters[o, s, :] = multyscale.normalization.spatial_kernel_gaussian( + LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s, :] + ) + +# %% +plt.subplot(1, 2, 1) plt.imshow(spatial_filters[0, 0, ...], cmap="coolwarm", extent=visextent) plt.colorbar() plt.subplot(1, 2, 2) diff --git a/docs/normalization/normalization_FLODOG.py b/docs/normalization/normalization_FLODOG.py index aa5b1aa..b63c784 100644 --- a/docs/normalization/normalization_FLODOG.py +++ b/docs/normalization/normalization_FLODOG.py @@ -165,8 +165,8 @@ # the filter outputs at these spatial scales. # %% Normalizing coefficients -normalizing_coefficients_LODOG = LODOG.normalizers(filters_output) -normalizing_coefficients_FLODOG = FLODOG.normalizers(filters_output) +normalizing_coefficients_LODOG = LODOG.norm_coeffs(filters_output) +normalizing_coefficients_FLODOG = FLODOG.norm_coeffs(filters_output) # Visualize each norm. coeff. vmin = min(np.min(normalizing_coefficients_LODOG), np.min(normalizing_coefficients_FLODOG)) @@ -215,12 +215,17 @@ # both in space, and in _spatial scale_ / _**F**requency_. # %% Spatial averaging window -spatial_windows_LODOG = multyscale.normalization.spatial_avg_windows_gaussian( - LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas -) -spatial_windows_FLODOG = multyscale.normalization.spatial_avg_windows_gaussian( - FLODOG.bank.x, FLODOG.bank.y, FLODOG.window_sigmas -) +spatial_windows_LODOG = np.ndarray(filters_output.shape) +for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]): + spatial_windows_LODOG[o, s, :] = multyscale.normalization.spatial_kernel_gaussian( + LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s, :] + ) + +spatial_windows_FLODOG = np.ndarray(filters_output.shape) +for o, s in np.ndindex(FLODOG.window_sigmas.shape[:2]): + spatial_windows_FLODOG[o, s, :] = multyscale.normalization.spatial_kernel_gaussian( + FLODOG.bank.x, FLODOG.bank.y, FLODOG.window_sigmas[o, s, :] + ) # Visualize each spatial avg. window fig, axs = plt.subplots(2, spatial_windows_LODOG.shape[1], sharex="all", sharey="all") diff --git a/docs/normalization/normalization_LODOG.py b/docs/normalization/normalization_LODOG.py index aad790b..7527611 100644 --- a/docs/normalization/normalization_LODOG.py +++ b/docs/normalization/normalization_LODOG.py @@ -261,10 +261,9 @@ assert np.array_equal(norm_weights, LODOG.normalization_weights) # %% Normalizing images as weighted combination (tensor dot-product) of filter outputs -normalizing_coefficients = multyscale.normalization.normalizers(weighted_outputs, norm_weights) +normalizing_coefficients = multyscale.normalization.norm_coeffs(weighted_outputs, norm_weights) -# Visualize each normalizing coefficient n_{o,s}, i.e. -# the normalizer image for each individual filter f_{o,s} +# Visualize each normalizing coefficient n_{o,s}, i.e. for each individual filter f_{o,s} fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") for o, s in np.ndindex(normalizing_coefficients.shape[:2]): axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent) @@ -346,24 +345,32 @@ plt.show() # %% [markdown] -# The function `multyscale.normalization.spatial_avg_windows_gaussian()` +# The function `multyscale.normalization.spatial_kernel_gaussian()` # generates a ( $O\times S$ set of) Gaussian filters $G$, -# where each Gaussian $G_{o',s'}$ is used to locally average filter $f_{o',s'}$. +# where each Gaussian spatial averaging window +# $G_{o',s'}$ is used to locally average filter $f_{o',s'}$. # In the LODOG model, all $G$ are identical. # # The parameter $\sigma$ controls the spatial size of each $G(\sigma)$ Gaussian filter; # thus this function takes in $O \times S$ $\sigma_{o', s'}$. # In the LODOG model, all $\sigma_{o', s'}$ are identical. -# %% Spatial Gaussian -sigmas = LODOG.window_sigmas -G = multyscale.normalization.spatial_avg_windows_gaussian(LODOG.bank.x, LODOG.bank.y, sigmas) +# %% All sigmas identical +print(LODOG.window_sigmas) -assert np.array_equal(G[0, 0, :], window) +# %% Generate Gaussian filters +spatial_windows = np.ndarray(filters_output.shape) +for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]): + spatial_windows[o, s, :] = multyscale.normalization.spatial_kernel_gaussian( + LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s, :] + ) + +assert np.array_equal(spatial_windows[0, 0, :], window) +assert np.array_equal(spatial_windows, LODOG.spatial_kernels()) idx = (2, 3) -plt.imshow(G[*idx], cmap="coolwarm", extent=visextent) +plt.imshow(spatial_windows[*idx], cmap="coolwarm", extent=visextent) plt.show() # %% [markdown] @@ -420,7 +427,7 @@ # get normalized quite differently. # # We now divide each filter(output) $f_{o',s'}$ -# by the spatial RMS of the normalizer coefficient $n_{o',s'}$: +# by the spatial RMS of the normalizing coefficient $n_{o',s'}$: # $$f'_{o',s'} = \frac{f_{o',s'}}{RMS(n_{o',s'})}$$ # %% Divisive normalization diff --git a/docs/normalization/normalization_ODOG.py b/docs/normalization/normalization_ODOG.py index a61ca02..08e31f3 100644 --- a/docs/normalization/normalization_ODOG.py +++ b/docs/normalization/normalization_ODOG.py @@ -244,7 +244,7 @@ ] # add this filter to normalizing coefficient # Plot each normalizing coefficient n_{o,s}, -# i.e., the normalizer image for each individual filter f_{o,s} +# i.e., for each individual filter f_{o,s} fig, axs = plt.subplots(*norm_coeffs.shape[:2], sharex="all", sharey="all") for o, s in np.ndindex(norm_coeffs.shape[:2]): axs[o, s].imshow( @@ -314,8 +314,8 @@ normalization_weights, weighted_outputs, axes=([0, 1], [0, 1]) ) -# Visualize each normalizing coefficient n_{o,s}, i.e. -# the normalizer image for each individual filter f_{o,s} +# Visualize each normalizing coefficient n_{o,s}, +# i.e., for each individual filter f_{o,s} fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") for o, s in np.ndindex(normalizing_coefficients.shape[:2]): axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent) @@ -404,15 +404,15 @@ # for constructing the normalizing coefficients # from such weights, and the filter outputs to be normalized. # -# The function `multyscale.normalization.normalizers()` creates these coefficients, +# The function `multyscale.normalization.norm_coeffs()` creates these coefficients, # and note that these are identical to the $N$ constructed above. # %% Identical normalizing coefficients -norm_coeffs = multyscale.normalization.normalizers(weighted_outputs, normalization_weights) +norm_coeffs = multyscale.normalization.norm_coeffs(weighted_outputs, normalization_weights) assert np.allclose(norm_coeffs, normalizing_coefficients) -# Visualize each normalizing coefficient n_{o,s}, i.e. -# the normalizer image for each individual filter f_{o,s} +# Visualize each normalizing coefficient n_{o,s}, +# i.e. for each individual filter f_{o,s} fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") for o, s in np.ndindex(normalizing_coefficients.shape[:2]): axs[o, s].imshow(normalizing_coefficients[o, s], cmap="coolwarm", extent=visextent) @@ -459,7 +459,7 @@ # # Thus, the normalized filter output $f'_{o', s'}$ # is calculating by dividing each filter(output) $f_{o',s'}$ -# by the energy of the normalizer coefficient $n_{o',s'}$: +# by the energy of the normalizing coefficient $n_{o',s'}$: # $$f'_{o',s'} = \frac{f_{o',s'}}{RMS(n_{o',s'})}$$ # %% Divisive normalization diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index 9224811..7186a2a 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -224,7 +224,7 @@ # to create the normalizing coefficients $\mathbf{n}$ # %% Normalizing coefficients -normalizing_coefficients = multyscale.normalization.normalizers(filters_output, interaction_weights) +normalizing_coefficients = multyscale.normalization.norm_coeffs(filters_output, interaction_weights) # Visualize each normalizing coefficient n_{o,s}, i.e. # the normalizer image for each individual filter f_{o,s} @@ -290,8 +290,8 @@ # the filter outputs at these spatial scales. # %% Normalizing coefficients -normalizing_coefficients_LODOG = LODOG.normalizers(filters_output) -normalizing_coefficients_FLODOG = FLODOG.normalizers(filters_output) +normalizing_coefficients_LODOG = LODOG.norm_coeffs(filters_output) +normalizing_coefficients_FLODOG = FLODOG.norm_coeffs(filters_output) # Visualize each norm. coeff. vmin = min(np.min(normalizing_coefficients_LODOG), np.min(normalizing_coefficients_FLODOG)) @@ -359,7 +359,7 @@ # This results in a single energy estimate for each $(o', s')$. # %% Global image RMS -ODOG_normalizers = ODOG.normalizers(filters_output) +ODOG_norm_coeffs = ODOG.norm_coeffs(filters_output) ODOG_energies = ODOG.normalizers_to_RMS(ODOG_normalizers) print(ODOG_energies.shape) @@ -511,10 +511,10 @@ # %% # have doubly sized kernel so that pixel reaches each pixel in the average -A = np.ones((1024 * 2, 1024 * 2)) / 1024**2 +spatial_kernel = np.ones((1024 * 2, 1024 * 2)) / 1024**2 img = filters_output[3, 4] -mean_filtered = multyscale.filters.apply(img, A, padval=0) +mean_filtered = multyscale.filters.apply(img, spatial_kernel, padval=0) assert np.allclose(mean_filtered, img.mean()) @@ -550,7 +550,7 @@ # calculate energy using the kernel ODOG_energies_i2 = np.ndarray(filters_output.shape) for o, s in np.ndindex(filters_output.shape[:2]): - norm = ODOG_normalizers[o, s, ...] ** 2 + norm = ODOG_norm_coeffs[o, s, ...] ** 2 mean = multyscale.filters.apply(norm, A, padval=0) ODOG_energies_i2[o, s] = np.sqrt(mean) diff --git a/multyscale/normalization.py b/multyscale/normalization.py index 86e274b..dcf1022 100644 --- a/multyscale/normalization.py +++ b/multyscale/normalization.py @@ -9,8 +9,8 @@ # Generalized (F)(L)ODOG normalization -def normalizers(multioutput: np.ndarray, normalization_weights: np.ndarray) -> np.ndarray: - """Construct all normalizers: weighted combination of all fitler outputs, for each filter +def norm_coeffs(multioutput: np.ndarray, normalization_weights: np.ndarray) -> np.ndarray: + """Construct all normalizing coefficients: weighted combination of all filter outputs, for each filter Parameters ---------- @@ -22,7 +22,7 @@ def normalizers(multioutput: np.ndarray, normalization_weights: np.ndarray) -> n Returns ------- numpy.ndarray - all (O, S, X, Y) normalizers: + all (O, S, X, Y) normalizing coefficients: a single 2D (X,Y) weighted combination of all filter outputs per (O, S) filter-to-normalize """ @@ -33,34 +33,35 @@ def normalizers(multioutput: np.ndarray, normalization_weights: np.ndarray) -> n weights = normalization_weights[o, s] # Tensor dot: multiply filters_output by weights, then sum over axes [0,1] - normalizer = np.tensordot(multioutput, weights, axes=([0, 1], [0, 1])) + norm_coeff = np.tensordot(multioutput, weights, axes=([0, 1], [0, 1])) + # Accumulate - norms[o, s, ...] = normalizer + norms[o, s, ...] = norm_coeff return norms -def norm_coeff(normalizer: np.ndarray, spatial_kernel: np.ndarray, eps=0.0) -> np.ndarray: - """Construct normalization coefficient: denominator for divisive normalization +def norm_energy(norm_coeff: np.ndarray, spatial_kernel: np.ndarray, eps=0.0) -> np.ndarray: + """Convert normalizing coefficient to energy (denominator for divisive normalization) Parameters ---------- - normalizer : numpy.ndarray - single 2D normalizer image; weighted combination of all filter outputs + norm_coeff : numpy.ndarray + single 2D normalizing coefficient; weighted combination of all filter outputs spatial_kernel : numpy.ndarray - single kernel to spatially average (2D; over x,y) the normalizer + single kernel to spatially average (2D; over x,y) the normalizing coefficient eps : float, optional precision offset, used to avoid square-root of negative numbers, by default 0.0 Returns ------- numpy.ndarray - single normalization coefficient: denominator for divisive normalization + single normalizing energy: denominator for divisive normalization """ - norm = normalizer**2 + norm = norm_coeff**2 spatial_average = filters.apply(norm, spatial_kernel, padval=0) - coeff = np.sqrt(spatial_average + eps) - return coeff + energy = np.sqrt(spatial_average + eps) + return energy def divisive_normalization( @@ -71,16 +72,18 @@ def divisive_normalization( Parameters ---------- filter_output : np.ndarray - output from a single filter, 2D of shape (x,y) + output from either a single filter, 2D of shape (Y, X), + or a whole filterbank, N-Dimensional of shape (M, ..., N, Y, X) norm_coeff : np.ndarray - single normalization coefficient: denominator for divisive normalization + normalizing coefficient(s): denominator for divisive normalization. + Must be of same shape as filter_output eps : float, optional precision offset, used to avoid DivideByZero errors, by default 0.0 Returns ------- np.ndarray - normalized filter output, 2D of shape (x, y) + normalized filter output, 2D of shape (y, x) """ return filter_output / (norm_coeff + eps) @@ -98,7 +101,10 @@ def spatial_kernel_globalmean(shape: Sequence[int]) -> np.ndarray: numpy.ndarray spatial averaging kernel """ - return np.ones([dim * 2 for dim in shape]) / np.prod(shape) + kernel = np.ones([dim * 2 for dim in shape]) + kernel /= kernel.sum() + kernel *= 4 + return kernel def spatial_kernel_gaussian(x: np.ndarray, y: np.ndarray, sigmas: Sequence[float]) -> np.ndarray: diff --git a/test/test_normalization.py b/test/test_normalization.py index f655b9a..e35cf5e 100644 --- a/test/test_normalization.py +++ b/test/test_normalization.py @@ -41,17 +41,12 @@ def test_norm_weights_combine(): assert np.all(norm_weights == ground_truth) + @pytest.mark.xfail -def test_normalizers(): +def test_norm_coeffs(): raise NotImplementedError() -def test_spatial_kernel_ODOG(): - ODOG_kernel = normalization.spatial_kernel_globalmean(shape) - A = np.ones((shape[0] * 2, shape[0] * 2)) / (shape[0] * shape[0]) - assert np.all(ODOG_kernel == A) - - def test_spatial_avg_kernel(): ODOG_kernel = normalization.spatial_kernel_globalmean(shape) @@ -62,7 +57,7 @@ def test_spatial_avg_kernel(): assert np.allclose(filtered, img.mean()) -def test_norm_coeff(): +def test_norm_energy(): filters_output = np.random.rand(O, S, shape[0], shape[1]) scale_norm_weights = normalization.scale_norm_weights_equal(S) @@ -71,15 +66,15 @@ def test_norm_coeff(): O, S, scale_norm_weights, orientation_norm_weights ) - normalizers = normalization.normalizers(filters_output, normalization_weights) + norm_coeffs = normalization.norm_coeffs(filters_output, normalization_weights) - coeffs = np.ndarray(filters_output.shape) + energies = np.ndarray(filters_output.shape) kernel = normalization.spatial_kernel_globalmean(shape) for o, s in np.ndindex(filters_output.shape[:2]): - coeffs[o, s, ...] = normalization.norm_coeff(normalizers[o, s, ...], kernel) + energies[o, s, ...] = normalization.norm_energy(norm_coeffs[o, s, ...], kernel) - ground_truth = np.sqrt((normalizers**2).mean(axis=(2, 3)) + 1e-6) + ground_truth = np.sqrt((norm_coeffs**2).mean(axis=(2, 3)) + 1e-6) ground_truth = np.tile(np.expand_dims(ground_truth, [2, 3]), (1, 1, shape[0], shape[1])) - assert np.allclose(coeffs, ground_truth) + assert np.allclose(energies, ground_truth) From 974bcdd4379ecd26f7ad7709e63bd181187a873e Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 22 Feb 2024 15:44:43 +0100 Subject: [PATCH 73/79] refactor(normalization): normalizing coefficients in single tensor dot --- multyscale/normalization.py | 44 ++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/multyscale/normalization.py b/multyscale/normalization.py index dcf1022..5446edc 100644 --- a/multyscale/normalization.py +++ b/multyscale/normalization.py @@ -12,31 +12,45 @@ def norm_coeffs(multioutput: np.ndarray, normalization_weights: np.ndarray) -> np.ndarray: """Construct all normalizing coefficients: weighted combination of all filter outputs, for each filter + The multi(dimensional)output consists of all (O, S) filter outputs, + where each is of dimension (Y, X). + Thus, the multioutput has dimension (O, S, Y, X). + + The output will be one normalizing coefficient, a matrix of (Y, X) pixels, + for each filter-to-normalize f'(o', s'). + Thus, the norm_coeffs output also has dimension (O', S', Y, X). + + To create these, for each filter-to-normalize f'(o', s'), + there should be (O, S) weights provide: + one for how each filter is weighted in this normalizing coefficient. + Thus, the normalization weights are of dimension (O', S', O, S): + for each (o',s') filter-to-normalize, there are (O, S) weights. + Parameters ---------- multioutput : numpy.ndarray - all (O, S, X, Y) filter outputs + all (O, S, Y, X) filter outputs normalization_weights : numpy.ndarray - full tensor (O, S, O, S) of normalization weights + full tensor (O', S', O, S) of normalization weights Returns ------- numpy.ndarray - all (O, S, X, Y) normalizing coefficients: - a single 2D (X,Y) weighted combination of all filter outputs - per (O, S) filter-to-normalize + all (O', S', Y, X) normalizing coefficients: + a single 2D (Y, X) weighted combination of all filter outputs + per (O', S') filter-to-normalize """ - # Create normalizing images from weighted combination of filter outputs - norms: np.ndarray = np.ndarray(shape=multioutput.shape) - for o, s in np.ndindex(multioutput.shape[:2]): - weights = normalization_weights[o, s] - - # Tensor dot: multiply filters_output by weights, then sum over axes [0,1] - norm_coeff = np.tensordot(multioutput, weights, axes=([0, 1], [0, 1])) - - # Accumulate - norms[o, s, ...] = norm_coeff + # Create all normalizing coefficients from weighted combination of filter outputs + # This is done as a single tensor dot-product operation. + # Multiply the (O', S', O, S) weights, by the (O, S, Y, X) filter outputs, + # then sum over the (O, S) dimensions. + # In Einstein summation notation (and q := o', r := s'), that is: + # + # norms = np.einsum('qros, osyx -> qryx', normalization_weights, multioutput) + # + # which in tensordot syntax is: + norms = np.tensordot(normalization_weights, multioutput, axes=([2, 3], [0, 1])) return norms From 8f539087a30725031391da9f854ba745976b7051 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Wed, 21 Feb 2024 19:34:46 +0100 Subject: [PATCH 74/79] refactor(models): use generalized normalization feat(models): `norm_energies` method to replace `normalizers_to_RMS` --- .../explore_FLODOG_parameters.py | 2 +- docs/normalization/explore_LODOG_parameter.py | 6 +- docs/normalization/normalization_FLODOG.py | 4 +- docs/normalization/normalization_LODOG.py | 23 +++--- .../normalization_generalized.py | 34 ++++---- multyscale/models.py | 79 ++++++++++--------- 6 files changed, 76 insertions(+), 72 deletions(-) diff --git a/docs/normalization/explore_FLODOG_parameters.py b/docs/normalization/explore_FLODOG_parameters.py index a909956..4c895af 100644 --- a/docs/normalization/explore_FLODOG_parameters.py +++ b/docs/normalization/explore_FLODOG_parameters.py @@ -169,7 +169,7 @@ FLODOG.window_sigmas = window_sigmas # Apply spatial averaging windows to normalizing coefficients -energies_2_3 = FLODOG.normalizers_to_RMS(normalizing_coefficients_3) +energies_2_3 = FLODOG.norm_energies(normalizing_coefficients_3) # Visualize each energy estimate fig, axs = plt.subplots(*energies_2_3.shape[:2], sharex="all", sharey="all") diff --git a/docs/normalization/explore_LODOG_parameter.py b/docs/normalization/explore_LODOG_parameter.py index 8966e2c..a381b4e 100644 --- a/docs/normalization/explore_LODOG_parameter.py +++ b/docs/normalization/explore_LODOG_parameter.py @@ -149,13 +149,13 @@ plt.show() # %% [markdown] -# The model method `.normalizers_to_RMS()` automatically generates and applies +# The model method `.norm_energies()` automatically generates and applies # these spatial averaging windows to the proved normalizing coeffiencts. # This produces an $O \times S$ set of locally ($Y \times X$) calculated energies, # one for each normalizaing coefficient. # %% -energies_4 = LODOG.normalizers_to_RMS(normalizing_coefficients) +energies_4 = LODOG.norm_energies(normalizing_coefficients) # Visualize each local energy fig, axs = plt.subplots(*energies_4.shape[:2], sharex="all", sharey="all") @@ -232,7 +232,7 @@ # more locally restricted (smaller spatial averaging window) energies # %% -energies_1 = LODOG.normalizers_to_RMS(normalizing_coefficients) +energies_1 = LODOG.norm_energies(normalizing_coefficients) # Visualize each local energy fig, axs = plt.subplots(*energies_4.shape[:2], sharex="all", sharey="all") diff --git a/docs/normalization/normalization_FLODOG.py b/docs/normalization/normalization_FLODOG.py index b63c784..02dba20 100644 --- a/docs/normalization/normalization_FLODOG.py +++ b/docs/normalization/normalization_FLODOG.py @@ -249,8 +249,8 @@ # that form the denominators of the normalization # %% Energy estimates -energies_LODOG = LODOG.normalizers_to_RMS(normalizing_coefficients_LODOG) -energies_FLODOG = FLODOG.normalizers_to_RMS(normalizing_coefficients_FLODOG) +energies_LODOG = LODOG.norm_energies(normalizing_coefficients_LODOG, eps=1e-6) +energies_FLODOG = FLODOG.norm_energies(normalizing_coefficients_FLODOG, eps=1e-6) # Visualize vmin = min(np.min(energies_LODOG), np.min(energies_FLODOG)) diff --git a/docs/normalization/normalization_LODOG.py b/docs/normalization/normalization_LODOG.py index 7527611..397e474 100644 --- a/docs/normalization/normalization_LODOG.py +++ b/docs/normalization/normalization_LODOG.py @@ -376,23 +376,22 @@ # %% [markdown] # Applying this Gaussian window gives the _local_ (estimate of) of energy -# %% Local RMS -normalization_local_RMS = np.square(normalizing_coefficients.copy()) +# %% Local energy estimates +normalization_local_energies = np.ndarray(normalizing_coefficients.shape) for o, s in np.ndindex(normalizing_coefficients.shape[:2]): - normalization_local_RMS[o, s] = multyscale.filters.apply( - window, normalization_local_RMS[o, s], padval=0 + coeff = normalizing_coefficients[o, s] ** 2 + energy = multyscale.filters.apply( + spatial_windows[o, s], coeff, padval=0 ) + energy = np.sqrt(energy + 1e-6) # minor offset to avoid negatives/0's + normalization_local_energies[o, s, :] = energy -normalization_local_RMS = ( - np.sqrt(normalization_local_RMS + 1e-6) + 1e-6 -) # minor offset to avoid negatives/0's - -assert np.array_equal(normalization_local_RMS, LODOG.normalizers_to_RMS(normalizing_coefficients)) +assert np.allclose(normalization_local_energies, LODOG.norm_energies(normalizing_coefficients, eps=1e-6)) # Visualize each local RMS -fig, axs = plt.subplots(*normalization_local_RMS.shape[:2], sharex="all", sharey="all") -for o, s in np.ndindex(normalization_local_RMS.shape[:2]): - axs[o, s].imshow(normalization_local_RMS[o, s], cmap="coolwarm", extent=visextent) +fig, axs = plt.subplots(*normalization_local_energies.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(normalization_local_energies.shape[:2]): + axs[o, s].imshow(normalization_local_energies[o, s], cmap="coolwarm", extent=visextent) fig.supxlabel("Spatial scale/freq. $s'$") fig.supylabel("Orientation $o'$") plt.show() diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index 7186a2a..ad59e85 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -360,7 +360,7 @@ # %% Global image RMS ODOG_norm_coeffs = ODOG.norm_coeffs(filters_output) -ODOG_energies = ODOG.normalizers_to_RMS(ODOG_normalizers) +ODOG_energies = ODOG_norm_coeffs.mean(axis=-1).mean(axis=-1) print(ODOG_energies.shape) # Visualise @@ -443,29 +443,33 @@ # # %% Spatial Gaussian -sigmas = LODOG.window_sigmas -G = multyscale.normalization.spatial_avg_windows_gaussian(LODOG.bank.x, LODOG.bank.y, sigmas) +spatial_kernels = np.ndarray(filters_output.shape) +for o, s in np.ndindex(LODOG.window_sigmas.shape[:2]): + spatial_kernels[o, s, :] = multyscale.normalization.spatial_kernel_gaussian( + LODOG.bank.x, LODOG.bank.y, LODOG.window_sigmas[o, s] + ) + +assert np.array_equal(spatial_kernels, LODOG.spatial_kernels()) # %% [markdown] # Applying this Gaussian window gives the _local_ (estimate of) of energy # %% Local RMS -normalization_local_energy = np.square(normalizing_coefficients.copy()) -for o, s in np.ndindex(normalizing_coefficients.shape[:2]): - normalization_local_energy[o, s] = multyscale.filters.apply( - G[o, s, :], normalization_local_energy[o, s], padval=0 +local_energies = np.ndarray(normalizing_coefficients_LODOG.shape) +for o, s in np.ndindex(normalizing_coefficients_LODOG.shape[:2]): + norm = normalizing_coefficients_LODOG[o, s] + norm = norm ** 2 + local_avg = multyscale.filters.apply( + norm, spatial_kernels[o, s], padval=0 ) + local_energies[o, s] = np.sqrt(local_avg + 1e-6) # minor offset to avoid negatives/0's -normalization_local_energy = ( - np.sqrt(normalization_local_energy + 1e-6) + 1e-6 -) # minor offset to avoid negatives/0's - -# assert np.array_equal(normalization_local_energy, LODOG.normalizers_to_RMS(normalizing_coefficients)) +assert np.allclose(local_energies, LODOG.norm_energies(normalizing_coefficients_LODOG, eps=1e-6)) # Visualize each local RMS -fig, axs = plt.subplots(*normalization_local_energy.shape[:2], sharex="all", sharey="all") -for o, s in np.ndindex(normalization_local_energy.shape[:2]): - axs[o, s].imshow(normalization_local_energy[o, s], cmap="coolwarm", extent=visextent) +fig, axs = plt.subplots(*local_energies.shape[:2], sharex="all", sharey="all") +for o, s in np.ndindex(local_energies.shape[:2]): + axs[o, s].imshow(local_energies[o, s], cmap="coolwarm", extent=visextent) fig.supxlabel("Spatial scale/freq. $s'$") fig.supylabel("Orientation $o'$") plt.show() diff --git a/multyscale/models.py b/multyscale/models.py index e5f8afc..fadebd0 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -2,7 +2,7 @@ import numpy as np # Local application imports -from . import filterbanks, filters, normalization +from . import filterbanks, normalization # TODO: refactor filter-output datastructures @@ -55,34 +55,41 @@ def __init__(self, shape, visextent): def weight_outputs(self, filters_output): return filterbanks.weight_oriented_multiscale_outputs(filters_output, self.scale_weights) - def normalizers(self, filters_output): - # Get normalizers - normalizers = normalization.normalizers(filters_output, self.normalization_weights) - return normalizers - - def normalizers_to_RMS(self, normalizers): - # Get RMS from each normalizer - spatial_avg_filters = normalization.spatial_avg_windows_globalmean(normalizers) - normalizers_RMS = normalizers.copy() - normalizers_RMS = np.square(normalizers_RMS) - normalizers_RMS = spatial_avg_filters * normalizers_RMS - normalizers_RMS = normalizers_RMS.sum(axis=(-1, -2)) - normalizers_RMS = np.sqrt(normalizers_RMS) - return normalizers_RMS - - def normalize_outputs(self, filters_output): + def norm_coeffs(self, filters_output): + return normalization.norm_coeffs(filters_output, self.normalization_weights) + + def spatial_kernels(self): + kernel = normalization.spatial_kernel_globalmean(self.bank.shape[2:]) + kernels = np.ndarray(self.bank.shape[:2] + kernel.shape) + for o, s in np.ndindex(kernels.shape[:2]): + kernels[o, s] = kernel + + return kernels + + def norm_energies(self, norm_coeffs, eps=0.0): + kernels = self.spatial_kernels() + + norm_energies = np.ndarray(norm_coeffs.shape) + for o_prime, s_prime in np.ndindex(norm_coeffs.shape[:2]): + norm_energies[o_prime, s_prime] = normalization.norm_energy( + norm_coeffs[o_prime, s_prime], kernels[o_prime, s_prime], eps=eps + ) + + return norm_energies + + def normalize_outputs(self, filters_output, eps=0.0): # TODO: docstring - normalizers = self.normalizers(filters_output) + norm_coeffs = self.norm_coeffs(filters_output) - normalizer_RMS = self.normalizers_to_RMS(normalizers) + norm_energies = self.norm_energies(norm_coeffs, eps=eps) - normalized_outputs = np.ndarray(filters_output.shape) - for o, s in np.ndindex(filters_output.shape[:2]): - normalized_outputs[o, s] = filters_output[o, s] / normalizer_RMS[o, s] + normalized_outputs = normalization.divisive_normalization( + filters_output, norm_energies, eps=eps + ) return normalized_outputs - def apply(self, image): + def apply(self, image, eps=0.0): # TODO: docstring # Sum over spatial scales @@ -90,10 +97,11 @@ def apply(self, image): weighted_outputs = self.weight_outputs(filters_output) # Normalize oriented multiscale outputs - normalized_outputs = self.normalize_outputs(weighted_outputs) + normalized_outputs = self.normalize_outputs(weighted_outputs, eps=eps) # Sum over orientations and scales output = normalized_outputs.sum((0, 1)) + return output @@ -106,22 +114,15 @@ def __init__(self, shape, visextent, window_sigma=4): self.window_sigma = window_sigma self.window_sigmas = np.ones(shape=(*self.bank.shape[:2], 2)) * self.window_sigma - def normalizers_to_RMS(self, normalizers): - # Expand sigmas - # Get RMS from each normalizer - spatial_avg_filters = normalization.spatial_avg_windows_gaussian( - self.bank.x, self.bank.y, self.window_sigmas - ) - normalizers_RMS = normalizers.copy() - normalizers_RMS = np.square(normalizers_RMS) - for o, s in np.ndindex(normalizers_RMS.shape[:2]): - normalizers_RMS[o, s] = filters.apply( - spatial_avg_filters[o, s], normalizers_RMS[o, s], padval=0 + def spatial_kernels(self): + kernels = np.ndarray(self.bank.shape) + for o_prime, s_prime in np.ndindex(self.window_sigmas.shape[:2]): + kernels[o_prime, s_prime] = normalization.spatial_kernel_gaussian( + self.bank.x, + self.bank.y, + sigmas=self.window_sigmas[o_prime, s_prime], ) - normalizers_RMS += 1e-6 - normalizers_RMS = np.sqrt(normalizers_RMS) - normalizers_RMS += 1e-6 - return normalizers_RMS + return kernels class FLODOG_RHS2007(LODOG_RHS2007): From dff5bade31338c61f5a646f0a70060c2e25a0ab3 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Thu, 22 Feb 2024 12:38:36 +0100 Subject: [PATCH 75/79] test(models): update to use generalized normalization --- test/test_FLODOG.py | 32 ++++++++++---------------------- test/test_LODOG.py | 31 ++++++++++--------------------- test/test_ODOG.py | 6 ++++-- test/test_models.py | 4 ++-- 4 files changed, 26 insertions(+), 47 deletions(-) diff --git a/test/test_FLODOG.py b/test/test_FLODOG.py index 2f71713..d31c8f1 100644 --- a/test/test_FLODOG.py +++ b/test/test_FLODOG.py @@ -39,34 +39,22 @@ def test_scale_norm_weights(model, MATLAB_FLODOG_params): assert np.allclose(RHS_weights, model.scale_norm_weights) -def test_normalizers(model, MATLAB_filteroutput, MATLAB_FLODOG_params): +def test_norm_coeffs(model, MATLAB_filteroutput, MATLAB_FLODOG_params): weighted_outputs = model.weight_outputs(MATLAB_filteroutput) - normalizers = model.normalizers(weighted_outputs) + norm_coeffs = model.norm_coeffs(weighted_outputs) RHS_norms = RHS_implementation.FLODOG_normalizers( weighted_outputs, sdmix=MATLAB_FLODOG_params["sdmix"] ) - assert np.allclose(normalizers.shape, RHS_norms.shape) - assert np.allclose(normalizers, RHS_norms) + assert np.allclose(norm_coeffs.shape, RHS_norms.shape) + assert np.allclose(norm_coeffs, RHS_norms) def test_spatial_masks(model, MATLAB_FLODOG_params): # Is the spatial (Gaussian) averaging window the same? - masks = RHS_implementation.FLODOG_masks(sigx=MATLAB_FLODOG_params["sigx"]) - spatial_avg_filters = multyscale.normalization.spatial_avg_windows_gaussian( - model.bank.x, model.bank.y, model.window_sigmas - ) - assert np.allclose(spatial_avg_filters, masks) - - -def test_FLODOG_RMS(model, MATLAB_filteroutput, MATLAB_FLODOG_params): - weighted_outputs = RHS_implementation.weight(MATLAB_filteroutput) - normalizers = RHS_implementation.FLODOG_normalizers( - weighted_outputs, sdmix=MATLAB_FLODOG_params["sdmix"] - ) - RHS_RMSs = RHS_implementation.FLODOG_RMSs(normalizers, sigx=MATLAB_FLODOG_params["sigx"]) - - RMSs = model.normalizers_to_RMS(normalizers) - assert np.allclose(RMSs, RHS_RMSs) + RHS_kernels = RHS_implementation.FLODOG_masks(sigx=MATLAB_FLODOG_params["sigx"]) + spatial_kernels = model.spatial_kernels() + for o, s in np.ndindex(spatial_kernels.shape[:2]): + assert np.allclose(spatial_kernels[o, s], RHS_kernels[o, s]) def test_normalized_outputs(model, MATLAB_filteroutput, MATLAB_FLODOG_params): @@ -77,11 +65,11 @@ def test_normalized_outputs(model, MATLAB_filteroutput, MATLAB_FLODOG_params): sdmix=MATLAB_FLODOG_params["sdmix"], ) - normed_outputs = model.normalize_outputs(weighted_outputs) + normed_outputs = model.normalize_outputs(weighted_outputs, eps=1e-6) assert np.allclose(normed_outputs, RHS_normalized_outputs) def test_model_output(output_FLODOG_MATLAB, model, stimulus): - output = model.apply(stimulus) + output = model.apply(stimulus, eps=1e-6) assert np.allclose(output, output_FLODOG_MATLAB) diff --git a/test/test_LODOG.py b/test/test_LODOG.py index c74547c..6a450d2 100644 --- a/test/test_LODOG.py +++ b/test/test_LODOG.py @@ -32,37 +32,26 @@ def test_weights(model): assert np.allclose(model.scale_weights, RHS_implementation.w_val) -def test_normalizers(model, MATLAB_filteroutput): +def test_norm_coeffs(model, MATLAB_filteroutput): weighted_outputs = model.weight_outputs(MATLAB_filteroutput) - normalizers = model.normalizers(weighted_outputs) + norm_coeffs = model.norm_coeffs(weighted_outputs) RHS_norms = RHS_implementation.LODOG_normalizers(weighted_outputs) - assert np.allclose(normalizers.shape, RHS_norms.shape) - assert np.allclose(normalizers, RHS_norms) + assert np.allclose(norm_coeffs.shape, RHS_norms.shape) + assert np.allclose(norm_coeffs, RHS_norms) def test_spatial_mask(model, MATLAB_LODOG_params): # Is the spatial (Gaussian) averaging window the same? - RHS_mask = RHS_implementation.LODOG_mask(sig1=MATLAB_LODOG_params["sig1"]) - spatial_avg_filters = multyscale.normalization.spatial_avg_windows_gaussian( - model.bank.x, model.bank.y, model.window_sigmas - ) - for o, s in np.ndindex(spatial_avg_filters.shape[:2]): - assert np.allclose(spatial_avg_filters[o, s], RHS_mask) - - -def test_LODOG_RMS(model, MATLAB_filteroutput, MATLAB_LODOG_params): - weighted_outputs = model.weight_outputs(MATLAB_filteroutput) - normalizers = model.normalizers(weighted_outputs) - - RMSs = model.normalizers_to_RMS(normalizers) - RHS_RMSs = RHS_implementation.LODOG_RMSs(normalizers, sig1=MATLAB_LODOG_params["sig1"]) - assert np.allclose(RMSs, RHS_RMSs) + RHS_kernel = RHS_implementation.LODOG_mask(sig1=MATLAB_LODOG_params["sig1"]) + spatial_kernels = model.spatial_kernels() + for o, s in np.ndindex(spatial_kernels.shape[:2]): + assert np.allclose(spatial_kernels[o, s], RHS_kernel) def test_normalized_outputs(model, MATLAB_filteroutput, MATLAB_LODOG_params): weighted_outputs = model.weight_outputs(MATLAB_filteroutput) - normed_outputs = model.normalize_outputs(weighted_outputs) + normed_outputs = model.normalize_outputs(weighted_outputs, eps=1e-6) RHS_normalized_outputs = RHS_implementation.LODOG_normalize( weighted_outputs, sig1=MATLAB_LODOG_params["sig1"] @@ -71,5 +60,5 @@ def test_normalized_outputs(model, MATLAB_filteroutput, MATLAB_LODOG_params): def test_model_output(output_LODOG_MATLAB, model, stimulus): - output = model.apply(stimulus) + output = model.apply(stimulus, eps=1e-6) assert np.allclose(output, output_LODOG_MATLAB) diff --git a/test/test_ODOG.py b/test/test_ODOG.py index eb6c351..0f8cfd7 100644 --- a/test/test_ODOG.py +++ b/test/test_ODOG.py @@ -29,9 +29,11 @@ def test_weights(model): def test_normalize(output_ODOG_MATLAB, model, MATLAB_filteroutput): - filter_outputs = model.weight_outputs(MATLAB_filteroutput) - normed_outputs = model.normalize_outputs(filter_outputs) + weighted_outputs = model.weight_outputs(MATLAB_filteroutput) + normed_outputs = model.normalize_outputs(weighted_outputs) + output = np.sum(normed_outputs, (0, 1)) + assert np.allclose(output, output_ODOG_MATLAB) diff --git a/test/test_models.py b/test/test_models.py index 4d76ed1..84cf393 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -22,7 +22,7 @@ def test_lodog_output(stimulus, output_LODOG_MATLAB, MATLAB_LODOG_params): visextent, window_sigma=MATLAB_LODOG_params["sig1"] / 32, ) - output = model.apply(stimulus) + output = model.apply(stimulus, eps=1e-6) assert np.allclose(output, output_LODOG_MATLAB) @@ -33,5 +33,5 @@ def test_flodog_output(stimulus, output_FLODOG_MATLAB, MATLAB_FLODOG_params): sdmix=MATLAB_FLODOG_params["sdmix"], spatial_window_scalar=MATLAB_FLODOG_params["sigx"], ) - output = model.apply(stimulus) + output = model.apply(stimulus, eps=1e-6) assert np.allclose(output, output_FLODOG_MATLAB) From aeffbcfbe5023597b397bb1c8af59b766a53c30f Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Wed, 21 Feb 2024 20:01:40 +0100 Subject: [PATCH 76/79] docs(normalization): use `eps` --- .../explore_FLODOG_parameters.py | 35 ++++++++++++++----- docs/normalization/explore_LODOG_parameter.py | 6 ++-- docs/normalization/normalization_LODOG.py | 9 ++--- .../normalization_generalized.py | 2 +- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/docs/normalization/explore_FLODOG_parameters.py b/docs/normalization/explore_FLODOG_parameters.py index 4c895af..d18e9a2 100644 --- a/docs/normalization/explore_FLODOG_parameters.py +++ b/docs/normalization/explore_FLODOG_parameters.py @@ -1,3 +1,20 @@ +# -*- coding: utf-8 -*- +# --- +# jupyter: +# jupytext: +# cell_metadata_filter: sdmix,incorrectly_encoded_metadata,spatial_window_scalar,size,title,-all +# custom_cell_magics: kql +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.11.2 +# kernelspec: +# display_name: multyscale +# language: python +# name: python3 +# --- + # %% [markdown] # # Exploring FLODOG normalization parameter # This guide describes how to change the normalization parameters @@ -86,7 +103,7 @@ plt.show() # %% Output default model -normalized_4_05 = FLODOG.normalize_outputs(filters_output) +normalized_4_05 = FLODOG.normalize_outputs(filters_output, eps=1e-6) output_4_05 = np.sum(normalized_4_05, axis=(0, 1)) # %% [markdown] @@ -153,11 +170,11 @@ # (the $\sigma$ of the 2D Gaussian) # to the spatial scale of the filter being normalized. # This is controlled by a parameter `spatial_window_scalar`: -# $\sigma = \mathrm{spatial_window_scalar} \times s$. +# $\sigma = \texttt{spatial window scalar} \times s$. # To explore how, here we apply various parameterizations of this normalization # to the same stimulus image and corresponding (weighted) filter outputs. -# %% FLODOG with spatial_window_scalar = 2 +# %% FLODOG with spatial_window_scalar=2 # Set parameter spatial_window_scalar = 2 # FLODOG.spatial_window_scalar = spaial_window_scalar @@ -169,7 +186,7 @@ FLODOG.window_sigmas = window_sigmas # Apply spatial averaging windows to normalizing coefficients -energies_2_3 = FLODOG.norm_energies(normalizing_coefficients_3) +energies_2_3 = FLODOG.norm_energies(normalizing_coefficients_3, eps=1e-6) # Visualize each energy estimate fig, axs = plt.subplots(*energies_2_3.shape[:2], sharex="all", sharey="all") @@ -186,7 +203,7 @@ plt.show() # %% Output -normalized_2_3 = filters_output / energies_2_3 +normalized_2_3 = filters_output / (energies_2_3 + 1e-6) output_2_3 = np.sum(normalized_2_3, axis=(0, 1)) # %% Comparing FLODOG outputs @@ -233,7 +250,7 @@ # to normalize the filter outputs accordingly. # %% Normalize -normalized_4_3 = FLODOG_4_3.normalize_outputs(filters_output) +normalized_4_3 = FLODOG_4_3.normalize_outputs(filters_output, eps=1e-6) # %% [markdown] # To then readout the final model prediction, @@ -291,11 +308,11 @@ # ) # %% Outputs -output_2_05 = FLODOG_2_05.normalize_outputs(filters_output).sum((0, 1)) +output_2_05 = FLODOG_2_05.normalize_outputs(filters_output, eps=1e-6).sum((0, 1)) # %% LODOG, $\sigma=4$ LODOG = multyscale.models.LODOG_RHS2007(shape=stimulus.shape, visextent=visextent, window_sigma=4) -output_LODOG_4 = LODOG.normalize_outputs(filters_output).sum((0, 1)) +output_LODOG_4 = LODOG.normalize_outputs(filters_output, eps=1e-6).sum((0, 1)) # %% [markdown] @@ -312,7 +329,7 @@ # - subtract the two means # - divide the whole model output by this difference -# %% Standardize such that effect size = 1 +# %% Standardize such that effect size=1 mask = np.load("example_stimulus_mask.npy") diff --git a/docs/normalization/explore_LODOG_parameter.py b/docs/normalization/explore_LODOG_parameter.py index a381b4e..ba77ea5 100644 --- a/docs/normalization/explore_LODOG_parameter.py +++ b/docs/normalization/explore_LODOG_parameter.py @@ -155,7 +155,7 @@ # one for each normalizaing coefficient. # %% -energies_4 = LODOG.norm_energies(normalizing_coefficients) +energies_4 = LODOG.norm_energies(normalizing_coefficients, eps=1e-6) # Visualize each local energy fig, axs = plt.subplots(*energies_4.shape[:2], sharex="all", sharey="all") @@ -232,7 +232,7 @@ # more locally restricted (smaller spatial averaging window) energies # %% -energies_1 = LODOG.norm_energies(normalizing_coefficients) +energies_1 = LODOG.norm_energies(normalizing_coefficients, eps=1e-6) # Visualize each local energy fig, axs = plt.subplots(*energies_4.shape[:2], sharex="all", sharey="all") @@ -248,7 +248,7 @@ # as would be expected. # %% -normalized_outputs_1 = filters_output / energies_1 +normalized_outputs_1 = filters_output / (energies_1 + 1e-6) # Visualize each normalized output vmin = min(np.min(normalized_outputs_4), np.min(normalized_outputs_1)) diff --git a/docs/normalization/normalization_LODOG.py b/docs/normalization/normalization_LODOG.py index 397e474..1f76de1 100644 --- a/docs/normalization/normalization_LODOG.py +++ b/docs/normalization/normalization_LODOG.py @@ -172,7 +172,7 @@ filters_output = ODOG.bank.apply(stimulus) weighted_outputs = ODOG.weight_outputs(filters_output) -norm_outputs = LODOG.normalize_outputs(weighted_outputs) +norm_outputs = LODOG.normalize_outputs(weighted_outputs, eps=1e-6) output_LODOG = norm_outputs.sum(axis=(0, 1)) # %% Extract target prediction @@ -262,6 +262,7 @@ # %% Normalizing images as weighted combination (tensor dot-product) of filter outputs normalizing_coefficients = multyscale.normalization.norm_coeffs(weighted_outputs, norm_weights) +assert np.array_equal(normalizing_coefficients, LODOG.norm_coeffs(weighted_outputs)) # Visualize each normalizing coefficient n_{o,s}, i.e. for each individual filter f_{o,s} fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") @@ -432,8 +433,8 @@ # %% Divisive normalization # Since the local RMSs tensor is the same (O, S, X, Y) shape as the filter outputs # we can simply divide -normalized_outputs = weighted_outputs / normalization_local_RMS -assert np.array_equal(normalized_outputs, norm_outputs) +normalized_outputs = weighted_outputs / (normalization_local_energies + 1e-6) +assert np.allclose(normalized_outputs, norm_outputs) # Visualize each normalized f'_{o',s'} fig, axs = plt.subplots(*normalized_outputs.shape[:2], sharex="all", sharey="all") @@ -462,7 +463,7 @@ # %% Recombine recombined_outputs = np.sum(normalized_outputs, axis=(0, 1)) -assert np.array_equal(recombined_outputs, output_LODOG) +assert np.allclose(recombined_outputs, output_LODOG) plt.subplot(2, 2, 1) plt.imshow(recombined_outputs, cmap="coolwarm", extent=visextent) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index ad59e85..6e992fa 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -483,7 +483,7 @@ # %% Divisive normalization # Since the local energies tensor is the same $(O, S, X, Y)$ shape as the filter outputs # we can simply divide -LODOG_outputs = filters_output / normalization_local_energy +LODOG_outputs = filters_output / (local_energies + 1e-6) # Visualize each normalized f'_{o',s'} fig, axs = plt.subplots(*LODOG_outputs.shape[:2], sharex="all", sharey="all") From 2ad0cbea2de2554f224c2342118724aef12b3efa Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Wed, 21 Feb 2024 16:54:33 +0100 Subject: [PATCH 77/79] docs(normalization): clarify norm_coeffs LODOG == ODOG --- .../normalization_generalized.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index 6e992fa..39fe463 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -135,7 +135,7 @@ # a 4D tensor of same dimensions and size as $\mathbf{F}$ # # Combined, this gives: -# +# # $$ f'_{o, s, y, x} := # \frac{f_{o, s, y, x}} # {\sqrt{\mathrm{avg_{xy}}((\mathbf{w}\cdot\mathbf{F})^2)}} @@ -226,6 +226,9 @@ # %% Normalizing coefficients normalizing_coefficients = multyscale.normalization.norm_coeffs(filters_output, interaction_weights) +assert np.array_equal(normalizing_coefficients, ODOG.norm_coeffs(filters_output)) + + # Visualize each normalizing coefficient n_{o,s}, i.e. # the normalizer image for each individual filter f_{o,s} fig, axs = plt.subplots(*normalizing_coefficients.shape[:2], sharex="all", sharey="all") @@ -236,7 +239,13 @@ plt.show() # %% [markdown] -# The LODOG model uses the same interaction weights as the base ODOG model. +# The LODOG model uses the same interaction weights +# (and thus same normalizing coefficients) +# as the base ODOG model. + +# %% +normalizing_coefficients_LODOG = LODOG.norm_coeffs(filters_output) +assert np.array_equal(normalizing_coefficients, normalizing_coefficients_LODOG) # %% [markdown] # The FLODOG model uses a different set of interaction weights. @@ -262,7 +271,7 @@ vmax=1, ) axs[0, 0].set_ylabel("scale of filter to normalize (idx)") -axs[0, 0].set_title("LODOG weights") +axs[0, 0].set_title("(L)ODOG weights") axs[0, 1].pcolor( FLODOG.scale_norm_weights, @@ -290,7 +299,6 @@ # the filter outputs at these spatial scales. # %% Normalizing coefficients -normalizing_coefficients_LODOG = LODOG.norm_coeffs(filters_output) normalizing_coefficients_FLODOG = FLODOG.norm_coeffs(filters_output) # Visualize each norm. coeff. @@ -308,7 +316,7 @@ ) fig.supxlabel("Spatial scale/freq. $s'$") fig.supylabel("Orientation $o'$") -fig.suptitle("LODOG") +fig.suptitle("(L)ODOG") plt.show() fig, axs = plt.subplots(*normalizing_coefficients_FLODOG.shape[:2], sharex="all", sharey="all") @@ -344,7 +352,7 @@ # Energy here is expressed as the (spatial) root-mean-square of the signal. # # 1. Square each ($X \times Y = 1024 \times 1024$) pixel of the normalizing coefficient $n_{o, s}$ -# 2. Average \mathrm{avg_{yx}} across pixels +# 2. Average $\mathrm{avg_{yx}}$ across pixels # 3. Square-root of this average # # $$ e_{o, s, y, x} := \sqrt{\mathrm{avg_{yx}}(n_{o, s}^2)} $$ @@ -359,7 +367,7 @@ # This results in a single energy estimate for each $(o', s')$. # %% Global image RMS -ODOG_norm_coeffs = ODOG.norm_coeffs(filters_output) +normalizing_coefficients_ODOG = normalizing_coefficients ODOG_energies = ODOG_norm_coeffs.mean(axis=-1).mean(axis=-1) print(ODOG_energies.shape) From 26e91923aced6bf1b40135c4ba518bf98f6f7f52 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Wed, 21 Feb 2024 16:54:53 +0100 Subject: [PATCH 78/79] docs(normalization): ODOG energies --- .../normalization_generalized.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/normalization/normalization_generalized.py b/docs/normalization/normalization_generalized.py index 39fe463..889a32c 100644 --- a/docs/normalization/normalization_generalized.py +++ b/docs/normalization/normalization_generalized.py @@ -368,7 +368,7 @@ # %% Global image RMS normalizing_coefficients_ODOG = normalizing_coefficients -ODOG_energies = ODOG_norm_coeffs.mean(axis=-1).mean(axis=-1) +ODOG_energies = np.sqrt((normalizing_coefficients_ODOG ** 2).mean(axis=(-1, -2))) print(ODOG_energies.shape) # Visualise @@ -429,7 +429,7 @@ # %% Divisive normalization norm_i_outputs = filters_output / ODOG_energies_i -assert np.allclose(ODOG_outputs, norm_i_outputs) +assert np.array_equal(ODOG_outputs, norm_i_outputs) # %% [markdown] @@ -557,18 +557,22 @@ # # %% Global image averaging as filter -A = np.ones((1024 * 2, 1024 * 2)) / 1024**2 - # calculate energy using the kernel -ODOG_energies_i2 = np.ndarray(filters_output.shape) -for o, s in np.ndindex(filters_output.shape[:2]): - norm = ODOG_norm_coeffs[o, s, ...] ** 2 - mean = multyscale.filters.apply(norm, A, padval=0) - ODOG_energies_i2[o, s] = np.sqrt(mean) +ODOG_energies_i2 = np.ndarray(normalizing_coefficients_ODOG.shape) +for o, s in np.ndindex(normalizing_coefficients_ODOG.shape[:2]): + norm_coeff = normalizing_coefficients_ODOG[o, s] + norm = norm_coeff ** 2 + spatial_average = multyscale.filters.apply(norm, spatial_kernel, padval=0) + energy = np.sqrt(spatial_average) + ODOG_energies_i2[o, s] = energy -# this replicates the just verified normalizing image assert np.allclose(ODOG_energies_i2, ODOG_energies_i) +# %% +ODOG_outputs_i2 = filters_output / ODOG_energies_i2 + +assert np.allclose(ODOG_outputs, ODOG_outputs_i2) + # %% [markdown] # ### Summary # Thus, we can now reformulate the ODOG normalization as: From 64865cec8cdbc66c2924d3c992971f365562ab56 Mon Sep 17 00:00:00 2001 From: Joris Vincent Date: Tue, 27 Feb 2024 12:40:12 +0100 Subject: [PATCH 79/79] docs(models): docstrings, type annotations Closes #5 --- multyscale/models.py | 304 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 282 insertions(+), 22 deletions(-) diff --git a/multyscale/models.py b/multyscale/models.py index fadebd0..8ebe798 100644 --- a/multyscale/models.py +++ b/multyscale/models.py @@ -1,14 +1,53 @@ +"""End-to-End multiscale spatial filtering models + +This module implements several existing multiscale spatial filtering models. +The models here are primarily used to model brightness perception. + +Each model-type is implement as a (separate) class; +the parameters, attributes and methods available differ per class. +All models require a filter `shape` in pixels (X, Y), +and a `visextent` in degrees of visual angle (left, right, top, bottom) +All models have a method `.apply()`, +which takes in a (2D) image and returns the final model output (also 2D image). + +Currently implemented are: +- Difference-of-Gaussian (Blakeslee & McCourt, 1997), `DOG_BM1997` +- Oriented Difference-of-Gaussian (ODOG; Blakeslee & McCourt, 1999; Robinson, Hammon & de Sa, 2007) + `ODOG_RHS2007` +- LODOG (Robinson, Hammon & de Sa, 2007) `LODOG_RHS2007` +- FLODOG (Robinson, Hammon & de Sa, 2007) `FLODOG_RHS2007` + +""" + +from typing import Sequence + # Third party imports import numpy as np # Local application imports from . import filterbanks, normalization -# TODO: refactor filter-output datastructures - class DOG_BM1997: - def __init__(self, shape, visextent): + """Difference-of-Gaussian model, after Blakeslee & McCourt (1997) + + This model uses 7 unoriented (isotropic) Difference-of-Gaussian filters, + weighted according to an (approximate) contrast sensitivity function. + + Parameters + ---------- + shape : array-like[int] + pixel size (X, Y) of filters + visextent : array-like[float] + extent in degrees visual angle, (left, right, top, bottom) + + See also + -------- + multyscale.filterbanks.BM1997 + + """ + + def __init__(self, shape: Sequence[int], visextent: Sequence[float]): self.shape = shape self.visextent = visextent @@ -18,11 +57,38 @@ def __init__(self, shape, visextent): self.weights_slope = 0.1 self.scale_weights = filterbanks.scale_weights(self.center_sigmas, self.weights_slope) - def weight_outputs(self, filters_output): + def weight_outputs(self, filters_output: np.ndarray) -> np.ndarray: + """Weight filter outputs according to spatial scale of filter + + Uses self.scale_weights for weighting + + Parameters + ---------- + filters_output : numpy.ndarray + output of whole filterbank, of shape (S, Y, X) + + Returns + ------- + numpy.ndarray + weighted output of whole filterbank, same shape is filters_output + """ return filterbanks.weight_multiscale_outputs(filters_output, self.scale_weights) - def apply(self, image): - # TODO: docstring + def apply(self, image: np.ndarray) -> np.ndarray: + """Apply model to given image + + Parameters + ---------- + image : numpy.ndarray + image, 2D in shape (Y, X) + eps : float, optional + precision offset, used to avoid floating point errors, by default 0.0 + + Returns + ------- + numpy.ndarray + matrix of brightness estimate, of same shape as image + """ # Sum over spatial scales filters_output = self.bank.apply(image) @@ -34,9 +100,32 @@ def apply(self, image): class ODOG_RHS2007: - # TODO: docstring + """Oriented Difference-of-Gaussian model, after Blakeslee & McCourt (1999) + + This model uses 7 Oriented Difference-of-Gaussian filters, + weighted according to an (approximate) contrast sensitivity function. + + After weighting, each filter output is normalized + by the global energy of a weighted combination of all other filter outputs. + + This implementation specifically corresponds to Robinson, Hammon and de Sa's (2007) + implementation, and exactly replicates the available MATLAB version. + + Parameters + ---------- + shape : array-like[int] + pixel size (X, Y) of filters + visextent : array-like[float] + extent in degrees visual angle, (left, right, top, bottom) + + See also + -------- + multyscale.filterbanks.RHS2007 + multyscale.normalizaton + + """ - def __init__(self, shape, visextent): + def __init__(self, shape: Sequence[int], visextent: Sequence[float]): self.shape = shape self.visextent = visextent @@ -52,13 +141,60 @@ def __init__(self, shape, visextent): *self.bank.shape[:2], self.scale_norm_weights, self.orientation_norm_weights ) - def weight_outputs(self, filters_output): + def weight_outputs(self, filters_output: np.ndarray) -> np.ndarray: + """Weight filter outputs according to spatial scale of filter + + Uses self.scale_weights for weighting + + Parameters + ---------- + filters_output : numpy.ndarray + output of whole filterbank, of shape (O, S, Y, X) + + Returns + ------- + numpy.ndarray + weighted output of whole filterbank, same shape is filters_output + """ return filterbanks.weight_oriented_multiscale_outputs(filters_output, self.scale_weights) - def norm_coeffs(self, filters_output): + def norm_coeffs(self, filters_output: np.ndarray) -> np.ndarray: + """Construct all normalizing coefficients for given filter outputs + + Uses model.normalization_weights + + Parameters + ---------- + filters_output : numpy.ndarray + output of whole filterbank, of shape (O, S, Y, X) + + Returns + ------- + numpy.ndarray + all (O', S', Y, X) normalizing coefficients: + a single 2D (Y, X) weighted combination of all filter outputs + per (O', S') filter-to-normalize + + See also + -------- + multyscale.normalization.norm_coeffs + """ return normalization.norm_coeffs(filters_output, self.normalization_weights) - def spatial_kernels(self): + def spatial_kernels(self) -> np.ndarray: + """Construct all spatial averaging kernels + + ODOG model uses a global image mean kernel + + Returns + ------- + numpy.ndarray + all (O, S, Y, X) spatial averaging kernels + + See also + -------- + multyscale.normalization.spatial_kernel_globalmean + """ kernel = normalization.spatial_kernel_globalmean(self.bank.shape[2:]) kernels = np.ndarray(self.bank.shape[:2] + kernel.shape) for o, s in np.ndindex(kernels.shape[:2]): @@ -66,7 +202,26 @@ def spatial_kernels(self): return kernels - def norm_energies(self, norm_coeffs, eps=0.0): + def norm_energies(self, norm_coeffs: np.ndarray, eps=0.0) -> np.ndarray: + """Convert normalizing coefficients to energy (denominator for divisive normalization) + + Parameters + ---------- + norm_coeffs : numpy.ndarray + all (O, S, Y, X) normalizing coefficients (weighted combination of all filter outputs) + eps : float, optional + precision offset, used to avoid square-root of negative numbers, by default 0.0 + + Returns + ------- + numpy.ndarray + all (O, S, Y, X) normalizing energies (denominator for divisive normalization) + + See also + -------- + multyscale.models.ODOG_RHS2007.norm_coeffs + multyscale.normalization.norm_energy + """ kernels = self.spatial_kernels() norm_energies = np.ndarray(norm_coeffs.shape) @@ -77,8 +232,21 @@ def norm_energies(self, norm_coeffs, eps=0.0): return norm_energies - def normalize_outputs(self, filters_output, eps=0.0): - # TODO: docstring + def normalize_outputs(self, filters_output: np.ndarray, eps=0.0) -> np.ndarray: + """Apply divisive normalization to given filter outputs + + Parameters + ---------- + filters_output : numpy.ndarray + output of whole filterbank, of shape (O, S, Y, X) + eps : float, optional + precision offset, used to avoid square-root of negative numbers, by default 0.0 + + Returns + ------- + numpy.ndarray + all (O, S, Y, X) normalized filter outputs + """ norm_coeffs = self.norm_coeffs(filters_output) norm_energies = self.norm_energies(norm_coeffs, eps=eps) @@ -89,8 +257,21 @@ def normalize_outputs(self, filters_output, eps=0.0): return normalized_outputs - def apply(self, image, eps=0.0): - # TODO: docstring + def apply(self, image: np.ndarray, eps=0.0) -> np.ndarray: + """Apply model to given image + + Parameters + ---------- + image : numpy.ndarray + image, 2D in shape (Y, X) + eps : float, optional + precision offset, used to avoid floating point errors, by default 0.0 + + Returns + ------- + numpy.ndarray + matrix of brightness estimate, of same shape as image + """ # Sum over spatial scales filters_output = self.bank.apply(image) @@ -106,15 +287,58 @@ def apply(self, image, eps=0.0): class LODOG_RHS2007(ODOG_RHS2007): - # TODO: docstring + """LODOG model, after Robinson, Hammon, and de Sa (2007) - def __init__(self, shape, visextent, window_sigma=4): + This model uses 7 Oriented Difference-of-Gaussian filters, + weighted according to an (approximate) contrast sensitivity function. + + After weighting, each filter output is normalized + by the local energy of a weighted combination of all other filter outputs. + + This implementation specifically corresponds to Robinson, Hammon and de Sa's (2007) + implementation, and exactly replicates the available MATLAB version. + + Parameters + ---------- + shape : array-like[int] + pixel size (X, Y) of filters + visextent : array-like[float] + extent in degrees visual angle, (left, right, top, bottom) + window_sigma : float + standard deviation (in degrees) of Gaussian spatial normalization kernel, by default 4.0 + + + See also + -------- + multyscale.filterbanks.RHS2007 + multyscale.normalization + multyscale.normalization.spatial_kernel_gaussian + + """ + + def __init__( + self, shape: Sequence[int], visextent: Sequence[float], window_sigma: float = 4.0 + ): super().__init__(shape, visextent) self.window_sigma = window_sigma self.window_sigmas = np.ones(shape=(*self.bank.shape[:2], 2)) * self.window_sigma - def spatial_kernels(self): + def spatial_kernels(self) -> np.ndarray: + """Construct all spatial averaging kernels + + (F)LODOG model uses a 2D Gaussian shape, + using the model.window_sigma(s) + + Returns + ------- + numpy.ndarray + spatial averaging kernel (Y, X) + + See also + -------- + multyscale.normalization.spatial_kernel_gaussian + """ kernels = np.ndarray(self.bank.shape) for o_prime, s_prime in np.ndindex(self.window_sigmas.shape[:2]): kernels[o_prime, s_prime] = normalization.spatial_kernel_gaussian( @@ -126,9 +350,45 @@ def spatial_kernels(self): class FLODOG_RHS2007(LODOG_RHS2007): - # TODO: docstring - - def __init__(self, shape, visextent, sdmix=0.5, spatial_window_scalar=4): + """LODOG model, after Robinson, Hammon, and de Sa (2007) + + This model uses 7 Oriented Difference-of-Gaussian filters, + weighted according to an (approximate) contrast sensitivity function. + + After weighting, each filter output is normalized + by the local energy of a weighted combination of all other filter outputs. + + This implementation specifically corresponds to Robinson, Hammon and de Sa's (2007) + implementation, and exactly replicates the available MATLAB version. + + Parameters + ---------- + shape : array-like[int] + pixel size (X, Y) of filters + visextent : array-like[float] + extent in degrees visual angle, (left, right, top, bottom) + sdmix : float + standard deviation of Gaussian weighting function, by default 0.5 + spatial_window_scalar : float + scaling factor of Gaussian spatial normalization kernel relative to filter size, + by default 4.0 + + + See also + -------- + multyscale.filterbanks.RHS2007 + multyscale.normalization + multyscale.normalization.spatial_kernel_gaussian + + """ + + def __init__( + self, + shape: Sequence[int], + visextent: Sequence[float], + sdmix: float = 0.5, + spatial_window_scalar: float = 4.0, + ): super().__init__(shape, visextent) self.sdmix = sdmix # stdev of Gaussian weights for scale mixing