TuringsSolutions commited on
Commit
530e015
·
verified ·
1 Parent(s): e348e25

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -829
app.py CHANGED
@@ -1,831 +1,136 @@
1
- # ============================================================
2
- # FLC v1.3 — Standalone Fibonacci Lattice Compression
3
- # + Real .flc file format (v2 header)
4
- # + Hologram preview PNG
5
- # + "Holographic Unzip" progressive GIF (REAL partial reconstructions)
6
- # + max_bands horizon control
7
- # + Fibonacci spiral unfold layout (right panel)
8
- # + NEW: Event-horizon ring overlay (left panel)
9
- # + NEW: Fibonacci tile outlines (right panel)
10
- #
11
- # Notebook usage:
12
- # enc = flc_encode_file("in.bin","out.flc","preview.png", unzip_gif="unzip.gif",
13
- # block_len=1024, n_bands=10, base_step=0.004)
14
- # dec = flc_decode_file("out.flc","recovered.bin", unzip_gif="decode_unzip.gif", max_bands=None)
15
- #
16
- # Partial/horizon:
17
- # decp = flc_decode_file("out.flc","partial.bin", unzip_gif="partial_unzip.gif", max_bands=4)
18
- #
19
- # Depends: numpy, pillow
20
- # ============================================================
21
-
22
- import os, struct, math, argparse
23
  import numpy as np
