zebranoise 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Maxwell Shinn
4
+ Copyright (c) 2024 Sophie Skriabine
5
+ Copyright (c) 2008 Casey Duncan
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.1
2
+ Name: zebranoise
3
+ Version: 0.1.0
4
+ Summary: Zebra noise stimuli for Python
5
+ Home-page: https://github.com/mwshinn/zebra_noise
6
+ Author: Max Shinn
7
+ Author-email: m.shinn@ucl.ac.uk
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: C
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Topic :: Multimedia :: Graphics
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE.txt
19
+
20
+ This library will efficiently generate visual stimulus videos using Perlin noise, including zebra noise. The core algorithm is in optimized C.
21
+
22
+ [Example zebra noise](http://www.youtube.com/watch?v=-SyjgbNCP4Q)
23
+
24
+ [![Example zebra noise](http://img.youtube.com/vi/-SyjgbNCP4Q/0.jpg)](http://www.youtube.com/watch?v=-SyjgbNCP4Q "Example zebra noise")
25
+
26
+ # Installation
27
+
28
+ ## From pypi
29
+
30
+ Run:
31
+
32
+ pip install zebranoise
33
+
34
+ ## From source
35
+
36
+ Make sure you have a C compiler installed. The easiest way to do this is by installing Cython.
37
+
38
+ Then, install using the standard
39
+
40
+ python setup.py install
41
+
42
+ This will compile the sources and install.
43
+
44
+ I have only tested this on Linux.
45
+
46
+ # Usage
47
+
48
+ ## Basic usage
49
+
50
+ If you would like to generate a zebra noise of size 640x480 for 2 minutes, you can run
51
+
52
+ import zebranoise
53
+ zebranoise.zebra_noise("output.mp4", xsize=640, ysize=480, tdur=60*2, fps=30, seed=0)
54
+
55
+ Other parameters control the spatial and temporal frequencies. For instance,
56
+
57
+ zebranoise.zebra_noise("output2.mp4", xsize=640, ysize=480, tdur=60*2, levels=10, xyscale=.2, tscale=50, fps=30, xscale=1.0, yscale=1.0, seed=0)
58
+
59
+ The meaning of these parameters is:
60
+
61
+ - **xsize** and **ysize**: the x and y dimensions of the output video. (Sometimes
62
+ these will be rounded up to multiples of 16.)
63
+ - **tdur**: the duration in seconds
64
+ - **levels**: The number of octaves to use when approximating the 1/f spectrum.
65
+ The default of 10 should be more than enough.
66
+ - **xyscale**: The spatial scale of the Perlin noise, from 0 to 1. Low values
67
+ will make the video smoother and high values choppier.
68
+ - **tscale**: The speed of the video
69
+ - **xscale** and **yscale**: Resize the x and y dimensions of the output.
70
+ - **fps**: Frames per second
71
+ - **seed**: Random seed
72
+
73
+ ## Advanced usage
74
+
75
+ This package can be used to generate new stimuli based on Perlin noise.
76
+
77
+ To generate a Perlin noise-based video, follow two steps with the package. First, generate the Perlin noise. This is the most time consuming step, and will also use a lot of temporary disk space. This is accomplished with:
78
+
79
+ ```python
80
+ noise = zebranoise.PerlinStimulus(xsize=400, ysize=100, tdur=60*2, xyscale=.2, tscale=50, seed=0)
81
+ ```
82
+
83
+ See the function documentation for perlstim.Perl for more information about modifying the properties of the noise.
84
+
85
+ Second, apply "filters" to the Perlin noise and save the resulting video. Filters include thresholds, sync squares in the corner, etc. If using a filter with no arguments, just pass a string naming the filter. If using a filter with arguments, pass a tuple where the first element is the name of the filter as a string, and the remaining elements are the arguments. For example,
86
+
87
+ ```python
88
+ noise.save_video("noise.mp4", filters=["reverse", ("comb", .05)])
89
+ ```
90
+
91
+ The above example applies the "reverse" filter (with no arguments) and the "comb" filter with the argument 0.1.
92
+
93
+ ## Filters
94
+
95
+ The following filters are currently defined:
96
+
97
+ - "threshold" (1 argument): set all values above the threshold argument to white, and all below to black.
98
+ - "softthresh" (1 argument): set values much greater than 0 to white and much less than zero to blac. Use shades of grey for intermediate values, according to a sigmoid with a temperature given as the argument.
99
+ - "comb" (1 argument): Alternate black and white thresholds at intervals given by the argument.
100
+ - "reverse" (0 arguments): play the video in reverse
101
+ - "invert" (0 arguments): switch white and black
102
+ - "wood" (1 argument): similar to "comb", but use a sawtooth wave
103
+ - "blur" (1 argument): Gaussian blur with the given spatial standard deviation
104
+ - "photodiode" (1 argument): Draw a sync square in the corner, with a size given by the argument.
105
+ - "photodiode_anywhere" (3 arguments): Draw a sync square at the coordinates given by the first two arguments (x and y), and of size given by argument 3.
106
+ - [any function] (0 arguments): If you pass a function, the function will be applied to each chunk of the video. Chunks consist of the entire x and y but a subset of z, guaranteed to be an even number.
107
+
108
+ ## Example
109
+
110
+ To get started, try the following:
111
+
112
+ ```python
113
+ import zebranoise
114
+ stim = zebranoise.PerlinStimulus(xsize=480, ysize=128, tdur=60*5, xyscale=.2, tscale=50)
115
+ stim.save_video("perlin_stimulus.mp4", loop=1, filters=[("comb", .08), ("photodiode", 30)])
116
+ ```
117
+ # Other information
118
+
119
+ Much of the C code is based on [Casey Duncan's "noise" package for Python](https://github.com/caseman/noise), under the MIT license.
120
+
121
+
@@ -0,0 +1,100 @@
1
+ This library will efficiently generate visual stimulus videos using Perlin noise, including zebra noise. The core algorithm is in optimized C.
2
+
3
+ [Example zebra noise](http://www.youtube.com/watch?v=-SyjgbNCP4Q)
4
+
5
+ [![Example zebra noise](http://img.youtube.com/vi/-SyjgbNCP4Q/0.jpg)](http://www.youtube.com/watch?v=-SyjgbNCP4Q "Example zebra noise")
6
+
7
+ # Installation
8
+
9
+ ## From pypi
10
+
11
+ Run:
12
+
13
+ pip install zebranoise
14
+
15
+ ## From source
16
+
17
+ Make sure you have a C compiler installed. The easiest way to do this is by installing Cython.
18
+
19
+ Then, install using the standard
20
+
21
+ python setup.py install
22
+
23
+ This will compile the sources and install.
24
+
25
+ I have only tested this on Linux.
26
+
27
+ # Usage
28
+
29
+ ## Basic usage
30
+
31
+ If you would like to generate a zebra noise of size 640x480 for 2 minutes, you can run
32
+
33
+ import zebranoise
34
+ zebranoise.zebra_noise("output.mp4", xsize=640, ysize=480, tdur=60*2, fps=30, seed=0)
35
+
36
+ Other parameters control the spatial and temporal frequencies. For instance,
37
+
38
+ zebranoise.zebra_noise("output2.mp4", xsize=640, ysize=480, tdur=60*2, levels=10, xyscale=.2, tscale=50, fps=30, xscale=1.0, yscale=1.0, seed=0)
39
+
40
+ The meaning of these parameters is:
41
+
42
+ - **xsize** and **ysize**: the x and y dimensions of the output video. (Sometimes
43
+ these will be rounded up to multiples of 16.)
44
+ - **tdur**: the duration in seconds
45
+ - **levels**: The number of octaves to use when approximating the 1/f spectrum.
46
+ The default of 10 should be more than enough.
47
+ - **xyscale**: The spatial scale of the Perlin noise, from 0 to 1. Low values
48
+ will make the video smoother and high values choppier.
49
+ - **tscale**: The speed of the video
50
+ - **xscale** and **yscale**: Resize the x and y dimensions of the output.
51
+ - **fps**: Frames per second
52
+ - **seed**: Random seed
53
+
54
+ ## Advanced usage
55
+
56
+ This package can be used to generate new stimuli based on Perlin noise.
57
+
58
+ To generate a Perlin noise-based video, follow two steps with the package. First, generate the Perlin noise. This is the most time consuming step, and will also use a lot of temporary disk space. This is accomplished with:
59
+
60
+ ```python
61
+ noise = zebranoise.PerlinStimulus(xsize=400, ysize=100, tdur=60*2, xyscale=.2, tscale=50, seed=0)
62
+ ```
63
+
64
+ See the function documentation for perlstim.Perl for more information about modifying the properties of the noise.
65
+
66
+ Second, apply "filters" to the Perlin noise and save the resulting video. Filters include thresholds, sync squares in the corner, etc. If using a filter with no arguments, just pass a string naming the filter. If using a filter with arguments, pass a tuple where the first element is the name of the filter as a string, and the remaining elements are the arguments. For example,
67
+
68
+ ```python
69
+ noise.save_video("noise.mp4", filters=["reverse", ("comb", .05)])
70
+ ```
71
+
72
+ The above example applies the "reverse" filter (with no arguments) and the "comb" filter with the argument 0.1.
73
+
74
+ ## Filters
75
+
76
+ The following filters are currently defined:
77
+
78
+ - "threshold" (1 argument): set all values above the threshold argument to white, and all below to black.
79
+ - "softthresh" (1 argument): set values much greater than 0 to white and much less than zero to blac. Use shades of grey for intermediate values, according to a sigmoid with a temperature given as the argument.
80
+ - "comb" (1 argument): Alternate black and white thresholds at intervals given by the argument.
81
+ - "reverse" (0 arguments): play the video in reverse
82
+ - "invert" (0 arguments): switch white and black
83
+ - "wood" (1 argument): similar to "comb", but use a sawtooth wave
84
+ - "blur" (1 argument): Gaussian blur with the given spatial standard deviation
85
+ - "photodiode" (1 argument): Draw a sync square in the corner, with a size given by the argument.
86
+ - "photodiode_anywhere" (3 arguments): Draw a sync square at the coordinates given by the first two arguments (x and y), and of size given by argument 3.
87
+ - [any function] (0 arguments): If you pass a function, the function will be applied to each chunk of the video. Chunks consist of the entire x and y but a subset of z, guaranteed to be an even number.
88
+
89
+ ## Example
90
+
91
+ To get started, try the following:
92
+
93
+ ```python
94
+ import zebranoise
95
+ stim = zebranoise.PerlinStimulus(xsize=480, ysize=128, tdur=60*5, xyscale=.2, tscale=50)
96
+ stim.save_video("perlin_stimulus.mp4", loop=1, filters=[("comb", .08), ("photodiode", 30)])
97
+ ```
98
+ # Other information
99
+
100
+ Much of the C code is based on [Casey Duncan's "noise" package for Python](https://github.com/caseman/noise), under the MIT license.
@@ -0,0 +1,2 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel", "numpy==1.13.3; python_version=='3.6'", "numpy==1.14.5; python_version=='3.7'", "numpy==1.17.3; python_version=='3.8'", "numpy>=2.0; python_version>='3.9'"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,46 @@
1
+ import sys
2
+ try:
3
+ from setuptools import setup, Extension
4
+ except ImportError:
5
+ from distutils.core import setup, Extension
6
+
7
+ import numpy as np
8
+
9
+ if sys.platform != 'win32':
10
+ compile_args = ['-funroll-loops']
11
+ else:
12
+ compile_args = []
13
+
14
+ with open("README.md", "r") as f:
15
+ long_desc = f.read()
16
+
17
+ with open("zebranoise/_version.py", "r") as f:
18
+ exec(f.read())
19
+
20
+ setup(
21
+ name='zebranoise',
22
+ version=__version__,
23
+ description='Zebra noise stimuli for Python',
24
+ long_description = long_desc,
25
+ long_description_content_type='text/markdown',
26
+ author='Max Shinn',
27
+ author_email='m.shinn@ucl.ac.uk',
28
+ url='https://github.com/mwshinn/zebra_noise',
29
+ classifiers = [
30
+ 'Development Status :: 4 - Beta',
31
+ 'Programming Language :: C',
32
+ 'Programming Language :: Python :: 3',
33
+ 'Intended Audience :: Science/Research',
34
+ 'License :: OSI Approved :: MIT License',
35
+ 'Topic :: Multimedia :: Graphics',
36
+ 'Topic :: Scientific/Engineering',
37
+ ],
38
+ install_requires = ['numpy', 'imageio', 'imageio-ffmpeg', 'tqdm'],
39
+ packages=['zebranoise'],
40
+ ext_modules=[
41
+ Extension('zebranoise._perlin', ['zebranoise/_perlin.c'],
42
+ extra_compile_args=compile_args,
43
+ include_dirs=[np.get_include()],
44
+ )
45
+ ],
46
+ )
@@ -0,0 +1,5 @@
1
+ from .perlin_stimulus import PerlinStimulus
2
+ from .util import generate_frames
3
+ from .easy import zebra_noise
4
+
5
+ Perl = PerlinStimulus # Backward compatibility
@@ -0,0 +1,239 @@
1
+ // Copyright (c) 2008, Casey Duncan (casey dot duncan at gmail dot com)
2
+ // Copyright (c) 2022, Max Shinn
3
+ // see LICENSE.txt for details
4
+
5
+ #include "Python.h"
6
+ #include <math.h>
7
+ #include <stdio.h>
8
+ #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
9
+ #include <numpy/arrayobject.h>
10
+
11
+ #ifdef _MSC_VER
12
+ #define inline __inline
13
+ #endif
14
+
15
+ #define lerp(t, a, b) ((a) + (t) * ((b) - (a)))
16
+
17
+ const float GRAD3[][3] = {
18
+ {1,1,0},{-1,1,0},{1,-1,0},{-1,-1,0},
19
+ {1,0,1},{-1,0,1},{1,0,-1},{-1,0,-1},
20
+ {0,1,1},{0,-1,1},{0,1,-1},{0,-1,-1},
21
+ {1,0,-1},{-1,0,-1},{0,-1,1},{0,1,1}};
22
+
23
+ // At the possible cost of unaligned access, we use char instead of
24
+ // int here to try to ensure that this table fits in L1 cache
25
+ const unsigned char PERM[] = {
26
+ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140,
27
+ 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120,
28
+ 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
29
+ 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71,
30
+ 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133,
31
+ 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161,
32
+ 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130,
33
+ 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250,
34
+ 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227,
35
+ 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44,
36
+ 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98,
37
+ 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34,
38
+ 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14,
39
+ 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121,
40
+ 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243,
41
+ 141, 128, 195, 78, 66, 215, 61, 156, 180, 151, 160, 137, 91, 90, 15, 131,
42
+ 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37,
43
+ 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252,
44
+ 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125,
45
+ 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158,
46
+ 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245,
47
+ 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187,
48
+ 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198,
49
+ 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126,
50
+ 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223,
51
+ 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167,
52
+ 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185,
53
+ 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179,
54
+ 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199,
55
+ 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236,
56
+ 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
57
+ 180};
58
+
59
+
60
+ static inline float
61
+ grad3(const int hash, const float x, const float y, const float z)
62
+ {
63
+ const int h = hash & 15;
64
+ return x * GRAD3[h][0] + y * GRAD3[h][1] + z * GRAD3[h][2];
65
+ }
66
+
67
+ float
68
+ noise3(float x, float y, float z, const int repeatx, const int repeaty, const int repeatz,
69
+ const int base)
70
+ {
71
+ float fx, fy, fz;
72
+ int A, AA, AB, B, BA, BB;
73
+ int i = (int)x;
74
+ int j = (int)y;
75
+ int k = (int)z;
76
+ int ii = (i + 1) % repeatx;
77
+ int jj = (j + 1) % repeaty;
78
+ int kk = (k + 1) % repeatz;
79
+ i = ((i + base) & 255);
80
+ j = ((j + base) & 255);
81
+ k = ((k + base) & 255);
82
+ ii = ((ii + base) & 255);
83
+ jj = ((jj + base) & 255);
84
+ kk = ((kk + base) & 255);
85
+
86
+ x -= (float)(int)x; y -= (float)(int)y; z -= (float)(int)z;
87
+ fx = x*x*x * (x * (x * 6 - 15) + 10);
88
+ fy = y*y*y * (y * (y * 6 - 15) + 10);
89
+ fz = z*z*z * (z * (z * 6 - 15) + 10);
90
+
91
+ A = PERM[i];
92
+ AA = PERM[A + j];
93
+ AB = PERM[A + jj];
94
+ B = PERM[ii];
95
+ BA = PERM[B + j];
96
+ BB = PERM[B + jj];
97
+
98
+ return lerp(fz, lerp(fy, lerp(fx, grad3(PERM[AA + k], x, y, z),
99
+ grad3(PERM[BA + k], x - 1, y, z)),
100
+ lerp(fx, grad3(PERM[AB + k], x, y - 1, z),
101
+ grad3(PERM[BB + k], x - 1, y - 1, z))),
102
+ lerp(fy, lerp(fx, grad3(PERM[AA + kk], x, y, z - 1),
103
+ grad3(PERM[BA + kk], x - 1, y, z - 1)),
104
+ lerp(fx, grad3(PERM[AB + kk], x, y - 1, z - 1),
105
+ grad3(PERM[BB + kk], x - 1, y - 1, z - 1))));
106
+ }
107
+
108
+ static PyObject *
109
+ make_perlin(PyObject *self, PyObject *args, PyObject *kwargs)
110
+ {
111
+ int octaves = 1;
112
+ float persistence = 0.5f;
113
+ float lacunarity = 2.0f;
114
+ int repeatx = 1024; // arbitrary
115
+ int repeaty = 1024; // arbitrary
116
+ int repeatz = 1024; // arbitrary
117
+ int base = 0;
118
+
119
+
120
+ float *x, *y, *z;
121
+ PyArrayObject *_x, *_y, *_z;
122
+ PyObject *__x, *__y, *__z;
123
+
124
+ static char *kwlist[] = {"x", "y", "z", "octaves", "persistence", "lacunarity",
125
+ "repeatx", "repeaty", "repeatz", "base", NULL};
126
+
127
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOO|iffiiii:noise3", kwlist,
128
+ &__x, &__y, &__z, &octaves, &persistence, &lacunarity, &repeatx, &repeaty, &repeatz, &base))
129
+ return NULL;
130
+ if (base < 0 || base > 255) {
131
+ PyErr_SetString(PyExc_ValueError, "Base must be between 0 and 255");
132
+ return NULL;
133
+ }
134
+ _x = (PyArrayObject*)PyArray_FROMANY(__x, NPY_FLOAT, 1, 1, NPY_ARRAY_C_CONTIGUOUS);
135
+ _y = (PyArrayObject*)PyArray_FROMANY(__y, NPY_FLOAT, 1, 1, NPY_ARRAY_C_CONTIGUOUS);
136
+ _z = (PyArrayObject*)PyArray_FROMANY(__z, NPY_FLOAT, 1, 1, NPY_ARRAY_C_CONTIGUOUS);
137
+ if (!_x || !_y || !_z)
138
+ return NULL;
139
+ x = (float*)PyArray_DATA(_x);
140
+ y = (float*)PyArray_DATA(_y);
141
+ z = (float*)PyArray_DATA(_z);
142
+ int len_x = PyArray_SIZE(_x);
143
+ int len_y = PyArray_SIZE(_y);
144
+ int len_z = PyArray_SIZE(_z);
145
+ int i,j,k,l;
146
+ float *ret = (float*)malloc(sizeof(float)*len_x*len_y*len_z);
147
+ if (x[len_x-1] >= repeatx || y[len_y-1] >= repeaty || z[len_z-1] >= repeatz) {
148
+ PyErr_SetString(PyExc_ValueError, "Cannot pass values greater than repeatx/y/z");
149
+ return NULL;
150
+ }
151
+
152
+ if (octaves == 1) {
153
+ // Single octave, return simple noise
154
+ for (i=0; i<len_x; i++) {
155
+ for (j=0; j<len_y; j++) {
156
+ for (k=0; k<len_z; k++) {
157
+ ret[i*len_y*len_z+j*len_z+k] = noise3(x[i], y[j], z[k],
158
+ repeatx, repeaty, repeatz, base);
159
+ }
160
+ }
161
+ }
162
+ } else if (octaves > 1) {
163
+ for (i=0; i<len_x; i++) {
164
+ for (j=0; j<len_y; j++) {
165
+ for (k=0; k<len_z; k++) {
166
+ float freq = 1.0f;
167
+ float amp = 1.0f;
168
+ float max = 0.0f;
169
+ float total = 0.0f;
170
+ for (l = 0; l < octaves; l++) {
171
+ total += noise3(x[i] * freq, y[j] * freq, z[k] * freq,
172
+ (const int)(repeatx*freq), (const int)(repeaty*freq), (const int)(repeatz*freq), base) * amp;
173
+ max += amp;
174
+ freq *= lacunarity;
175
+ amp *= persistence;
176
+ if (amp < .004) break; // No chance of influence beyond ~1/256
177
+ }
178
+ ret[i*len_y*len_z+j*len_z+k] = (float) (total / max);
179
+ }
180
+ }
181
+ }
182
+ } else {
183
+ PyErr_SetString(PyExc_ValueError, "Expected octaves value > 0");
184
+ return NULL;
185
+ }
186
+ npy_intp dims[3] = { len_x, len_y, len_z };
187
+ PyObject *retarray = PyArray_SimpleNewFromData(3, dims, NPY_FLOAT, ret);
188
+ PyArray_ENABLEFLAGS((PyArrayObject*)retarray, NPY_ARRAY_OWNDATA);
189
+ Py_DECREF(_x);
190
+ Py_DECREF(_y);
191
+ Py_DECREF(_z);
192
+ return retarray;
193
+ }
194
+
195
+ static PyMethodDef perlin_functions[] = {
196
+ {"make_perlin", (PyCFunction) make_perlin, METH_VARARGS | METH_KEYWORDS,
197
+ "Generate a perlin noise stimulus\n\
198
+ \n\
199
+ Parameters\n\
200
+ ----------\n\
201
+ x,y,z : 1D ndarray of float32, length > 0\n\
202
+ Grid of positions on which to compute the noise\n\
203
+ octaves : int > 0, default: 1\n\
204
+ Number of spatial scales to include\n\
205
+ persistence : float > 0, default: 0.5\n\
206
+ Relative strength of neighbouring octaves\n\
207
+ repeatx,repeaty,repeatz : int > 0, default: 1024\n\
208
+ Maximum x, y, or z value before the stimulus repeats\n\
209
+ base : int ∈ [0,255], default: 0\n\
210
+ Start position of the permutation, essentially the random seed\n\
211
+ \n\
212
+ Returns\n\
213
+ -------\n\
214
+ 3D int ndarray, values ∈ [0,255]\n\
215
+ Pink noise movie\n\
216
+ "},
217
+ {NULL}
218
+ };
219
+
220
+ PyDoc_STRVAR(module_doc, "Efficiently generate Perlin noise, based on the 'noise' package by Casey Duncan");
221
+
222
+ static struct PyModuleDef moduledef = {
223
+ PyModuleDef_HEAD_INIT,
224
+ "_perlin",
225
+ module_doc,
226
+ -1, /* m_size */
227
+ perlin_functions, /* m_methods */
228
+ NULL, /* m_reload (unused) */
229
+ NULL, /* m_traverse */
230
+ NULL, /* m_clear */
231
+ NULL /* m_free */
232
+ };
233
+
234
+ PyObject *
235
+ PyInit__perlin(void)
236
+ {
237
+ import_array();
238
+ return PyModule_Create(&moduledef);
239
+ }
@@ -0,0 +1 @@
1
+ __version__ = '0.1.0'
@@ -0,0 +1,51 @@
1
+ import imageio
2
+ from tqdm import tqdm
3
+ import warnings
4
+ from .util import generate_frames, filter_frames_index_function, apply_filters, discretize
5
+
6
+ def zebra_noise(output_file, xsize, ysize, tdur, levels=10, xyscale=.2, tscale=50, fps=30, xscale=1.0, yscale=1.0, seed=0, filters=[("comb", 0.08)]):
7
+ """Generate a .mp4 of zebra noise.
8
+
9
+ This method is a simplified interface for the PerlinStimulus class, designed to only generate zebra noise
10
+ as defined in the paper.
11
+
12
+ Parameters
13
+ ----------
14
+ output_file : string
15
+ Filename to save the generated .mp4 file
16
+ xsize, ysize : int
17
+ The x and y dimensions of the output video. (Sometimes these will be rounded up to multiples of 16.)
18
+ tdur : float
19
+ The duration of the video in seconds
20
+ levels : int
21
+ The number of octaves to use when approximating the 1/f spectrum. The default of 10 should be more than enough.
22
+ xyscale : float from (0,1)
23
+ The spatial scale of the Perlin noise. Values near 0 will make the video smoother and near 1 choppier.
24
+ tscale : int
25
+ A scaling factor to set the speed of the video
26
+ xscale, yscale : float
27
+ Resize the x and y dimensions of the output
28
+ fps : int
29
+ Frames per second
30
+ seed : int
31
+ Random seed
32
+
33
+ Returns
34
+ -------
35
+ None, but saves the video file to the desired filename
36
+ """
37
+ tsize = int(tdur*fps)
38
+ tscale = tscale * (fps/30)
39
+ textra = (tscale - (tsize % tscale)) % tscale
40
+ if textra > 0:
41
+ warnings.warn(f"Adding {textra} extra timepoints to make tscale a multiple of tdur")
42
+ tsize += round(textra)
43
+ get_index = filter_frames_index_function(filters, tsize)
44
+ writer = imageio.get_writer(output_file, fps=fps)
45
+ for _i in tqdm(range(0, tsize)):
46
+ i = get_index(_i)
47
+ frame = generate_frames(xsize, ysize, tsize, [i], levels=levels, xyscale=xyscale, tscale=tscale, xscale=xscale, yscale=yscale, seed=seed)
48
+ filtered = apply_filters(frame[None], filters)[0] # TODO I don't think this will work with the photodiode filter
49
+ disc = discretize(filtered[:,:,0])
50
+ writer.append_data(disc)
51
+ writer.close()
@@ -0,0 +1,267 @@
1
+ import hashlib
2
+ import os
3
+ import warnings
4
+ import tempfile
5
+ from subprocess import call
6
+ from pathlib import Path
7
+ import numpy as np
8
+ import imageio
9
+ from imageio_ffmpeg import get_ffmpeg_exe
10
+
11
+ from . import _perlin
12
+ from .util import generate_frames, filter_frames, filter_frames_index_function, XYSCALEBASE, discretize
13
+
14
+
15
+ class PerlinStimulus:
16
+ """Class to generate a Perlin noise stimulus.
17
+
18
+ This is the "heavy duty" way of generating stimuli, mostly for experimenting
19
+ with new Perlin-based stimuli. If you would just like to generate zebra
20
+ noise, use the "zebra_noise" function instead, which is faster and more
21
+ memory efficient.
22
+
23
+ To generate noise, instantiate this method. This will automatically
24
+ generate Perlin noise which you can save, filter, etc.
25
+
26
+ """
27
+ def __init__(self, xsize, ysize, tdur, levels=10, xyscale=.2, tscale=50, fps=30, xscale=1.0, yscale=1.0, seed=0, demean="both", cachedir="perlcache", delay_batch=False):
28
+ """Initialise Perlin noise stimulus.
29
+
30
+ Parameters
31
+ ----------
32
+ xsize,ysize : int > 0
33
+ The number of pixels in the x or y dimension
34
+ tdur : int > 0
35
+ The duration of the stimulus, in seconds. Sometimes will be slightly longer.
36
+ levels : int > 0, default: 10
37
+ The number of spatial scales to include. The default is usually okay.
38
+ xyscale : float ∈ [0,1], default: 0.5
39
+ The spatial grain scale of the noise, larger is more granular
40
+ xscale : float, default: 1.0
41
+ The spatial scale in x, larger is a bigger scale
42
+ yscale : float, default: 1.0
43
+ The spatial scale in y, larger is a bigger scale
44
+ tscale : int > 0, default: 1
45
+ The temporal scaling of the noise, larger is slower.
46
+ fps : int > 0, default: 30
47
+ Frames per second of the stimulus
48
+ seed : int ∈ [0, 255], default: 0
49
+ Random seed for the noise
50
+ demean : {'both', 'time', 'space', 'none'}, default: 'both'
51
+ Dimensions across which to fix the mean to zero
52
+ cachedir : str
53
+ A file path to the cache directory. Will contain large files.
54
+ delay_batch : bool
55
+ If true, do not immediately generate perlin noise. Manually call self.generate_batch() instead.
56
+
57
+ Notes
58
+ -----
59
+ This causes data to be written to the disk and long-running
60
+ computations to be performed
61
+
62
+ """
63
+ assert demean in ["both", "time", "space", "none"]
64
+ tsize = int(tdur*fps)
65
+ tscale = tscale
66
+ self.ratio = xsize/ysize*XYSCALEBASE
67
+ assert self.ratio == int(self.ratio), f"Ratio between x and y times {XYSCALEBASE} must be an integer"
68
+ self.ratio = int(self.ratio)
69
+ textra = (tscale - (tsize % tscale)) % tscale
70
+ if textra > 0:
71
+ warnings.warn(f"Adding {textra} extra timepoints to make tscale a multiple of tdur")
72
+ tsize += round(textra)
73
+ self.size = (xsize, ysize, tsize)
74
+ assert self.size[0] >= self.size[1], "Wrong orientation"
75
+ self.fps = fps
76
+ self.seed = seed
77
+ self.cachedir = Path(cachedir)
78
+ self.cachedir.mkdir(exist_ok=True)
79
+ self.tmpdir = Path(tempfile.mkdtemp())
80
+ self.xyscale = xyscale
81
+ self.tscale = tscale
82
+ self.levels = levels
83
+ self.demean = demean
84
+ self.xscale = xscale
85
+ self.yscale = yscale
86
+ self.batch_size = int(400000000/(self.size[0]*self.size[1]))
87
+ #self.batch_size = int(400000/(self.size[0]*self.size[1]))
88
+ if self.batch_size % 2 == 1: # Make sure it is an even number
89
+ self.batch_size += 1
90
+ if not delay_batch:
91
+ self.generate_batch()
92
+ def cache_filename(self, batch=None):
93
+ """Return the filename for the cache.
94
+
95
+ The cache is split up into different batches, saved in different files
96
+
97
+ Parameters
98
+ ----------
99
+ batch : int, 'stats', or None
100
+ The ID of the batch to find.
101
+
102
+ Returns
103
+ -------
104
+ str
105
+ The filename of the cache
106
+ """
107
+ h = hashlib.md5(str((self.size, self.fps, self.seed, self.xyscale, self.tscale, self.levels, self.demean)).encode()).hexdigest()
108
+ if batch is None:
109
+ return str(self.cachedir.joinpath(f"perlcache_{h}.npy"))
110
+ if batch == "stats":
111
+ return str(self.cachedir.joinpath(f"perlcache_{h}_stats.npz"))
112
+ return str(self.cachedir.joinpath(f"perlcache_{h}_{batch}.npy"))
113
+ def generate_frame(self, t=0, filters=[]):
114
+ """Generate and return a single image of noise.
115
+
116
+
117
+ Parameters
118
+ ----------
119
+ t : float
120
+ the frame at which to generate the image
121
+ filters : list of str and/or (str, ...) tuples
122
+ A list of filters to apply to the stimulus. If a filter requires
123
+ parameters, pass a tuple, where the first element is the name of
124
+ the filter and the subsequent elements are the parameters.
125
+
126
+ Returns
127
+ -------
128
+ 2-dimensional ndarray
129
+ An image of the noise
130
+ """
131
+ arr = generate_frames(self.size[0], self.size[1], self.size[2], timepoints=[t] if not hasattr(t, "__iter__") else t, levels=self.levels,
132
+ xyscale=self.xyscale, tscale=self.tscale, xscale=self.xscale,
133
+ yscale=self.yscale, fps=self.fps, seed=self.seed)
134
+ if self.demean in ["both", "time"]:
135
+ arr -= np.mean(arr, axis=(0,1), keepdims=True)
136
+ arr = apply_filters(arr, filters)
137
+ return arr.squeeze()
138
+
139
+ def generate_batch(self):
140
+ """Create the stimulus
141
+
142
+ Runs the _perlin C module and saves the output in batches.
143
+
144
+ If this function has already been run before and has been cached on the
145
+ filesytem, automatically load the statistics from it. Otherwise,
146
+ generate the stimuli and cache them.
147
+ """
148
+ # If the cache already exists, load the statistics (used for
149
+ # normalisation) and exit immediately.
150
+ if os.path.isfile(self.cache_filename("stats")):
151
+ stats = np.load(self.cache_filename("stats"))
152
+ self.min_ = stats['min_']
153
+ self.max_ = stats['max_']
154
+ self.nframes = stats['nframes']
155
+ return
156
+ # Generate the stimuli and save the means and mins/maxes. If we are
157
+ # demeaning across space, we won't end up using the mins or maxes saved
158
+ # here.
159
+ means_t = []
160
+ means_xy = []
161
+ weights = []
162
+ mins = []
163
+ maxes = []
164
+ for k in range(0, int(np.ceil(self.size[2]/self.batch_size))):
165
+ print(f"Generating batch {k}")
166
+ arr = generate_frames(self.size[0], self.size[1], tsize=self.size[2],
167
+ timepoints=list(range(k*self.batch_size,min(self.size[2],(k+1)*self.batch_size))),
168
+ levels=self.levels, xyscale=self.xyscale, tscale=self.tscale, xscale=self.xscale,
169
+ yscale=self.yscale, fps=self.fps, seed=self.seed)
170
+ if self.demean in ["both", "time"]:
171
+ arr -= np.mean(arr, axis=(0,1), keepdims=True)
172
+ mins.append(np.min(arr))
173
+ maxes.append(np.max(arr))
174
+ means_t.append(np.mean(arr, axis=2))
175
+ weights.append(arr.shape[2])
176
+ np.save(self.cache_filename(k), arr.astype("float16"))
177
+ del arr
178
+ # Compute the spatial mean (across t)
179
+ mean_t = np.sum(means_t*(np.asarray(weights)[:,None,None]), axis=0)/np.sum(weights)
180
+ nframes = np.sum(weights)
181
+ # Optionally remove the spatial mean. If so, iterate through and
182
+ # update all of the saved data files to remove the spatial mean.
183
+ if self.demean in ["both", "space"]:
184
+ k = 0
185
+ mins = []
186
+ maxes = []
187
+ while os.path.isfile(self.cache_filename(k)):
188
+ arr = np.load(self.cache_filename(k)).astype("float32")
189
+ arr -= mean_t[:,:,None]
190
+ mins.append(np.min(arr))
191
+ maxes.append(np.max(arr))
192
+ np.save(self.cache_filename(k), arr.astype("float16"))
193
+ del arr
194
+ k += 1
195
+ min_ = np.min(mins)
196
+ max_ = np.max(maxes)
197
+ # Save the mins and maxes.
198
+ np.savez_compressed(self.cache_filename("stats"), min_=min_, max_=max_, nframes=nframes)
199
+ self.min_ = min_
200
+ self.max_ = max_
201
+ self.nframes = nframes
202
+ def save_grey_pad(self, fn, dur, bitrate=20):
203
+ """Create a grey screen video which can be used to pad"""
204
+ one_frame = np.load(self.cache_filename(0), mmap_mode='r')[:,:,0].astype('uint8') * 0 + 127
205
+ imageio.imsave(self.tmpdir.joinpath("_grey.tif"), one_frame, format="pillow", compression="tiff_adobe_deflate")
206
+ n_frames = int(dur * self.fps)
207
+ for i in range(0, n_frames):
208
+ os.link(self.tmpdir.joinpath("_grey.tif"), self.tmpdir.joinpath(f"_frame{i:05}.tif"))
209
+ if fn[-4:] != ".mp4":
210
+ fn += ".mp4"
211
+ call([get_ffmpeg_exe(), "-r", str(self.fps), "-i", str(self.tmpdir.joinpath("_frame%5d.tif")), "-c:v", "mpeg2video", "-an", "-b:v", f"{bitrate}M", fn])
212
+ for p in self.tmpdir.glob("_frame*.tif"):
213
+ p.unlink()
214
+ self.tmpdir.joinpath("_grey.tif").unlink()
215
+ def save_video(self, fn, loop=1, filters=[], bitrate=20):
216
+ """Save the filtered stimulus.
217
+
218
+ Parameters
219
+ ----------
220
+ fn : str
221
+ The file name to save the video
222
+ loop : int > 0
223
+ The number of times the stimulus should loop in the saved video
224
+ filters : list of str and/or (str, ...) tuples
225
+ A list of filters to apply to the stimulus. If a filter requires
226
+ parameters, pass a tuple, where the first element is the name of
227
+ the filter and the subsequent elements are the parameters.
228
+ bitrate : int > 0
229
+ The bitrate in megabits per second. The default is good for binary
230
+ videos and pure noise, but a higher value may be necessary if
231
+ using the "wood" filter.
232
+ """
233
+ if Path(fn).exists():
234
+ raise IOError("Output video file already exists!")
235
+ i = 0
236
+ k = 0
237
+ filtind = filter_frames_index_function(filters, nframes=self.nframes)
238
+ while os.path.isfile(self.cache_filename(k)):
239
+ data = np.load(self.cache_filename(k)).astype("float32")
240
+ # Renormalise using precomputed mins/maxes
241
+ data -= self.min_
242
+ data *= 1/(self.max_-self.min_)
243
+ # Apply filters
244
+ for f in filters:
245
+ if isinstance(f, str):
246
+ n = f
247
+ args = []
248
+ else:
249
+ n = f[0]
250
+ args = f[1:]
251
+ data = filter_frames(data, n, *args)
252
+ data = discretize(data)
253
+ assert data.dtype == 'uint8'
254
+ for j in range(0, data.shape[2]):
255
+ imageio.imsave(self.tmpdir.joinpath(f"_frame{filtind(i):05}.tif"), data[:,:,j], format="pillow", compression="tiff_adobe_deflate")
256
+ i += 1
257
+ k += 1
258
+ del data
259
+ n_frames = i
260
+ for j in range(1, loop):
261
+ for i in range(0, n_frames):
262
+ os.link(self.tmpdir.joinpath(f"_frame{i:05}.tif"), self.tmpdir.joinpath(f"_frame{(i+j*n_frames):05}.tif"))
263
+ if fn[-4:] != ".mp4":
264
+ fn += ".mp4"
265
+ call([get_ffmpeg_exe(), "-r", str(self.fps), "-i", str(self.tmpdir.joinpath("_frame%5d.tif")), "-c:v", "mpeg2video", "-an", "-b:v", f"{bitrate}M", fn])
266
+ for p in self.tmpdir.glob("_frame*.tif"):
267
+ p.unlink()
@@ -0,0 +1,152 @@
1
+ import numpy as np
2
+ import scipy.ndimage
3
+ from . import _perlin
4
+
5
+ XYSCALEBASE = 100
6
+
7
+ def filter_frames(im, filt, *args):
8
+ """Apply a filter/transformation to an image batch
9
+
10
+ Parameters
11
+ ----------
12
+ im : 3D float ndarray, values ∈ [0,1]
13
+ Frames to filter
14
+ filt : str
15
+ The name of the filter
16
+ *args : tuple
17
+ Extra arguments are passed to the filter
18
+
19
+ Returns
20
+ -------
21
+ im : 3D float ndarray, values ∈ [0,1]
22
+ Filtered noise movie
23
+ """
24
+ if filt == "threshold":
25
+ return (im>args[0]).astype(np.float32)
26
+ if filt == "softthresh":
27
+ return 1/(1+np.exp(-args[0]*(im-.5)))
28
+ if filt == "comb":
29
+ return (im//args[0] % 2 == 1).astype(np.float32)
30
+ if filt == "invert":
31
+ return 1-im
32
+ if filt == "reverse":
33
+ return im # We need to use filter_index_function for this
34
+ if filt == "blur":
35
+ return np.asarray([scipy.ndimage.filters.gaussian_filter(im.astype(np.float32)[:,:,i], args[0], mode='wrap') for i in range(0, im.shape[2])]).transpose([1,2,0])
36
+ if filt == "wood":
37
+ return (im % args[0]) / args[0]
38
+ if filt == "center":
39
+ return 1-(np.abs(im-.5)*2)
40
+ if filt == "photodiode":
41
+ im = im.copy()
42
+ s = args[0]
43
+ im[:s,-s:,::2] = 0
44
+ im[:s,-s:,1::2] = 1
45
+ return im
46
+ if filt == "photodiode_anywhere":
47
+ im = im.copy()
48
+ x = args[0]
49
+ y = args[1]
50
+ s = args[2]
51
+ im[y:(y+s),x:(x+s),::2] = 0
52
+ im[y:(y+s),x:(x+s),1::2] = 1
53
+ return im
54
+ if filt == "photodiode_b2":
55
+ im = im.copy()
56
+ s = 125
57
+ im[:s,-s:,::2] = 0
58
+ im[:s,-s:,1::2] = 1
59
+ return im
60
+ if filt == "photodiode_fusi":
61
+ im = im.copy()
62
+ s = 75
63
+ im[:s,-s:,::2] = 0
64
+ im[:s,-s:,1::2] = 1
65
+ return im
66
+ if filt == "photodiode_bscope":
67
+ im = im.copy()
68
+ s = 100
69
+ im[-s:,:s,::2] = 0
70
+ im[-s:,:s,1::2] = 1
71
+ return im
72
+ if callable(filt):
73
+ return filt(im)
74
+ raise ValueError("Invalid filter specified")
75
+
76
+ def apply_filters(arr, filters):
77
+ for f in filters:
78
+ if isinstance(f, str):
79
+ n = f
80
+ args = []
81
+ else:
82
+ n = f[0]
83
+ args = f[1:]
84
+ arr = filter_frames(arr, n, *args)
85
+ return arr
86
+
87
+
88
+ def filter_frames_index_function(filters, nframes):
89
+ """Reordering frames in the video based on the filter.
90
+
91
+
92
+ Parameters
93
+ ----------
94
+ filters : list of strings or tuples
95
+ the list of filters passed to save_video
96
+
97
+ Returns
98
+ -------
99
+ function mapping int -> int
100
+ Reindexing function
101
+
102
+ Notes
103
+ -----
104
+ Some filters may need to operate on the global video instead of in
105
+ batches. However, for large videos, batches are necessary due to
106
+ limited amounts of RAM. Thus, this function should return another
107
+ function which takes an index as input and outputs a new index,
108
+ remapping the initial noise frame to the output video frame. This
109
+ was primarily designed to support reversing the video, but it might be
110
+ useful for other things too.
111
+
112
+ """
113
+ if "reverse" in filters:
114
+ return lambda x : nframes - x - 1
115
+ return lambda x : x
116
+
117
+ def discretize(im):
118
+ """Convert movie to an unsigned 8-bit integer
119
+
120
+ Parameters
121
+ ----------
122
+ im : 3D float ndarray, values ∈ [0,1]
123
+ Noise movie
124
+
125
+ Returns
126
+ -------
127
+ 3D int ndarray, values ∈ [0,255]
128
+ Noise movie
129
+ """
130
+ im *= 255
131
+ ret = im.astype(np.uint8)
132
+ return ret
133
+
134
+ def generate_frames(xsize, ysize, tsize, timepoints, levels=10, xyscale=.5, tscale=1, xscale=1.0, yscale=1.0, fps=30, seed=0):
135
+ """Preprocess arguments before passing to the C implementation of Perlin noise.
136
+ """
137
+ # Use the temporal scale and number of timepoints to compute how many
138
+ # units to make the stimulus across the temporal dimension
139
+ tunits = int(tsize/(tscale*(fps/30)))
140
+ ts_all = np.arange(0, tsize, dtype="float32")/(tscale*(fps/30))
141
+ ratio = int(xsize/ysize*XYSCALEBASE)
142
+ arr = _perlin.make_perlin(np.arange(0, xsize, dtype="float32")/ysize/xscale, # Yes, divide by y size
143
+ np.arange(0, ysize, dtype="float32")/ysize/yscale,
144
+ ts_all[timepoints],
145
+ octaves=levels,
146
+ persistence=xyscale,
147
+ repeatx=ratio,
148
+ repeaty=XYSCALEBASE,
149
+ repeatz=tunits,
150
+ base=seed)
151
+ arr = arr.swapaxes(0,1)
152
+ return arr
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.1
2
+ Name: zebranoise
3
+ Version: 0.1.0
4
+ Summary: Zebra noise stimuli for Python
5
+ Home-page: https://github.com/mwshinn/zebra_noise
6
+ Author: Max Shinn
7
+ Author-email: m.shinn@ucl.ac.uk
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: C
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Topic :: Multimedia :: Graphics
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE.txt
19
+
20
+ This library will efficiently generate visual stimulus videos using Perlin noise, including zebra noise. The core algorithm is in optimized C.
21
+
22
+ [Example zebra noise](http://www.youtube.com/watch?v=-SyjgbNCP4Q)
23
+
24
+ [![Example zebra noise](http://img.youtube.com/vi/-SyjgbNCP4Q/0.jpg)](http://www.youtube.com/watch?v=-SyjgbNCP4Q "Example zebra noise")
25
+
26
+ # Installation
27
+
28
+ ## From pypi
29
+
30
+ Run:
31
+
32
+ pip install zebranoise
33
+
34
+ ## From source
35
+
36
+ Make sure you have a C compiler installed. The easiest way to do this is by installing Cython.
37
+
38
+ Then, install using the standard
39
+
40
+ python setup.py install
41
+
42
+ This will compile the sources and install.
43
+
44
+ I have only tested this on Linux.
45
+
46
+ # Usage
47
+
48
+ ## Basic usage
49
+
50
+ If you would like to generate a zebra noise of size 640x480 for 2 minutes, you can run
51
+
52
+ import zebranoise
53
+ zebranoise.zebra_noise("output.mp4", xsize=640, ysize=480, tdur=60*2, fps=30, seed=0)
54
+
55
+ Other parameters control the spatial and temporal frequencies. For instance,
56
+
57
+ zebranoise.zebra_noise("output2.mp4", xsize=640, ysize=480, tdur=60*2, levels=10, xyscale=.2, tscale=50, fps=30, xscale=1.0, yscale=1.0, seed=0)
58
+
59
+ The meaning of these parameters is:
60
+
61
+ - **xsize** and **ysize**: the x and y dimensions of the output video. (Sometimes
62
+ these will be rounded up to multiples of 16.)
63
+ - **tdur**: the duration in seconds
64
+ - **levels**: The number of octaves to use when approximating the 1/f spectrum.
65
+ The default of 10 should be more than enough.
66
+ - **xyscale**: The spatial scale of the Perlin noise, from 0 to 1. Low values
67
+ will make the video smoother and high values choppier.
68
+ - **tscale**: The speed of the video
69
+ - **xscale** and **yscale**: Resize the x and y dimensions of the output.
70
+ - **fps**: Frames per second
71
+ - **seed**: Random seed
72
+
73
+ ## Advanced usage
74
+
75
+ This package can be used to generate new stimuli based on Perlin noise.
76
+
77
+ To generate a Perlin noise-based video, follow two steps with the package. First, generate the Perlin noise. This is the most time consuming step, and will also use a lot of temporary disk space. This is accomplished with:
78
+
79
+ ```python
80
+ noise = zebranoise.PerlinStimulus(xsize=400, ysize=100, tdur=60*2, xyscale=.2, tscale=50, seed=0)
81
+ ```
82
+
83
+ See the function documentation for perlstim.Perl for more information about modifying the properties of the noise.
84
+
85
+ Second, apply "filters" to the Perlin noise and save the resulting video. Filters include thresholds, sync squares in the corner, etc. If using a filter with no arguments, just pass a string naming the filter. If using a filter with arguments, pass a tuple where the first element is the name of the filter as a string, and the remaining elements are the arguments. For example,
86
+
87
+ ```python
88
+ noise.save_video("noise.mp4", filters=["reverse", ("comb", .05)])
89
+ ```
90
+
91
+ The above example applies the "reverse" filter (with no arguments) and the "comb" filter with the argument 0.1.
92
+
93
+ ## Filters
94
+
95
+ The following filters are currently defined:
96
+
97
+ - "threshold" (1 argument): set all values above the threshold argument to white, and all below to black.
98
+ - "softthresh" (1 argument): set values much greater than 0 to white and much less than zero to blac. Use shades of grey for intermediate values, according to a sigmoid with a temperature given as the argument.
99
+ - "comb" (1 argument): Alternate black and white thresholds at intervals given by the argument.
100
+ - "reverse" (0 arguments): play the video in reverse
101
+ - "invert" (0 arguments): switch white and black
102
+ - "wood" (1 argument): similar to "comb", but use a sawtooth wave
103
+ - "blur" (1 argument): Gaussian blur with the given spatial standard deviation
104
+ - "photodiode" (1 argument): Draw a sync square in the corner, with a size given by the argument.
105
+ - "photodiode_anywhere" (3 arguments): Draw a sync square at the coordinates given by the first two arguments (x and y), and of size given by argument 3.
106
+ - [any function] (0 arguments): If you pass a function, the function will be applied to each chunk of the video. Chunks consist of the entire x and y but a subset of z, guaranteed to be an even number.
107
+
108
+ ## Example
109
+
110
+ To get started, try the following:
111
+
112
+ ```python
113
+ import zebranoise
114
+ stim = zebranoise.PerlinStimulus(xsize=480, ysize=128, tdur=60*5, xyscale=.2, tscale=50)
115
+ stim.save_video("perlin_stimulus.mp4", loop=1, filters=[("comb", .08), ("photodiode", 30)])
116
+ ```
117
+ # Other information
118
+
119
+ Much of the C code is based on [Casey Duncan's "noise" package for Python](https://github.com/caseman/noise), under the MIT license.
120
+
121
+
@@ -0,0 +1,15 @@
1
+ LICENSE.txt
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ zebranoise/__init__.py
6
+ zebranoise/_perlin.c
7
+ zebranoise/_version.py
8
+ zebranoise/easy.py
9
+ zebranoise/perlin_stimulus.py
10
+ zebranoise/util.py
11
+ zebranoise.egg-info/PKG-INFO
12
+ zebranoise.egg-info/SOURCES.txt
13
+ zebranoise.egg-info/dependency_links.txt
14
+ zebranoise.egg-info/requires.txt
15
+ zebranoise.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ numpy
2
+ imageio
3
+ imageio-ffmpeg
4
+ tqdm
@@ -0,0 +1 @@
1
+ zebranoise