import logging
import numpy as np
# Get module level logger
logger = logging.getLogger("stimupy.noises")
def randomize_sign(array, rng=None):
"""Randomize the sign of values in an array
Parameters
----------
array
N-dimensional array
rng : numpy.random.Generator, optional
Random number generator to use. If None, a new default_rng is created.
By passing in a custom rng, you can control the randomness,
e.g., make it replicable.
Returns
-------
array
Same array with randomized signs
"""
if rng is None:
rng = np.random.default_rng()
sign = rng.random(array.shape) - 0.5
sign[sign <= 0.0] = -1.0
sign[sign > 0.0] = 1.0
array = array * sign
return array
def pseudo_white_helper(
shape,
amplitude,
rng=None,
):
"""Generate pseudorandom white noise patch
Parameters
----------
shape : int or (int, int)
Shape of noise patch in pixels (height, width)
amplitude
Amplitude of each (pos/neg) frequency component = A/2
rng : numpy.random.Generator, optional
Random number generator to use. If None, a new default_rng is created.
By passing in a custom rng, you can control the randomness of the noise generation,
e.g., make it replicable.
Returns
-------
output
Pseudorandom white noise patch
"""
if isinstance(shape, (float, int)):
shape = (shape, shape)
if rng is None:
rng = np.random.default_rng()
Re = rng.random(shape) * amplitude - amplitude / 2.0
Im = np.sqrt((amplitude / 2.0) ** 2 - Re**2)
Im = randomize_sign(Im, rng=rng)
output = Re + Im * 1j
return output
[docs]
def pseudo_white_spectrum(
shape=(100, 100),
amplitude=2.0,
rng=None,
):
"""Create pseudorandom white noise spectrum
Code translated and adapted from Matlab scripts
provided by T. Peromaa
Parameters
----------
shape : int or (int, int)
Shape of noise array in pixels (height, width)
amplitude
Amplitude of noise power spectrum
rng : numpy.random.Generator, optional
Random number generator to use. If None, a new default_rng is created.
By passing in a custom rng, you can control the randomness of the noise generation,
e.g., make it replicable.
Returns
-------
spectrum
Shifted 2d complex number spectrum. DC = 0.
Amplitude of each (pos/neg) frequency component = A/2
Power of each (pos/neg) frequency component = (A/2)**2
"""
if isinstance(shape, (float, int)):
shape = (shape, shape)
y, x = shape
A = amplitude
if (y % 2 != 0) or (x % 2 != 0):
raise ValueError("shape needs to be even-numbered")
# We divide the noise spectrum in four quadrants with pseudorandom white noise
quadrant1 = pseudo_white_helper((int(y / 2) - 1, int(x / 2) - 1), A, rng=rng)
quadrant2 = pseudo_white_helper((int(y / 2) - 1, int(x / 2) - 1), A, rng=rng)
quadrant3 = quadrant2[::-1, ::-1].conj()
quadrant4 = quadrant1[::-1, ::-1].conj()
# We place the quadrants in the spectrum to eventuate that each frequency component has
# an amplitude of A/2
spectrum = np.zeros([y, x], dtype=complex)
spectrum[1 : int(y / 2), 1 : int(x / 2)] = quadrant1
spectrum[1 : int(y / 2), int(x / 2) + 1 : x] = quadrant2
spectrum[int(y / 2 + 1) : y, 1 : int(x / 2)] = quadrant3
spectrum[int(y / 2 + 1) : y, int(x / 2 + 1) : x] = quadrant4
# We need to fill the rows / columns that the quadrants do not cover
# Fill first row:
row = pseudo_white_helper((1, x), A, rng=rng)
apu = np.fliplr(row)
row[0, int(x / 2 + 1) : x] = apu[0, int(x / 2) : x - 1].conj()
spectrum[0, :] = np.squeeze(row)
# Fill central row:
row = pseudo_white_helper((1, x), A, rng=rng)
apu = np.fliplr(row)
row[0, int(x / 2 + 1) : x] = apu[0, int(x / 2) : x - 1].conj()
spectrum[int(y / 2), :] = np.squeeze(row)
# Fill first column:
col = pseudo_white_helper((y, 1), A, rng=rng)
apu = np.flipud(col)
col[int(y / 2 + 1) : y, 0] = apu[int(y / 2) : y - 1, 0].conj()
spectrum[:, int(x / 2)] = np.squeeze(col)
# Fill central column:
col = pseudo_white_helper((y, 1), A, rng=rng)
apu = np.flipud(col)
col[int(y / 2 + 1) : y, 0] = apu[int(y / 2) : y - 1, 0].conj()
spectrum[:, 0] = np.squeeze(col)
# Set amplitude at filled-corners to A/2:
spectrum[0, 0] = -A / 2 + 0j
spectrum[0, int(x / 2)] = -A / 2 + 0j
spectrum[int(y / 2), 0] = -A / 2 + 0j
# Set DC = 0:
spectrum[int(y / 2), int(x / 2)] = 0 + 0j
return spectrum
# flake8: noqa: E402
from stimupy.noises import binaries, narrowbands, naturals, whites
__all__ = [
"overview",
"plot_overview",
"pseudo_white_spectrum",
"binaries",
"narrowbands",
"naturals",
"whites",
]
[docs]
def overview(skip=False):
"""Generate example stimuli from this module
Returns
-------
dict[str, dict]
Dict mapping names to individual stimulus dicts
"""
stimuli = {}
for stimmodule_name in __all__:
if stimmodule_name in [
"overview",
"plot_overview",
"pseudo_white_spectrum",
]:
continue
logger.info(f"Generating stimuli from {stimmodule_name}")
# Get a reference to the actual module
stimmodule = globals()[stimmodule_name]
try:
stims = stimmodule.overview()
# Accumulate
stimuli.update(stims)
except NotImplementedError as e:
if not skip:
raise e
# Skip stimuli that aren't implemented
logger.info("-- not implemented")
pass
return stimuli
[docs]
def plot_overview(mask=False, save=None, units="deg"):
"""Plot overview of examples in this module (and submodules)
Parameters
----------
mask : bool or str, optional
If True, plot mask on top of stimulus image (default: False).
If string is provided, plot this key from stimulus dictionary as mask
save : None or str, optional
If None (default), do not save the plot.
If string is provided, save plot under this name.
units : "px", "deg" (default), or str
what units to put on the axes, by default degrees visual angle ("deg").
If a str other than "deg"(/"degrees") or "px"(/"pix"/"pixels") is passed,
it must be the key to a tuple in stim
"""
from stimupy.utils import plot_stimuli
stims = overview(skip=True)
plot_stimuli(stims, mask=mask, units=units, save=save)
if __name__ == "__main__":
# Log to console at INFO level
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())
plot_overview()