24
- from PIL import Image, ImageDraw
25
-
26
- PHI = (1.0 + 5.0**0.5) / 2.0
27
- MAGIC = b"FLC1\x00\x00\x00\x00" # 8 bytes
28
- VER_V2 = 2 # float64 params
29
-
30
- # --------------------------
31
- # Fibonacci utilities
32
- # --------------------------
33
- def fibonacci_sequence(n):
34
- fibs = [1, 2]
35
- while len(fibs) < n:
36
- fibs.append(fibs[-1] + fibs[-2])
37
- return np.array(fibs[:n], dtype=np.int64)
38
-
39
- def fibonacci_sequence_std(n):
40
- fibs = [1, 1]
41
- while len(fibs) < n:
42
- fibs.append(fibs[-1] + fibs[-2])
43
- return np.array(fibs[:n], dtype=np.int64)
44
-
45
- def fibonacci_frequency_boundaries(n_coeffs: int, n_bands: int):
46
- """
47
- Strict: returns exactly n_bands+1 boundaries => exactly n_bands intervals.
48
- Deterministic repair enforces strict monotone boundaries.
49
- """
50
- if n_bands < 2:
51
- return [0, n_coeffs]
52
-
53
- fibs = fibonacci_sequence(n_bands).astype(np.float64)
54
- w = fibs / (fibs.sum() + 1e-12)
55
- cum = np.cumsum(w)
56
-
57
- b = [0]
58
- for i in range(n_bands - 1):
59
- bi = int(round(n_coeffs * cum[i]))
60
- b.append(bi)
61
- b.append(n_coeffs)
62
-
63
- b = [max(0, min(n_coeffs, int(x))) for x in b]
64
- b[0] = 0
65
- b[-1] = n_coeffs
66
-
67
- # strictify forward
68
- for i in range(1, len(b) - 1):
69
- if b[i] <= b[i-1]:
70
- b[i] = b[i-1] + 1
71
- # strictify backward
72
- for i in range(len(b) - 2, 0, -1):
73
- if b[i] >= b[i+1]:
74
- b[i] = b[i+1] - 1
75
-
76
- # remove invalid interior points
77
- b = [0] + [x for x in b[1:-1] if 0 < x < n_coeffs] + [n_coeffs]
78
-
79
- # repack if we lost points (n_coeffs small or rounding)
80
- while len(b) < n_bands + 1:
81
- gaps = [(b[i+1] - b[i], i) for i in range(len(b) - 1)]
82
- gaps.sort(reverse=True)
83
- _, gi = gaps[0]
84
- mid = (b[gi] + b[gi+1]) // 2
85
- if mid == b[gi] or mid == b[gi+1]:
86
- break
87
- b.insert(gi+1, mid)
88
-
89
- # trim if we have too many
90
- while len(b) > n_bands + 1:
91
- gaps = [(b[i+1] - b[i], i) for i in range(len(b) - 1)]
92
- gaps.sort() # smallest first
93
- _, gi = gaps[0]
94
- if 0 < gi+1 < len(b)-1:
95
- b.pop(gi+1)
96
- else:
97
- break
98
-
99
- return b
100
-
101
- # --------------------------
102
- # Orthonormal DCT-II / IDCT (no scipy)
103
- # --------------------------
104
- def dct_ortho_1d(x: np.ndarray) -> np.ndarray:
105
- x = np.asarray(x, dtype=np.float64)
106
- N = x.shape[0]
107
- v = np.concatenate([x, x[::-1]])
108
- V = np.fft.fft(v)
109
- k = np.arange(N)
110
- X = np.real(V[:N] * np.exp(-1j * np.pi * k / (2 * N)))
111
- X *= 2.0
112
- X[0] *= (1.0 / math.sqrt(4 * N))
113
- X[1:] *= (1.0 / math.sqrt(2 * N))
114
- return X
115
-
116
- def idct_ortho_1d(X: np.ndarray) -> np.ndarray:
117
- X = np.asarray(X, dtype=np.float64)
118
- N = X.shape[0]
119
- x = X.copy()
120
-
121
- x0 = x[0] * math.sqrt(4 * N)
122
- xr = x[1:] * math.sqrt(2 * N)
123
- c = np.empty(N, dtype=np.complex128)
124
- c[0] = x0 / 2.0
125
- c[1:] = xr / 2.0
126
-
127
- k = np.arange(N)
128
- c = c * np.exp(1j * np.pi * k / (2 * N))
129
-
130
- V = np.zeros(2 * N, dtype=np.complex128)
131
- V[:N] = c
132
- V[N+1:] = np.conj(c[1:][::-1])
133
- v = np.fft.ifft(V).real
134
- return v[:N]
135
-
136
- def dct_blocks_ortho(x_blocks: np.ndarray) -> np.ndarray:
137
- B, L = x_blocks.shape
138
- out = np.empty((B, L), dtype=np.float64)
139
- for i in range(B):
140
- out[i] = dct_ortho_1d(x_blocks[i])
141
- return out
142
-
143
- def idct_blocks_ortho(X_blocks: np.ndarray) -> np.ndarray:
144
- B, L = X_blocks.shape
145
- out = np.empty((B, L), dtype=np.float64)
146
- for i in range(B):
147
- out[i] = idct_ortho_1d(X_blocks[i])
148
- return out
149
-
150
- # --------------------------
151
- # Bit IO
152
- # --------------------------
153
- class BitWriter:
154
- def __init__(self):
155
- self.buf = bytearray()
156
- self.acc = 0
157
- self.nbits = 0
158
-
159
- def write_bit(self, b: int):
160
- self.acc = (self.acc << 1) | (b & 1)
161
- self.nbits += 1
162
- if self.nbits == 8:
163
- self.buf.append(self.acc & 0xFF)
164
- self.acc = 0
165
- self.nbits = 0
166
-
167
- def finish(self) -> bytes:
168
- if self.nbits:
169
- self.acc <<= (8 - self.nbits)
170
- self.buf.append(self.acc & 0xFF)
171
- self.acc = 0
172
- self.nbits = 0
173
- return bytes(self.buf)
174
-
175
- class BitReader:
176
- def __init__(self, data: bytes):
177
- self.data = data
178
- self.i = 0
179
- self.acc = 0
180
- self.nbits = 0
181
-
182
- def read_bit(self) -> int:
183
- if self.nbits == 0:
184
- if self.i >= len(self.data):
185
- raise EOFError("BitReader EOF")
186
- self.acc = self.data[self.i]
187
- self.i += 1
188
- self.nbits = 8
189
- b = (self.acc >> (self.nbits - 1)) & 1
190
- self.nbits -= 1
191
- return b
192
-
193
- # --------------------------
194
- # Fibonacci coding
195
- # --------------------------
196
- def _fib_numbers_upto(n: int):
197
- fibs = [1, 2]
198
- while fibs[-1] <= n:
199
- fibs.append(fibs[-1] + fibs[-2])
200
- return fibs
201
-
202
- def fib_encode_nonneg(bw: BitWriter, n: int):
203
- m = int(n) + 1
204
- fibs = _fib_numbers_upto(m)
205
- bits = [0] * (len(fibs) - 1)
206
- rem = m
207
- for i in reversed(range(len(bits))):
208
- f = fibs[i]
209
- if f <= rem:
210
- bits[i] = 1
211
- rem -= f
212
- hi = max((i for i, b in enumerate(bits) if b), default=0)
213
- for i in range(hi + 1):
214
- bw.write_bit(bits[i])
215
- bw.write_bit(1)
216
-
217
- def fib_decode_nonneg(br: BitReader) -> int:
218
- fibs = [1, 2]
219
- bits = []
220
- prev = 0
221
- while True:
222
- b = br.read_bit()
223
- bits.append(b)
224
- if prev == 1 and b == 1:
225
- break
226
- prev = b
227
- if len(bits) > len(fibs):
228
- fibs.append(fibs[-1] + fibs[-2])
229
- payload = bits[:-1]
230
- while len(fibs) < len(payload):
231
- fibs.append(fibs[-1] + fibs[-2])
232
- m = 0
233
- for i, bi in enumerate(payload):
234
- if bi:
235
- m += fibs[i]
236
- return m - 1
237
-
238
- def zigzag_encode_i64(x: int) -> int:
239
- x = int(x)
240
- return (x << 1) ^ (x >> 63)
241
-
242
- def zigzag_decode_i64(u: int) -> int:
243
- u = int(u)
244
- return (u >> 1) ^ (-(u & 1))
245
-
246
- def rle_fib_encode_ints(ints: np.ndarray) -> bytes:
247
- bw = BitWriter()
248
- ints = ints.astype(np.int64, copy=False)
249
- zrun = 0
250
- for v in ints:
251
- if v == 0:
252
- zrun += 1
253
- continue
254
- if zrun:
255
- bw.write_bit(0)
256
- fib_encode_nonneg(bw, zrun)
257
- zrun = 0
258
- bw.write_bit(1)
259
- fib_encode_nonneg(bw, zigzag_encode_i64(int(v)))
260
- if zrun:
261
- bw.write_bit(0)
262
- fib_encode_nonneg(bw, zrun)
263
- return bw.finish()
264
-
265
- def rle_fib_decode_ints(payload: bytes, n_out: int) -> np.ndarray:
266
- br = BitReader(payload)
267
- out = np.zeros(n_out, dtype=np.int64)
268
- i = 0
269
- while i < n_out:
270
- tag = br.read_bit()
271
- if tag == 0:
272
- k = fib_decode_nonneg(br)
273
- i = min(n_out, i + k)
274
- else:
275
- u = fib_decode_nonneg(br)
276
- out[i] = zigzag_decode_i64(u)
277
- i += 1
278
- return out
279
-
280
- def delta1d(x: np.ndarray) -> np.ndarray:
281
- x = x.astype(np.int64, copy=False)
282
- out = np.empty_like(x, dtype=np.int64)
283
- prev = 0
284
- for i, v in enumerate(x):
285
- out[i] = int(v) - prev
286
- prev = int(v)
287
- return out
288
-
289
- def inv_delta1d(d: np.ndarray) -> np.ndarray:
290
- d = d.astype(np.int64, copy=False)
291
- out = np.empty_like(d, dtype=np.int64)
292
- acc = 0
293
- for i, v in enumerate(d):
294
- acc += int(v)
295
- out[i] = acc
296
- return out
297
-
298
- # --------------------------
299
- # φ-scaled band quantization
300
- # --------------------------
301
- def band_quantize_dct(coeffs: np.ndarray, boundaries, base_step: float, phi: float = PHI):
302
- B, L = coeffs.shape
303
- q = np.zeros((B, L), dtype=np.int32)
304
- for bi in range(len(boundaries) - 1):
305
- a, b = boundaries[bi], boundaries[bi + 1]
306
- if a >= b:
307
- continue
308
- step = base_step * (phi ** bi)
309
- q[:, a:b] = np.round(coeffs[:, a:b] / step).astype(np.int32)
310
- return q
311
-
312
- def band_dequantize_dct(q: np.ndarray, boundaries, base_step: float, phi: float = PHI):
313
- B, L = q.shape
314
- coeffs = np.zeros((B, L), dtype=np.float64)
315
- for bi in range(len(boundaries) - 1):
316
- a, b = boundaries[bi], boundaries[bi + 1]
317
- if a >= b:
318
- continue
319
- step = base_step * (phi ** bi)
320
- coeffs[:, a:b] = q[:, a:b].astype(np.float64) * step
321
- return coeffs
322
-
323
- # --------------------------
324
- # Hologram (left panel) + horizon ring overlay
325
- # --------------------------
326
- def choose_fib_grid(n_symbols: int) -> int:
327
- N = int(math.ceil(math.sqrt(n_symbols)))
328
- fibs = [1, 2]
329
- while fibs[-1] < N:
330
- fibs.append(fibs[-1] + fibs[-2])
331
- return fibs[-1]
332
-
333
- def ints_to_constellation(zints: np.ndarray):
334
- zints = zints.astype(np.int64)
335
- v = np.tanh(zints / 32.0).astype(np.float64)
336
- k = np.arange(v.size, dtype=np.float64)
337
- golden = 2 * math.pi / (PHI**2)
338
- theta = golden * k + 2.0 * math.pi * (v * 0.25)
339
- r = 1.0 + 0.35 * np.abs(v)
340
- return (r * np.cos(theta) + 1j * r * np.sin(theta)).astype(np.complex64)
341
-
342
- def hologram_spectrum_image(zints: np.ndarray, max_symbols: int = 262144):
343
- if zints.size > max_symbols:
344
- zints = zints[:max_symbols]
345
- syms = ints_to_constellation(zints)
346
- N = choose_fib_grid(syms.size)
347
- total = N * N
348
- if syms.size < total:
349
- syms = np.pad(syms, (0, total - syms.size), constant_values=(1+0j)).astype(np.complex64)
350
- U = syms.reshape(N, N)
351
- F = np.fft.fftshift(np.fft.fft2(U))
352
- mag = np.log1p(np.abs(F)).astype(np.float32)
353
- mag -= mag.min()
354
- mag /= (mag.max() + 1e-12)
355
- return (mag * 255).astype(np.uint8)
356
-
357
- def make_hologram_preview(zints: np.ndarray, out_png: str, max_symbols: int = 262144):
358
- img = hologram_spectrum_image(zints, max_symbols=max_symbols)
359
- Image.fromarray(img).save(out_png)
360
-
361
- def overlay_event_horizon_ring(imL: Image.Image, t: int, T: int):
362
- """
363
- Draw a glowing event-horizon ring (as 3 concentric ellipses) on the hologram.
364
- Radius expands with progress t/T.
365
- """
366
- W, H = imL.size
367
- cx, cy = W // 2, H // 2
368
- # start small, expand
369
- r0 = int(0.08 * min(W, H))
370
- r1 = int((0.45 * min(W, H)) * (t / max(1, T)) + r0)
371
-
372
- rgb = imL.convert("RGB")
373
- d = ImageDraw.Draw(rgb)
374
-
375
- # glow: 3 rings
376
- for k, alpha in [(0, 255), (3, 170), (6, 90)]:
377
- rr = r1 + k
378
- bbox = (cx - rr, cy - rr, cx + rr, cy + rr)
379
- # gold-ish ring
380
- col = (255, 215, 0) if alpha == 255 else (220, 180, 0)
381
- d.ellipse(bbox, outline=col, width=2)
382
-
383
- # center dot (singularity hint)
384
- d.ellipse((cx-2, cy-2, cx+2, cy+2), fill=(255, 215, 0))
385
- return rgb
386
-
387
- # --------------------------
388
- # Fibonacci spiral unfold (right panel) + tile outlines
389
- # --------------------------
390
- def fibonacci_spiral_tiles_for_area(n_pixels: int, max_tiles: int = 30):
391
- fibs = fibonacci_sequence_std(max_tiles) # 1,1,2,3,5...
392
- sizes = []
393
- area = 0
394
- for s in fibs:
395
- sizes.append(int(s))
396
- area += int(s) * int(s)
397
- if area >= n_pixels:
398
- break
399
- return sizes
400
-
401
- def fibonacci_spiral_tile_positions(sizes):
402
- tiles = []
403
- s0 = sizes[0]
404
- minx, miny, maxx, maxy = 0, 0, s0, s0
405
- tiles.append((0, 0, s0))
406
-
407
- for i in range(1, len(sizes)):
408
- s = sizes[i]
409
- d = (i - 1) % 4 # right, up, left, down
410
- if d == 0: # right
411
- x, y = maxx, miny
412
- elif d == 1: # up
413
- x, y = maxx - s, maxy
414
- elif d == 2: # left
415
- x, y = minx - s, maxy - s
416
- else: # down
417
- x, y = minx, miny - s
418
-
419
- tiles.append((x, y, s))
420
- minx = min(minx, x)
421
- miny = min(miny, y)
422
- maxx = max(maxx, x + s)
423
- maxy = max(maxy, y + s)
424
-
425
- bbox = (minx, miny, maxx, maxy)
426
- return tiles, bbox
427
-
428
- def bytes_to_fib_spiral_image(data: bytes, max_pixels: int = 262144, want_tiles=False):
429
- arr = np.frombuffer(data, dtype=np.uint8)
430
- if arr.size > max_pixels:
431
- arr = arr[:max_pixels]
432
- n = int(arr.size)
433
- if n == 0:
434
- if want_tiles:
435
- return np.zeros((1, 1), dtype=np.uint8), [(0,0,1)], (0,0,1,1)
436
- return np.zeros((1, 1), dtype=np.uint8)
437
-
438
- sizes = fibonacci_spiral_tiles_for_area(n_pixels=n, max_tiles=32)
439
- tiles, (minx, miny, maxx, maxy) = fibonacci_spiral_tile_positions(sizes)
440
-
441
- W = int(maxx - minx)
442
- H = int(maxy - miny)
443
- img = np.zeros((H, W), dtype=np.uint8)
444
- idx = 0
445
-
446
- for (x, y, s) in tiles:
447
- if idx >= n:
448
- break
449
- x0 = int(x - minx)
450
- y0_up = int(y - miny)
451
- y0 = int((H - (y0_up + s)))
452
-
453
- take = min(s * s, n - idx)
454
- block = arr[idx:idx + take]
455
- if take < s * s:
456
- block = np.pad(block, (0, s * s - take), constant_values=0)
457
- block2d = block.reshape(s, s)
458
- img[y0:y0+s, x0:x0+s] = block2d
459
- idx += take
460
-
461
- if want_tiles:
462
- return img, tiles, (minx, miny, maxx, maxy)
463
- return img
464
-
465
- def overlay_tile_outlines(imL: Image.Image, tiles, bbox_info, outline=(120,120,120)):
466
- """
467
- Draw square outlines for the Fibonacci tiling on the right panel.
468
- `tiles` are in y-up coords with original bbox min offsets.
469
- """
470
- rgb = imL.convert("RGB")
471
- d = ImageDraw.Draw(rgb)
472
- # rebuild H,W from image
473
- W, H = rgb.size
474
-
475
- # bbox is y-up coords: minx,miny,maxx,maxy
476
- minx, miny, maxx, maxy = bbox_info
477
- # For each tile, map y-up to y-down in image coords
478
- for (x, y, s) in tiles:
479
- x0 = int(x - minx)
480
- y0_up = int(y - miny)
481
- y0 = int((H - (y0_up + s)))
482
- d.rectangle((x0, y0, x0 + s - 1, y0 + s - 1), outline=outline, width=1)
483
- return rgb
484
-
485
- # --------------------------
486
- # GIF assembly
487
- # --------------------------
488
- def save_gif(frames, out_gif: str, duration=90):
489
- if not frames:
490
- return
491
- frames[0].save(out_gif, save_all=True, append_images=frames[1:], duration=duration, loop=0, optimize=True)
492
-
493
- def make_unzip_gif_from_Q(Q_full: np.ndarray,
494
- boundaries,
495
- base_step: float,
496
- mu: float,
497
- n_bytes: int,
498
- out_gif: str,
499
- max_bands: int = None,
500
- gif_scale: int = 2,
501
- max_symbols: int = 262144,
502
- max_pixels: int = 262144,
503
- draw_outlines: bool = True,
504
- draw_horizon: bool = True):
505
- n_total_bands = len(boundaries) - 1
506
- T = n_total_bands if max_bands is None else max(0, min(int(max_bands), n_total_bands))
507
- if T == 0:
508
- T = 1
509
-
510
- frames = []
511
- band_slices = [(boundaries[i], boundaries[i+1]) for i in range(n_total_bands)]
512
-
513
- for t in range(1, T + 1):
514
- Q_part = np.zeros_like(Q_full)
515
- for bi in range(t):
516
- a, b = band_slices[bi]
517
- if a < b:
518
- Q_part[:, a:b] = Q_full[:, a:b]
519
-
520
- # partial recon bytes
521
- C_part = band_dequantize_dct(Q_part, boundaries, base_step=base_step, phi=PHI)
522
- X_part = idct_blocks_ortho(C_part)
523
- x = X_part.reshape(-1)[:n_bytes]
524
- x = (x * 127.5) + float(mu)
525
- x = np.clip(np.round(x), 0, 255).astype(np.uint8).tobytes()
526
-
527
- # left hologram
528
- qflat = Q_part.reshape(-1).astype(np.int64)
529
- holo_u8 = hologram_spectrum_image(qflat, max_symbols=max_symbols)
530
- A = Image.fromarray(holo_u8).convert("L")
531
- if gif_scale != 1:
532
- A = A.resize((A.size[0]*gif_scale, A.size[1]*gif_scale), Image.NEAREST)
533
-
534
- # horizon ring overlay
535
- if draw_horizon:
536
- A_rgb = overlay_event_horizon_ring(A, t=t, T=T)
537
- else:
538
- A_rgb = A.convert("RGB")
539
-
540
- # right spiral image
541
- field_u8, tiles, bbox_info = bytes_to_fib_spiral_image(x, max_pixels=max_pixels, want_tiles=True)
542
- Bimg = Image.fromarray(field_u8).convert("L")
543
- if gif_scale != 1:
544
- Bimg = Bimg.resize((Bimg.size[0]*gif_scale, Bimg.size[1]*gif_scale), Image.NEAREST)
545
-
546
- if draw_outlines:
547
- # scale tile coordinates too (by rendering outlines after scaling)
548
- # easiest: draw outlines on scaled image using scaled bbox & tiles
549
- # so adjust bbox and tiles by gif_scale
550
- minx, miny, maxx, maxy = bbox_info
551
- tiles_s = [(x*gif_scale, y*gif_scale, s*gif_scale) for (x,y,s) in tiles]
552
- bbox_s = (minx*gif_scale, miny*gif_scale, maxx*gif_scale, maxy*gif_scale)
553
- B_rgb = overlay_tile_outlines(Bimg, tiles_s, bbox_s, outline=(120,120,120))
554
- else:
555
- B_rgb = Bimg.convert("RGB")
556
-
557
- # match heights by padding
558
- H = max(A_rgb.size[1], B_rgb.size[1])
559
- def pad_to_h_rgb(im, H):
560
- if im.size[1] == H:
561
- return im
562
- canvas = Image.new("RGB", (im.size[0], H), (0,0,0))
563
- canvas.paste(im, (0, (H - im.size[1])//2))
564
- return canvas
565
- A_rgb = pad_to_h_rgb(A_rgb, H)
566
- B_rgb = pad_to_h_rgb(B_rgb, H)
567
-
568
- W = A_rgb.size[0]
569
- canvas = Image.new("RGB", (W*2, H + 44), (0, 0, 0))
570
- canvas.paste(A_rgb, (0, 44))
571
- canvas.paste(B_rgb, (W, 44))
572
-
573
- draw = ImageDraw.Draw(canvas)
574
- draw.text((12, 10), f"FLC HOLOGRAPHIC UNZIP | bands {t}/{T} (horizon={T})", fill=(255, 215, 0))
575
- draw.text((12, 26), "LEFT: hologram + event horizon | RIGHT: Fibonacci spiral + tile outlines", fill=(180, 180, 180))
576
- frames.append(canvas)
577
-
578
- save_gif(frames, out_gif, duration=90)
579
-
580
- # ============================================================
581
- # Encoder / Decoder
582
- # ============================================================
583
- def flc_encode_file(in_path: str,
584
- out_flc: str,
585
- preview_png: str = None,
586
- unzip_gif: str = None,
587
- block_len: int = 1024,
588
- n_bands: int = 10,
589
- base_step: float = 0.004,
590
- use_mean_center: bool = True,
591
- gif_scale: int = 2,
592
- gif_horizon: int = None,
593
- draw_horizon: bool = True,
594
- draw_outlines: bool = True):
595
- raw = open(in_path, "rb").read()
596
- n_bytes = len(raw)
597
-
598
- x = np.frombuffer(raw, dtype=np.uint8).astype(np.float64)
599
- mu = float(np.mean(x)) if use_mean_center else 127.5
600
- x = (x - mu) / 127.5
601
-
602
- pad = (-x.size) % block_len
603
- if pad:
604
- x = np.pad(x, (0, pad), constant_values=0.0)
605
- n_blocks = x.size // block_len
606
- X = x.reshape(n_blocks, block_len)
607
-
608
- C = dct_blocks_ortho(X)
609
- boundaries = fibonacci_frequency_boundaries(block_len, n_bands)
610
- Q = band_quantize_dct(C, boundaries, base_step=base_step, phi=PHI)
611
-
612
- q_flat = Q.reshape(-1).astype(np.int64)
613
- d = delta1d(q_flat)
614
- payload = rle_fib_encode_ints(d)
615
-
616
- header = struct.pack(
617
- "<8sH Q I I H d d H",
618
- MAGIC, VER_V2,
619
- int(n_bytes),
620
- int(block_len),
621
- int(n_blocks),
622
- int(n_bands),
623
- float(base_step),
624
- float(mu),
625
- int(len(boundaries)),
626
- )
627
- bnds = struct.pack("<" + "I"*len(boundaries), *[int(b) for b in boundaries])
628
- paylen = struct.pack("<I", int(len(payload)))
629
-
630
- with open(out_flc, "wb") as f:
631
- f.write(header)
632
- f.write(bnds)
633
- f.write(paylen)
634
- f.write(payload)
635
-
636
- if preview_png is not None:
637
- make_hologram_preview(q_flat, preview_png)
638
-
639
- if unzip_gif is not None:
640
- make_unzip_gif_from_Q(
641
- Q_full=Q,
642
- boundaries=boundaries,
643
- base_step=float(base_step),
644
- mu=float(mu),
645
- n_bytes=int(n_bytes),
646
- out_gif=unzip_gif,
647
- max_bands=gif_horizon,
648
- gif_scale=gif_scale,
649
- draw_horizon=draw_horizon,
650
- draw_outlines=draw_outlines,
651
- )
652
-
653
- return {
654
- "n_bytes": int(n_bytes),
655
- "block_len": int(block_len),
656
- "n_blocks": int(n_blocks),
657
- "n_bands": int(n_bands),
658
- "base_step": float(base_step),
659
- "mu": float(mu),
660
- "payload_len": int(len(payload)),
661
- "out_flc": out_flc,
662
- "preview_png": preview_png,
663
- "unzip_gif": unzip_gif,
664
- "ratio": (os.path.getsize(out_flc) / max(1, n_bytes)),
665
- }
666
-
667
- def flc_decode_file(in_flc: str,
668
- out_path: str,
669
- unzip_gif: str = None,
670
- max_bands: int = None,
671
- gif_scale: int = 2,
672
- draw_horizon: bool = True,
673
- draw_outlines: bool = True):
674
- blob = open(in_flc, "rb").read()
675
- off = 0
676
-
677
- (magic, ver, n_bytes, block_len, n_blocks, n_bands, base_step, mu, bnd_len) = struct.unpack_from(
678
- "<8sH Q I I H d d H", blob, off
679
- )
680
- off += struct.calcsize("<8sH Q I I H d d H")
681
- if magic != MAGIC:
682
- raise ValueError("Bad magic (not FLC1)")
683
- if ver != VER_V2:
684
- raise ValueError(f"Unsupported version {ver}")
685
-
686
- boundaries = list(struct.unpack_from("<" + "I"*bnd_len, blob, off))
687
- off += 4 * bnd_len
688
-
689
- (payload_len,) = struct.unpack_from("<I", blob, off)
690
- off += 4
691
- payload = blob[off:off+payload_len]
692
- off += payload_len
693
-
694
- n_coeffs = int(n_blocks) * int(block_len)
695
- d = rle_fib_decode_ints(payload, n_coeffs)
696
- q_flat = inv_delta1d(d).astype(np.int64)
697
- Q_full = q_flat.reshape(int(n_blocks), int(block_len)).astype(np.int32)
698
-
699
- # horizon
700
- n_total_bands = len(boundaries) - 1
701
- bands_to_apply = n_total_bands if max_bands is None else max(0, min(int(max_bands), n_total_bands))
702
-
703
- if bands_to_apply < n_total_bands:
704
- Q_use = np.zeros_like(Q_full)
705
- for bi in range(bands_to_apply):
706
- a, b = boundaries[bi], boundaries[bi+1]
707
- if a < b:
708
- Q_use[:, a:b] = Q_full[:, a:b]
709
- else:
710
- Q_use = Q_full
711
-
712
- C = band_dequantize_dct(Q_use, boundaries, base_step=float(base_step), phi=PHI)
713
- X = idct_blocks_ortho(C)
714
-
715
- x = X.reshape(-1)[:int(n_bytes)]
716
- x = (x * 127.5) + float(mu)
717
- x = np.clip(np.round(x), 0, 255).astype(np.uint8)
718
-
719
- with open(out_path, "wb") as f:
720
- f.write(x.tobytes())
721
-
722
- if unzip_gif is not None:
723
- make_unzip_gif_from_Q(
724
- Q_full=Q_full,
725
- boundaries=boundaries,
726
- base_step=float(base_step),
727
- mu=float(mu),
728
- n_bytes=int(n_bytes),
729
- out_gif=unzip_gif,
730
- max_bands=(bands_to_apply if bands_to_apply > 0 else 1),
731
- gif_scale=gif_scale,
732
- draw_horizon=draw_horizon,
733
- draw_outlines=draw_outlines,
734
- )
735
-
736
- return {
737
- "n_bytes": int(n_bytes),
738
- "block_len": int(block_len),
739
- "n_blocks": int(n_blocks),
740
- "n_bands": int(n_bands),
741
- "base_step": float(base_step),
742
- "mu": float(mu),
743
- "payload_len": int(payload_len),
744
- "out_path": out_path,
745
- "unzip_gif": unzip_gif,
746
- "bands_applied": int(bands_to_apply),
747
  }
748
-
749
- # --------------------------
750
- # Quality metric
751
- # --------------------------
752
- def cosine_similarity_bytes(a: bytes, b: bytes) -> float:
753
- x = np.frombuffer(a, dtype=np.uint8).astype(np.float64)
754
- y = np.frombuffer(b, dtype=np.uint8).astype(np.float64)
755
- n = min(x.size, y.size)
756
- x = x[:n]; y = y[:n]
757
- x -= x.mean(); y -= y.mean()
758
- nx = np.linalg.norm(x) + 1e-12
759
- ny = np.linalg.norm(y) + 1e-12
760
- return float(np.dot(x, y) / (nx * ny))
761
-
762
- # --------------------------
763
- # CLI wrapper (call explicitly)
764
- # --------------------------
765
- def flc_cli(argv=None):
766
- ap = argparse.ArgumentParser(description="FLC v1.3: Fibonacci-banded DCT + φ quant + Fibonacci coding + horizon ring + tile outlines")
767
- ap.add_argument("inp", type=str)
768
- ap.add_argument("out_flc", type=str)
769
- ap.add_argument("preview_png", type=str)
770
- ap.add_argument("unzip_gif", type=str)
771
- ap.add_argument("out_recovered", type=str)
772
- ap.add_argument("--block", type=int, default=1024)
773
- ap.add_argument("--bands", type=int, default=10)
774
- ap.add_argument("--step", type=float, default=0.004)
775
- ap.add_argument("--no_center", action="store_true")
776
- ap.add_argument("--horizon", type=int, default=None)
777
- ap.add_argument("--gif_scale", type=int, default=2)
778
- ap.add_argument("--no_ring", action="store_true")
779
- ap.add_argument("--no_outlines", action="store_true")
780
- args = ap.parse_args(argv)
781
-
782
- enc = flc_encode_file(
783
- args.inp, args.out_flc, args.preview_png, unzip_gif=args.unzip_gif,
784
- block_len=args.block, n_bands=args.bands, base_step=args.step,
785
- use_mean_center=(not args.no_center),
786
- gif_scale=args.gif_scale,
787
- gif_horizon=None,
788
- draw_horizon=(not args.no_ring),
789
- draw_outlines=(not args.no_outlines),
790
- )
791
- print("ENC:", enc)
792
-
793
- dec = flc_decode_file(
794
- args.out_flc, args.out_recovered,
795
- unzip_gif="decode_" + os.path.basename(args.unzip_gif),
796
- max_bands=args.horizon,
797
- gif_scale=args.gif_scale,
798
- draw_horizon=(not args.no_ring),
799
- draw_outlines=(not args.no_outlines),
800
- )
801
- print("DEC:", dec)
802
-
803
- a = open(args.inp, "rb").read()
804
- b = open(args.out_recovered, "rb").read()
805
- cs = cosine_similarity_bytes(a, b)
806
- print("Cosine similarity (bytes):", cs, " => ", cs*100, "%")
807
- print("ORIG:", os.path.getsize(args.inp), "FLC:", os.path.getsize(args.out_flc),
808
- "RATIO:", os.path.getsize(args.out_flc)/max(1, os.path.getsize(args.inp)))
809
-
810
- # ============================================================
811
- # Notebook demo (run manually)
812
- # ============================================================
813
- with open("test_input.bin","wb") as f:
814
- f.write(b"Black hole horizon unzip meets Fibonacci spiral. " * 220)
815
- enc = flc_encode_file("test_input.bin","test_output.flc","test_preview.png",
816
- unzip_gif="test_unzip.gif",
817
- block_len=1024, n_bands=10, base_step=0.004,
818
- gif_scale=2, draw_horizon=True, draw_outlines=True)
819
- print("ENC:", enc)
820
- dec = flc_decode_file("test_output.flc","test_recovered.bin",
821
- unzip_gif="test_decode_unzip.gif",
822
- max_bands=None, gif_scale=2,
823
- draw_horizon=True, draw_outlines=True)
824
- print("DEC:", dec)
825
- a = open("test_input.bin","rb").read()
826
- b = open("test_recovered.bin","rb").read()
827
- print("Cosine similarity:", cosine_similarity_bytes(a,b))
828
- decp = flc_decode_file("test_output.flc","test_partial.bin",
829
- unzip_gif="test_partial_unzip.gif",
830
- max_bands=4, gif_scale=2,
831
- draw_horizon
 
1
+ import streamlit as st
2
+ import os
3
+ import tempfile
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import numpy as np
5
+ from PIL import Image
6
+ from flc_core import flc_encode_file, flc_decode_file, cosine_similarity_bytes
7
+
8
+ st.set_page_config(page_title="FLC v1.3 | How it Works", layout="wide")
9
+
10
+ # Styling for a "Scientific Laboratory" look
11
+ st.markdown("""
12
+ <style>
13
+ .reportview-container { background: #0e1117; }
14
+ .main { color: #e0e0e0; }
15
+ h1, h2, h3 { color: #f1c40f !important; }
16
+ .stAlert { background-color: #1a1c24; border: 1px solid #f1c40f; }
17
+ </style>
18
+ """, unsafe_allow_html=True)
19
+
20
+ st.title("🌀 Fibonacci Lattice Compression (FLC)")
21
+ st.markdown("""
22
+ **FLC v1.3** is a bio-inspired data compression architecture. Unlike standard ZIP or JPEG formats,
23
+ FLC uses the **Golden Ratio ($\Phi$)** to decide which parts of your data are "essential" and which are "noise."
24
+ """)
25
+
26
+ # --- PILLAR 1: THE EXPLAINER ---
27
+ with st.expander("📖 Step-by-Step: How does the 'Secret Sauce' work?"):
28
+ col1, col2, col3 = st.columns(3)
29
+
30
+ with col1:
31
+ st.markdown("### 1. Spectral Projection")
32
+ st.write("""
33
+ We treat your data like a sound wave. Using a **DCT (Discrete Cosine Transform)**,
34
+ we project the bits into frequency space.
35
+ * **Low Frequencies:** The "skeleton" of your data.
36
+ * **High Frequencies:** The "dust" and fine details.
37
+ """)
38
+
39
+ with col2:
40
+ st.markdown("### 2. The Golden Filter")
41
+ st.write("""
42
+ Instead of treating all frequencies equally, FLC uses **Fibonacci Bands**.
43
+ We compress the 'dust' using steps based on the **Golden Ratio ($\Phi \approx 1.618$)**.
44
+ As the frequency increases, the compression gets exponentially more aggressive.
45
+ """)
46
+
47
+ with col3:
48
+ with st.container():
49
+ st.markdown("### 3. Fibonacci Coding")
50
+ st.write("""
51
+ Standard computers use 8-bit bytes. FLC uses **Fibonacci Binary**.
52
+ It's a "universal code" that uses the sum of Fibonacci numbers to represent values,
53
+ making the compressed stream incredibly resilient and dense.
54
+ """)
55
+
56
+ st.divider()
57
+
58
+ # --- PILLAR 2: THE INTERACTIVE DEMO ---
59
+ st.header("🧪 Test the Horizon")
60
+ with st.sidebar:
61
+ st.header("🎛️ Architecture Params")
62
+ st.info("Adjusting these changes how the 'Secret Sauce' math is applied.")
63
+
64
+ quality_map = {
65
+ "High Compression (Lossy)": {"bands": 6, "step": 0.08, "desc": "Aggressive $\Phi$-scaling."},
66
+ "Balanced": {"bands": 12, "step": 0.005, "desc": "The Golden Mean of fidelity."},
67
+ "Near-Lossless": {"bands": 24, "step": 0.0001, "desc": "Full spectral recovery."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
+
70
+ tier = st.radio("Fidelity Tier", list(quality_map.keys()), index=1)
71
+ st.caption(quality_map[tier]["desc"])
72
+
73
+ st.subheader("Visual Overlays")
74
+ show_spiral = st.checkbox("Fibonacci Spiral Outlines", value=True)
75
+ show_ring = st.checkbox("Event Horizon Ring", value=True)
76
+
77
+ uploaded_file = st.file_uploader("Upload a file (Image, Text, or Binary)", type=["bin", "png", "jpg", "txt"])
78
+
79
+ if uploaded_file is not None:
80
+ with tempfile.TemporaryDirectory() as tmpdir:
81
+ in_path = os.path.join(tmpdir, "input.bin")
82
+ out_flc = os.path.join(tmpdir, "output.flc")
83
+ out_gif = os.path.join(tmpdir, "unzip.gif")
84
+ recovered_path = os.path.join(tmpdir, "recovered.bin")
85
+
86
+ with open(in_path, "wb") as f:
87
+ f.write(uploaded_file.getbuffer())
88
+
89
+ if st.button("RUN HOLOGRAPHIC RECONSTRUCTION"):
90
+ with st.status("Initializing Fibonacci Manifolds...", expanded=True) as status:
91
+ st.write("Transforming data to Frequency Space...")
92
+ enc = flc_encode_file(
93
+ in_path, out_flc, unzip_gif=out_gif,
94
+ n_bands=quality_map[tier]["bands"],
95
+ base_step=quality_map[tier]["step"]
96
+ )
97
+
98
+ st.write("Applying $\Phi$-scaled quantization...")
99
+ dec = flc_decode_file(out_flc, recovered_path)
100
+
101
+ st.write("Generating Holographic Unzip visualization...")
102
+ status.update(label="Reconstruction Complete!", state="complete", expanded=False)
103
+
104
+ # Results Section
105
+ st.subheader("📊 Compression Performance")
106
+ c1, c2, c3, c4 = st.columns(4)
107
+ c1.metric("Original Size", f"{enc['n_bytes']} B")
108
+ c2.metric("Compressed Size", f"{enc['payload_len']} B")
109
+ c3.metric("Ratio", f"{enc['ratio']:.2%}")
110
+
111
+ # Calculate Similarity
112
+ orig_data = open(in_path, "rb").read()
113
+ reco_data = open(recovered_path, "rb").read()
114
+ fidelity = cosine_similarity_bytes(orig_data, reco_data)
115
+ c4.metric("Data Fidelity", f"{fidelity*100:.2f}%")
116
+
117
+ st.divider()
118
+
119
+ # Visualization
120
+ st.header("🎞️ The Unzip Sequence")
121
+ st.markdown("""
122
+ This animation shows the **Progressive Reconstruction**.
123
+ The 'Hologram' on the left shows the frequency data being added band-by-band.
124
+ The 'Spiral' on the right shows the bits filling the Fibonacci tiles in real-time.
125
+ """)
126
+
127
+ if os.path.exists(out_gif):
128
+ st.image(out_gif, use_container_width=True)
129
+
130
+ st.info("💡 Notice how the general shape appears first, and the fine details (noise) appear last. This is the hallmark of Spectral Compression.")
131
+
132
+ with open(out_flc, "rb") as f:
133
+ st.download_button("📥 Download Encoded .FLC File", f, file_name="demo.flc")
134
+
135
+ else:
136
+ st.warning("Please upload a file to visualize the Fibonacci transformation.")