TuringsSolutions commited on
Commit
85a96c5
·
verified ·
1 Parent(s): 2ff4d2d

Create flc_core.py

Browse files
Files changed (1) hide show
  1. flc_core.py +244 -0
flc_core.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, struct, math
2
+ import numpy as np
3
+ from PIL import Image, ImageDraw
4
+
5
+ PHI = (1.0 + 5.0**0.5) / 2.0
6
+ MAGIC = b"FLC1\x00\x00\x00\x00"
7
+ VER_V2 = 2
8
+
9
+ # --- Fibonacci & Frequency Logic ---
10
+ def fibonacci_sequence(n):
11
+ fibs = [1, 2]
12
+ while len(fibs) < n:
13
+ fibs.append(fibs[-1] + fibs[-2])
14
+ return np.array(fibs[:n], dtype=np.int64)
15
+
16
+ def fibonacci_sequence_std(n):
17
+ fibs = [1, 1]
18
+ while len(fibs) < n:
19
+ fibs.append(fibs[-1] + fibs[-2])
20
+ return np.array(fibs[:n], dtype=np.int64)
21
+
22
+ def fibonacci_frequency_boundaries(n_coeffs: int, n_bands: int):
23
+ if n_bands < 2: return [0, n_coeffs]
24
+ fibs = fibonacci_sequence(n_bands).astype(np.float64)
25
+ w = fibs / (fibs.sum() + 1e-12)
26
+ cum = np.cumsum(w)
27
+ b = [0]
28
+ for i in range(n_bands - 1):
29
+ b.append(int(round(n_coeffs * cum[i])))
30
+ b.append(n_coeffs)
31
+ # Ensure strict monotonicity
32
+ for i in range(1, len(b)):
33
+ if b[i] <= b[i-1]: b[i] = b[i-1] + 1
34
+ return b
35
+
36
+ # --- Orthonormal DCT Engine ---
37
+ def dct_ortho_1d(x: np.ndarray) -> np.ndarray:
38
+ N = x.shape[0]
39
+ v = np.concatenate([x, x[::-1]])
40
+ V = np.fft.fft(v)
41
+ k = np.arange(N)
42
+ X = np.real(V[:N] * np.exp(-1j * np.pi * k / (2 * N)))
43
+ X *= 2.0
44
+ X[0] *= (1.0 / math.sqrt(4 * N))
45
+ X[1:] *= (1.0 / math.sqrt(2 * N))
46
+ return X
47
+
48
+ def idct_ortho_1d(X: np.ndarray) -> np.ndarray:
49
+ N = X.shape[0]
50
+ x0, xr = X[0] * math.sqrt(4 * N), X[1:] * math.sqrt(2 * N)
51
+ c = np.empty(N, dtype=np.complex128)
52
+ c[0], c[1:] = x0 / 2.0, xr / 2.0
53
+ k = np.arange(N)
54
+ c = c * np.exp(1j * np.pi * k / (2 * N))
55
+ V = np.zeros(2 * N, dtype=np.complex128)
56
+ V[:N] = c
57
+ V[N+1:] = np.conj(c[1:][::-1])
58
+ return np.fft.ifft(V).real[:N]
59
+
60
+ def dct_blocks_ortho(x_blocks: np.ndarray) -> np.ndarray:
61
+ return np.array([dct_ortho_1d(b) for b in x_blocks])
62
+
63
+ def idct_blocks_ortho(X_blocks: np.ndarray) -> np.ndarray:
64
+ return np.array([idct_ortho_1d(B) for B in X_blocks])
65
+
66
+ # --- Fibonacci Coding (Bit IO) ---
67
+ class BitWriter:
68
+ def __init__(self):
69
+ self.buf, self.acc, self.nbits = bytearray(), 0, 0
70
+ def write_bit(self, b: int):
71
+ self.acc = (self.acc << 1) | (b & 1)
72
+ self.nbits += 1
73
+ if self.nbits == 8:
74
+ self.buf.append(self.acc); self.acc = 0; self.nbits = 0
75
+ def finish(self):
76
+ if self.nbits: self.buf.append(self.acc << (8 - self.nbits))
77
+ return bytes(self.buf)
78
+
79
+ class BitReader:
80
+ def __init__(self, data: bytes):
81
+ self.data, self.i, self.acc, self.nbits = data, 0, 0, 0
82
+ def read_bit(self):
83
+ if self.nbits == 0:
84
+ self.acc = self.data[self.i]; self.i += 1; self.nbits = 8
85
+ b = (self.acc >> (self.nbits - 1)) & 1
86
+ self.nbits -= 1
87
+ return b
88
+
89
+ def fib_encode_nonneg(bw, n):
90
+ m = int(n) + 1
91
+ fibs = [1, 2]
92
+ while fibs[-1] <= m: fibs.append(fibs[-1] + fibs[-2])
93
+ bits = [0] * (len(fibs) - 1)
94
+ for i in reversed(range(len(bits))):
95
+ if fibs[i] <= m: bits[i] = 1; m -= fibs[i]
96
+ for i in range(max((i for i, b in enumerate(bits) if b), default=0) + 1):
97
+ bw.write_bit(bits[i])
98
+ bw.write_bit(1)
99
+
100
+ def fib_decode_nonneg(br):
101
+ fibs, bits, prev = [1, 2], [], 0
102
+ while True:
103
+ b = br.read_bit(); bits.append(b)
104
+ if prev == 1 and b == 1: break
105
+ prev = b
106
+ if len(bits) > len(fibs): fibs.append(fibs[-1] + fibs[-2])
107
+ m = sum(fibs[i] for i, bi in enumerate(bits[:-1]) if bi)
108
+ return m - 1
109
+
110
+ def rle_fib_encode_ints(ints):
111
+ bw = BitWriter()
112
+ zrun = 0
113
+ for v in ints:
114
+ if v == 0: zrun += 1; continue
115
+ if zrun: bw.write_bit(0); fib_encode_nonneg(bw, zrun); zrun = 0
116
+ bw.write_bit(1); fib_encode_nonneg(bw, (v << 1) ^ (v >> 63))
117
+ if zrun: bw.write_bit(0); fib_encode_nonneg(bw, zrun)
118
+ return bw.finish()
119
+
120
+ def rle_fib_decode_ints(payload, n_out):
121
+ br, out, i = BitReader(payload), np.zeros(n_out, dtype=np.int64), 0
122
+ while i < n_out:
123
+ if br.read_bit() == 0: i = min(n_out, i + fib_decode_nonneg(br))
124
+ else:
125
+ u = fib_decode_nonneg(br)
126
+ out[i] = (u >> 1) ^ (-(u & 1)); i += 1
127
+ return out
128
+
129
+ # --- Quantization & Spiral Visuals ---
130
+ def band_quantize_dct(coeffs, boundaries, base_step):
131
+ q = np.zeros_like(coeffs, dtype=np.int32)
132
+ for bi in range(len(boundaries) - 1):
133
+ a, b = boundaries[bi], boundaries[bi + 1]
134
+ step = base_step * (PHI ** bi)
135
+ q[:, a:b] = np.round(coeffs[:, a:b] / step)
136
+ return q
137
+
138
+ def band_dequantize_dct(q, boundaries, base_step):
139
+ coeffs = np.zeros_like(q, dtype=np.float64)
140
+ for bi in range(len(boundaries) - 1):
141
+ a, b = boundaries[bi], boundaries[bi + 1]
142
+ step = base_step * (PHI ** bi)
143
+ coeffs[:, a:b] = q[:, a:b] * step
144
+ return coeffs
145
+
146
+ def hologram_spectrum_image(zints, max_symbols=262144):
147
+ z = zints[:max_symbols]; v = np.tanh(z / 32.0)
148
+ theta = (2 * math.pi / (PHI**2)) * np.arange(v.size) + 2.0 * math.pi * (v * 0.25)
149
+ r = 1.0 + 0.35 * np.abs(v)
150
+ syms = r * np.cos(theta) + 1j * r * np.sin(theta)
151
+ N = int(2**math.ceil(math.log2(math.sqrt(syms.size or 1))))
152
+ U = np.pad(syms, (0, N*N - syms.size)).reshape(N, N)
153
+ mag = np.log1p(np.abs(np.fft.fftshift(np.fft.fft2(U))))
154
+ mag = (mag - mag.min()) / (mag.max() - mag.min() + 1e-12)
155
+ return (mag * 255).astype(np.uint8)
156
+
157
+ def bytes_to_fib_spiral_image(data, max_pixels=262144):
158
+ arr = np.frombuffer(data, dtype=np.uint8)[:max_pixels]
159
+ fibs = fibonacci_sequence_std(32)
160
+ sizes, area = [], 0
161
+ for s in fibs:
162
+ sizes.append(int(s)); area += s*s
163
+ if area >= arr.size: break
164
+ # Simple tile placement logic for demo
165
+ tiles, minx, miny, maxx, maxy = [], 0, 0, 0, 0
166
+ curr_x, curr_y = 0, 0
167
+ for i, s in enumerate(sizes):
168
+ d = (i-1)%4
169
+ if i>0:
170
+ if d==0: curr_x = maxx; curr_y = miny
171
+ elif d==1: curr_x = maxx-s; curr_y = maxy
172
+ elif d==2: curr_x = minx-s; curr_y = maxy-s
173
+ else: curr_x = minx; curr_y = miny-s
174
+ tiles.append((curr_x, curr_y, s))
175
+ minx, miny = min(minx, curr_x), min(miny, curr_y)
176
+ maxx, maxy = max(maxx, curr_x+s), max(maxy, curr_y+s)
177
+
178
+ W, H = maxx-minx, maxy-miny
179
+ img = np.zeros((H, W), dtype=np.uint8)
180
+ idx = 0
181
+ for x, y, s in tiles:
182
+ take = min(s*s, arr.size - idx)
183
+ if take <= 0: break
184
+ block = np.pad(arr[idx:idx+take], (0, s*s-take)).reshape(s, s)
185
+ img[H-(y-miny+s):H-(y-miny), x-minx:x-minx+s] = block
186
+ idx += take
187
+ return img, tiles, (minx, miny, maxx, maxy)
188
+
189
+ # --- High Level API ---
190
+ def flc_encode_file(in_path, out_flc, preview_png=None, unzip_gif=None, block_len=1024, n_bands=10, base_step=0.004, **kwargs):
191
+ raw = open(in_path, "rb").read()
192
+ x = (np.frombuffer(raw, dtype=np.uint8).astype(np.float64) - 127.5) / 127.5
193
+ pad = (-x.size) % block_len
194
+ X = np.pad(x, (0, pad)).reshape(-1, block_len)
195
+ C = dct_blocks_ortho(X)
196
+ bnds = fibonacci_frequency_boundaries(block_len, n_bands)
197
+ Q = band_quantize_dct(C, bnds, base_step)
198
+ payload = rle_fib_encode_ints(np.diff(Q.flatten(), prepend=0))
199
+
200
+ header = struct.pack("<8sH Q I I H d d H", MAGIC, VER_V2, len(raw), block_len, X.shape[0], n_bands, base_step, 127.5, len(bnds))
201
+ with open(out_flc, "wb") as f:
202
+ f.write(header); f.write(struct.pack("<"+ "I"*len(bnds), *bnds))
203
+ f.write(struct.pack("<I", len(payload))); f.write(payload)
204
+
205
+ if unzip_gif: # Generate the unzip frames
206
+ frames = []
207
+ for t in range(1, n_bands + 1):
208
+ Q_p = np.zeros_like(Q)
209
+ for bi in range(t): Q_p[:, bnds[bi]:bnds[bi+1]] = Q[:, bnds[bi]:bnds[bi+1]]
210
+ X_p = idct_blocks_ortho(band_dequantize_dct(Q_p, bnds, base_step))
211
+ recon = np.clip((X_p.flatten()[:len(raw)] * 127.5) + 127.5, 0, 255).astype(np.uint8)
212
+
213
+ # Create Frame
214
+ h_img = Image.fromarray(hologram_spectrum_image(Q_p.flatten())).resize((256, 256))
215
+ s_img_arr, _, _ = bytes_to_fib_spiral_image(recon.tobytes())
216
+ s_img = Image.fromarray(s_img_arr).resize((256, 256))
217
+
218
+ frame = Image.new("RGB", (512, 280), (10, 10, 15))
219
+ frame.paste(h_img, (0, 24)); frame.paste(s_img, (256, 24))
220
+ frames.append(frame)
221
+ frames[0].save(unzip_gif, save_all=True, append_images=frames[1:], duration=100, loop=0)
222
+
223
+ return {"n_bytes": len(raw), "payload_len": len(payload), "ratio": len(payload)/len(raw)}
224
+
225
+ def flc_decode_file(in_flc, out_path):
226
+ blob = open(in_flc, "rb").read()
227
+ h_sz = struct.calcsize("<8sH Q I I H d d H")
228
+ magic, ver, n_bytes, b_len, n_blks, n_bnds, step, mu, bnd_l = struct.unpack_from("<8sH Q I I H d d H", blob)
229
+ off = h_sz
230
+ bnds = struct.unpack_from("<" + "I"*bnd_l, blob, off); off += 4*bnd_l
231
+ p_len = struct.unpack_from("<I", blob, off)[0]; off += 4
232
+ d = rle_fib_decode_ints(blob[off:off+p_len], n_blks * b_len)
233
+ Q = np.cumsum(d).reshape(n_blks, b_len)
234
+ X = idct_blocks_ortho(band_dequantize_dct(Q, bnds, step))
235
+ res = np.clip((X.flatten()[:n_bytes] * 127.5) + mu, 0, 255).astype(np.uint8)
236
+ with open(out_path, "wb") as f: f.write(res.tobytes())
237
+ return {"n_bytes": n_bytes}
238
+
239
+ def cosine_similarity_bytes(a, b):
240
+ x = np.frombuffer(a, dtype=np.uint8).astype(float)
241
+ y = np.frombuffer(b, dtype=np.uint8).astype(float)
242
+ n = min(len(x), len(y))
243
+ x, y = x[:n]-x[:n].mean(), y[:n]-y[:n].mean()
244
+ return np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y) + 1e-12)