Spaces:
Runtime error
Runtime error
Commit
·
47b3fde
1
Parent(s):
54db940
added app
Browse files- app.py +111 -0
- requirements.txt +4 -0
app.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from scipy.fft import fftn, ifftn, fftshift, fftfreq
|
| 3 |
+
import streamlit as st
|
| 4 |
+
import matplotlib.pyplot as plt
|
| 5 |
+
import subprocess
|
| 6 |
+
|
| 7 |
+
image_seed = st.sidebar.slider("Image Seed", 0, 10000, 0)
|
| 8 |
+
transform_seed = st.sidebar.slider("Transform Seed", 0, 10000, 0)
|
| 9 |
+
transform_level = st.sidebar.slider("Transform Level", 0.0, 100.0, 0.0)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def random_channel(n, rand, fpower=2.0):
|
| 13 |
+
freq = fftn(rand.rand(n, n))
|
| 14 |
+
|
| 15 |
+
fx = fftfreq(n)[:, None]
|
| 16 |
+
fy = fftfreq(n)[None, :]
|
| 17 |
+
|
| 18 |
+
# combine as l2 norm of freq
|
| 19 |
+
f = (fx**2 + fy**2) ** 0.5
|
| 20 |
+
|
| 21 |
+
i = f > 0
|
| 22 |
+
freq[i] /= f[i] ** fpower
|
| 23 |
+
freq[0, 0] = 0.0
|
| 24 |
+
|
| 25 |
+
data = np.real(ifftn(freq))
|
| 26 |
+
data -= data.min()
|
| 27 |
+
data /= data.max()
|
| 28 |
+
return data
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@st.cache_data
|
| 32 |
+
def random_gray(n, seed, fpower=2.0):
|
| 33 |
+
rand = np.random.RandomState(seed)
|
| 34 |
+
return random_channel(n, rand, fpower)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@st.cache_data
|
| 38 |
+
def random_color(n, seed):
|
| 39 |
+
rand = np.random.RandomState(seed)
|
| 40 |
+
return np.stack([random_channel(n, rand) for _ in range(3)], 2)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def rotate_image(img, i, j, a):
|
| 44 |
+
c = np.cos(a)
|
| 45 |
+
s = np.sin(a)
|
| 46 |
+
|
| 47 |
+
img = 2.0 * img - 1.0
|
| 48 |
+
x, y = img[:, :, i], img[:, :, j]
|
| 49 |
+
img[:, :, i], img[:, :, j] = c * x + s * y, -s * x + c * y
|
| 50 |
+
img -= img.min()
|
| 51 |
+
img /= img.max()
|
| 52 |
+
return img
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
n = 512
|
| 56 |
+
img = random_color(n, image_seed)
|
| 57 |
+
|
| 58 |
+
a = random_color(n, transform_seed) * transform_level
|
| 59 |
+
img = rotate_image(img, 0, 1, a[:, :, 0])
|
| 60 |
+
img = rotate_image(img, 0, 2, a[:, :, 1])
|
| 61 |
+
img = rotate_image(img, 1, 2, a[:, :, 2])
|
| 62 |
+
|
| 63 |
+
"""
|
| 64 |
+
# Output
|
| 65 |
+
|
| 66 |
+
Play around with the parameters on the left to change the image.
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
st.image(img, width=600)
|
| 70 |
+
|
| 71 |
+
"""
|
| 72 |
+
The image is generated as follows:
|
| 73 |
+
|
| 74 |
+
1. Generate 6D cube of "brownian noise". We'll name these coordinates (R, G, B, A1, A2, A3).
|
| 75 |
+
2. Apply a rotation of angle A1*Level to (R, G) plane.
|
| 76 |
+
3. Apply a rotation of angle A2*Level to (R, B) plane.
|
| 77 |
+
4. Apply a rotation of angle A3*Level to (G, B) plane.
|
| 78 |
+
5. The resulting (R, G, B) data is the image.
|
| 79 |
+
|
| 80 |
+
# Background
|
| 81 |
+
|
| 82 |
+
I was inspired to write this after seeing some neat examples from the [accidental noise library](http://accidentalnoise.sourceforge.net).
|
| 83 |
+
|
| 84 |
+
My approach to doing this was:
|
| 85 |
+
|
| 86 |
+
1. Generate nice looking 2D noise. I found that a 2D analog of [brownian noise](https://en.wikipedia.org/wiki/Colors_of_noise#Brownian_noise) looked pretty good.
|
| 87 |
+
2. Stack 6 independent channels to form the 6D cube.
|
| 88 |
+
3. Apply the rotations outlined above.
|
| 89 |
+
|
| 90 |
+
Out of these steps, I found the most interesting bit to be generating the 2D noise.
|
| 91 |
+
|
| 92 |
+
There seem to be many approaches for doing this, including classic approaches like [Perlin](https://en.wikipedia.org/wiki/Perlin_noise) and [Simplex](https://en.wikipedia.org/wiki/Simplex_noise) noise. Since I had access to good numeric libraries, I ended up going straight to the following FFT based approach.
|
| 93 |
+
|
| 94 |
+
First, we generate a 2D image of uniformly sampled random noise. It pretty much looks like the static on your TV a.k.a. white noise.
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
st.image(random_gray(512, 32, 0.0))
|
| 98 |
+
|
| 99 |
+
"""
|
| 100 |
+
White noise looks pretty harsh, so we want to smooth it out to get brownian noise.
|
| 101 |
+
|
| 102 |
+
To do this, we take the 2D FFT to get to the frequency domain and then rescale things so they fall off like 1/f^2. To be nitpicky, I only found a general definition of brownian noise in 1D. Still... we're doing something very similar by rescaling by "one over the Euclidean norm squared".
|
| 103 |
+
|
| 104 |
+
Finally, we take the inverse 2D FFT to get our image back.
|
| 105 |
+
"""
|
| 106 |
+
|
| 107 |
+
st.image(random_gray(512, 32))
|
| 108 |
+
|
| 109 |
+
"""
|
| 110 |
+
Noice!
|
| 111 |
+
"""
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy
|
| 2 |
+
scipy
|
| 3 |
+
matplotlib
|
| 4 |
+
streamlit
|