image-math 1.0.0__py3-none-any.whl

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.
image_math/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .main import image_function, img_func
2
+ from .main import escher_function, rotate, zoom_into_corner
3
+ from .main import kaleidoscope, assymetrical_kaleidoscope, logarithm, inverse, weierstrass
image_math/main.py ADDED
@@ -0,0 +1,160 @@
1
+ """
2
+ Provide the vectorised inverted function. e.g.
3
+ Droste-to-Escher Image:
4
+
5
+ z` = e ^ (ln(z) * c)
6
+ where, c = e ^ (i * arctan(ln(scale)/2pi))
7
+
8
+ Inverted function:
9
+ z = e ^ (ln(z`) * (1/c))
10
+
11
+ The second (i.e. inverted function) is what we want
12
+
13
+ Vectorised: Uses numpy functions, allowing a tremendous speed up.
14
+ """
15
+
16
+ import numpy as np
17
+ from PIL import Image
18
+ from skimage.draw import polygon
19
+ from tqdm import tqdm
20
+
21
+ def image_function(my_func):
22
+ def apply_function(input_image:str, output_image:str, zoom = 1.0, output_scale = 1.0, *args, **kwargs):
23
+ """
24
+ scale: refers to range of z to try out. Input is mapped from f(± 1 ± i) * scale. Output is always mapped from ± 1 ± i
25
+ """
26
+ img = Image.open(input_image)
27
+ arr = np.array(img)
28
+
29
+
30
+ h,w,channels = arr.shape
31
+ ho,wo = int(h*output_scale),int(w*output_scale)
32
+ arr2 = np.zeros((ho,wo,channels), dtype=arr.dtype)
33
+
34
+
35
+ empty_pixel = (0,0,0,255) if channels == 4 else (0,0,0)
36
+
37
+ x = np.linspace(-1,1, wo+1)
38
+ y = np.linspace(-1,1,ho+1)
39
+
40
+ mesh_output = x[None, :] + 1j * y[:,None]
41
+ mesh_input = my_func(mesh_output/zoom, *args, **kwargs) # Gets input mesh
42
+
43
+ #Convert mesh to pixel borders
44
+ mesh_inp_x = (mesh_input.real * ((w-1)/2)) + ((w-1)/2)
45
+ mesh_inp_y = (mesh_input.imag * ((h-1)/2)) + ((h-1)/2)
46
+
47
+ for y in tqdm(range(ho)):
48
+ for x in range(wo):
49
+ x_l = np.array([mesh_inp_x[y,x], mesh_inp_x[y,x+1], mesh_inp_x[y+1,x+1], mesh_inp_x[y+1,x]])
50
+ y_l = np.array([mesh_inp_y[y,x], mesh_inp_y[y,x+1], mesh_inp_y[y+1,x+1], mesh_inp_y[y+1,x]])
51
+
52
+ if not (np.isfinite(x_l).all() and np.isfinite(y_l).all()):
53
+ arr2[y, x] = empty_pixel
54
+ continue
55
+
56
+ #Draw mask
57
+ rr, cc = polygon(y_l, x_l, shape=(h,w))
58
+
59
+ #Get final pixel
60
+ if len(rr):
61
+ arr2[y,x] = arr[rr,cc].mean(axis=0)
62
+ else:
63
+ inside = ((0 <= x_l) & (x_l <= w-1) & (0 <= y_l) & (y_l <= h-1)).any()
64
+ if inside:
65
+ ix = np.clip(x_l.mean(), 0, w-1)
66
+ iy = np.clip(y_l.mean(), 0, h-1)
67
+ arr2[y,x] = arr[int(round(iy)), int(round(ix))]
68
+ else:
69
+ arr2[y,x] = empty_pixel
70
+
71
+ Image.fromarray(arr2).save(output_image)
72
+
73
+
74
+
75
+ return apply_function
76
+
77
+ def img_func(my_func):
78
+ return image_function(my_func) #For pylance
79
+
80
+ def escher_function(scale:float, center = (0,0), subject = (0,0), rev_warp = False):
81
+ """
82
+ Creates a function to convert a repeating picture into an escher-style image
83
+ scale: size of edge of image compared to 1st nested image.
84
+ center: center of repeating pattern in droste image (center = 0,0; top right = (1,1))
85
+ subject: subject position (remains stationary-ish)
86
+ """
87
+ b = 1 + 1j*(np.log(scale)/(2*np.pi)) * (-1 if rev_warp else 1)
88
+ c = center[0] + 1j* center[1]
89
+
90
+ d = 0 + 0j if subject == (0,0) else np.log(subject[0] + 1j * subject[1])
91
+
92
+ @image_function
93
+ def f(z):
94
+ w = z-c
95
+ w = np.where(w == 0, 1e-8 + 1e-8j, w) #Buffer
96
+
97
+ log_w_new = np.log(w)
98
+ low_w_old = b * (log_w_new - d) + d
99
+ z_raw = np.exp(low_w_old)
100
+
101
+ mag = np.abs(z_raw)
102
+ wrap_p = np.floor(-np.log(mag)/np.log(scale))
103
+ return z_raw * (scale ** wrap_p) + c
104
+
105
+ return f
106
+
107
+ def wp_lattice(w1 = 1, w2 = np.exp(1j * np.pi / 3), N = 10, scale = 5):
108
+ w1 *= scale
109
+ w2 *= scale
110
+
111
+ m = np.arange(-N, N+1)
112
+ n = np.arange(-N, N+1)
113
+
114
+ A, B = np.meshgrid(m,n, indexing='ij')
115
+
116
+ w = A*w1 + B*w2
117
+ return w[~((A==0)&(B==0))]
118
+
119
+ # ----
120
+ # Cool functions to try out
121
+ @image_function
122
+ def rotate(z):
123
+ return z * 1j
124
+
125
+ @image_function
126
+ def zoom_into_corner(z):
127
+ return (z/3) + 2j/3 + 2/3
128
+
129
+ @image_function
130
+ def kaleidoscope(z):
131
+ return np.pow(z,6)
132
+
133
+ @image_function
134
+ def assymetrical_kaleidoscope(z):
135
+ return np.pow(z,3) - 3*np.pow(z,2)
136
+
137
+ @image_function
138
+ def logarithm(z):
139
+ return np.exp(z)
140
+
141
+ @image_function
142
+ def inverse(z):
143
+ return 1/z
144
+
145
+ WP_LATTICE = None
146
+ @image_function
147
+ def weierstrass(z):
148
+ global WP_LATTICE
149
+ if WP_LATTICE is None:
150
+ WP_LATTICE = wp_lattice()
151
+
152
+ w = WP_LATTICE[:,None,None]
153
+ r = (1/(z**2))
154
+
155
+ for wi in w:
156
+ r += (1/((z-wi)**2)) - (1/(wi**2)) #For memory reasons
157
+
158
+ return r
159
+
160
+
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.1
2
+ Name: image_math
3
+ Version: 1.0.0
4
+ Requires-Dist: numpy>=2.0.0
5
+ Requires-Dist: Pillow>=10.0.0
6
+ Requires-Dist: tqdm>=4.0.0
7
+ Requires-Dist: scikit-image>=0.24.0
8
+
@@ -0,0 +1,6 @@
1
+ image_math/__init__.py,sha256=0KK7QkqDaBS5fLL7Gudrq7xY78EQN-U10Um_Ecyt0fk,196
2
+ image_math/main.py,sha256=cvzh9xei0DXN0B3Lr-jXritl3w4N4cUs3Wu2Er4vf_g,4669
3
+ image_math-1.0.0.dist-info/METADATA,sha256=z4fWKOkugdBtouS8xzyfIfaJjIODy7RxEDQtyzIXeuk,184
4
+ image_math-1.0.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
5
+ image_math-1.0.0.dist-info/top_level.txt,sha256=bAtBRWKz0Z_rTr0U4DlCusY2TL_yl_uXEyRKJS43V1w,11
6
+ image_math-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ image_math