| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import os, struct, math, argparse |
| import numpy as np |
| from PIL import Image, ImageDraw |
|
|
| PHI = (1.0 + 5.0**0.5) / 2.0 |
| MAGIC = b"FLC1\x00\x00\x00\x00" |
| VER_V2 = 2 |
|
|
| |
| |
| |
| def fibonacci_sequence(n): |
| fibs = [1, 2] |
| while len(fibs) < n: |
| fibs.append(fibs[-1] + fibs[-2]) |
| return np.array(fibs[:n], dtype=np.int64) |
|
|
| def fibonacci_sequence_std(n): |
| fibs = [1, 1] |
| while len(fibs) < n: |
| fibs.append(fibs[-1] + fibs[-2]) |
| return np.array(fibs[:n], dtype=np.int64) |
|
|
| def fibonacci_frequency_boundaries(n_coeffs: int, n_bands: int): |
| """ |
| Strict: returns exactly n_bands+1 boundaries => exactly n_bands intervals. |
| Deterministic repair enforces strict monotone boundaries. |
| """ |
| if n_bands < 2: |
| return [0, n_coeffs] |
|
|
| fibs = fibonacci_sequence(n_bands).astype(np.float64) |
| w = fibs / (fibs.sum() + 1e-12) |
| cum = np.cumsum(w) |
|
|
| b = [0] |
| for i in range(n_bands - 1): |
| bi = int(round(n_coeffs * cum[i])) |
| b.append(bi) |
| b.append(n_coeffs) |
|
|
| b = [max(0, min(n_coeffs, int(x))) for x in b] |
| b[0] = 0 |
| b[-1] = n_coeffs |
|
|
| |
| for i in range(1, len(b) - 1): |
| if b[i] <= b[i-1]: |
| b[i] = b[i-1] + 1 |
| |
| for i in range(len(b) - 2, 0, -1): |
| if b[i] >= b[i+1]: |
| b[i] = b[i+1] - 1 |
|
|
| |
| b = [0] + [x for x in b[1:-1] if 0 < x < n_coeffs] + [n_coeffs] |
|
|
| |
| while len(b) < n_bands + 1: |
| gaps = [(b[i+1] - b[i], i) for i in range(len(b) - 1)] |
| gaps.sort(reverse=True) |
| _, gi = gaps[0] |
| mid = (b[gi] + b[gi+1]) // 2 |
| if mid == b[gi] or mid == b[gi+1]: |
| break |
| b.insert(gi+1, mid) |
|
|
| |
| while len(b) > n_bands + 1: |
| gaps = [(b[i+1] - b[i], i) for i in range(len(b) - 1)] |
| gaps.sort() |
| _, gi = gaps[0] |
| if 0 < gi+1 < len(b)-1: |
| b.pop(gi+1) |
| else: |
| break |
|
|
| return b |
|
|
| |
| |
| |
| def dct_ortho_1d(x: np.ndarray) -> np.ndarray: |
| x = np.asarray(x, dtype=np.float64) |
| N = x.shape[0] |
| v = np.concatenate([x, x[::-1]]) |
| V = np.fft.fft(v) |
| k = np.arange(N) |
| X = np.real(V[:N] * np.exp(-1j * np.pi * k / (2 * N))) |
| X *= 2.0 |
| X[0] *= (1.0 / math.sqrt(4 * N)) |
| X[1:] *= (1.0 / math.sqrt(2 * N)) |
| return X |
|
|
| def idct_ortho_1d(X: np.ndarray) -> np.ndarray: |
| X = np.asarray(X, dtype=np.float64) |
| N = X.shape[0] |
| x = X.copy() |
|
|
| x0 = x[0] * math.sqrt(4 * N) |
| xr = x[1:] * math.sqrt(2 * N) |
| c = np.empty(N, dtype=np.complex128) |
| c[0] = x0 / 2.0 |
| c[1:] = xr / 2.0 |
|
|
| k = np.arange(N) |
| c = c * np.exp(1j * np.pi * k / (2 * N)) |
|
|
| V = np.zeros(2 * N, dtype=np.complex128) |
| V[:N] = c |
| V[N+1:] = np.conj(c[1:][::-1]) |
| v = np.fft.ifft(V).real |
| return v[:N] |
|
|
| def dct_blocks_ortho(x_blocks: np.ndarray) -> np.ndarray: |
| B, L = x_blocks.shape |
| out = np.empty((B, L), dtype=np.float64) |
| for i in range(B): |
| out[i] = dct_ortho_1d(x_blocks[i]) |
| return out |
|
|
| def idct_blocks_ortho(X_blocks: np.ndarray) -> np.ndarray: |
| B, L = X_blocks.shape |
| out = np.empty((B, L), dtype=np.float64) |
| for i in range(B): |
| out[i] = idct_ortho_1d(X_blocks[i]) |
| return out |
|
|
| |
| |
| |
| class BitWriter: |
| def __init__(self): |
| self.buf = bytearray() |
| self.acc = 0 |
| self.nbits = 0 |
|
|
| def write_bit(self, b: int): |
| self.acc = (self.acc << 1) | (b & 1) |
| self.nbits += 1 |
| if self.nbits == 8: |
| self.buf.append(self.acc & 0xFF) |
| self.acc = 0 |
| self.nbits = 0 |
|
|
| def finish(self) -> bytes: |
| if self.nbits: |
| self.acc <<= (8 - self.nbits) |
| self.buf.append(self.acc & 0xFF) |
| self.acc = 0 |
| self.nbits = 0 |
| return bytes(self.buf) |
|
|
| class BitReader: |
| def __init__(self, data: bytes): |
| self.data = data |
| self.i = 0 |
| self.acc = 0 |
| self.nbits = 0 |
|
|
| def read_bit(self) -> int: |
| if self.nbits == 0: |
| if self.i >= len(self.data): |
| raise EOFError("BitReader EOF") |
| self.acc = self.data[self.i] |
| self.i += 1 |
| self.nbits = 8 |
| b = (self.acc >> (self.nbits - 1)) & 1 |
| self.nbits -= 1 |
| return b |
|
|
| |
| |
| |
| def _fib_numbers_upto(n: int): |
| fibs = [1, 2] |
| while fibs[-1] <= n: |
| fibs.append(fibs[-1] + fibs[-2]) |
| return fibs |
|
|
| def fib_encode_nonneg(bw: BitWriter, n: int): |
| m = int(n) + 1 |
| fibs = _fib_numbers_upto(m) |
| bits = [0] * (len(fibs) - 1) |
| rem = m |
| for i in reversed(range(len(bits))): |
| f = fibs[i] |
| if f <= rem: |
| bits[i] = 1 |
| rem -= f |
| hi = max((i for i, b in enumerate(bits) if b), default=0) |
| for i in range(hi + 1): |
| bw.write_bit(bits[i]) |
| bw.write_bit(1) |
|
|
| def fib_decode_nonneg(br: BitReader) -> int: |
| fibs = [1, 2] |
| bits = [] |
| prev = 0 |
| while True: |
| b = br.read_bit() |
| bits.append(b) |
| if prev == 1 and b == 1: |
| break |
| prev = b |
| if len(bits) > len(fibs): |
| fibs.append(fibs[-1] + fibs[-2]) |
| payload = bits[:-1] |
| while len(fibs) < len(payload): |
| fibs.append(fibs[-1] + fibs[-2]) |
| m = 0 |
| for i, bi in enumerate(payload): |
| if bi: |
| m += fibs[i] |
| return m - 1 |
|
|
| def zigzag_encode_i64(x: int) -> int: |
| x = int(x) |
| return (x << 1) ^ (x >> 63) |
|
|
| def zigzag_decode_i64(u: int) -> int: |
| u = int(u) |
| return (u >> 1) ^ (-(u & 1)) |
|
|
| def rle_fib_encode_ints(ints: np.ndarray) -> bytes: |
| bw = BitWriter() |
| ints = ints.astype(np.int64, copy=False) |
| zrun = 0 |
| for v in ints: |
| if v == 0: |
| zrun += 1 |
| continue |
| if zrun: |
| bw.write_bit(0) |
| fib_encode_nonneg(bw, zrun) |
| zrun = 0 |
| bw.write_bit(1) |
| fib_encode_nonneg(bw, zigzag_encode_i64(int(v))) |
| if zrun: |
| bw.write_bit(0) |
| fib_encode_nonneg(bw, zrun) |
| return bw.finish() |
|
|
| def rle_fib_decode_ints(payload: bytes, n_out: int) -> np.ndarray: |
| br = BitReader(payload) |
| out = np.zeros(n_out, dtype=np.int64) |
| i = 0 |
| while i < n_out: |
| tag = br.read_bit() |
| if tag == 0: |
| k = fib_decode_nonneg(br) |
| i = min(n_out, i + k) |
| else: |
| u = fib_decode_nonneg(br) |
| out[i] = zigzag_decode_i64(u) |
| i += 1 |
| return out |
|
|
| def delta1d(x: np.ndarray) -> np.ndarray: |
| x = x.astype(np.int64, copy=False) |
| out = np.empty_like(x, dtype=np.int64) |
| prev = 0 |
| for i, v in enumerate(x): |
| out[i] = int(v) - prev |
| prev = int(v) |
| return out |
|
|
| def inv_delta1d(d: np.ndarray) -> np.ndarray: |
| d = d.astype(np.int64, copy=False) |
| out = np.empty_like(d, dtype=np.int64) |
| acc = 0 |
| for i, v in enumerate(d): |
| acc += int(v) |
| out[i] = acc |
| return out |
|
|
| |
| |
| |
| def band_quantize_dct(coeffs: np.ndarray, boundaries, base_step: float, phi: float = PHI): |
| B, L = coeffs.shape |
| q = np.zeros((B, L), dtype=np.int32) |
| for bi in range(len(boundaries) - 1): |
| a, b = boundaries[bi], boundaries[bi + 1] |
| if a >= b: |
| continue |
| step = base_step * (phi ** bi) |
| q[:, a:b] = np.round(coeffs[:, a:b] / step).astype(np.int32) |
| return q |
|
|
| def band_dequantize_dct(q: np.ndarray, boundaries, base_step: float, phi: float = PHI): |
| B, L = q.shape |
| coeffs = np.zeros((B, L), dtype=np.float64) |
| for bi in range(len(boundaries) - 1): |
| a, b = boundaries[bi], boundaries[bi + 1] |
| if a >= b: |
| continue |
| step = base_step * (phi ** bi) |
| coeffs[:, a:b] = q[:, a:b].astype(np.float64) * step |
| return coeffs |
|
|
| |
| |
| |
| def choose_fib_grid(n_symbols: int) -> int: |
| N = int(math.ceil(math.sqrt(n_symbols))) |
| fibs = [1, 2] |
| while fibs[-1] < N: |
| fibs.append(fibs[-1] + fibs[-2]) |
| return fibs[-1] |
|
|
| def ints_to_constellation(zints: np.ndarray): |
| zints = zints.astype(np.int64) |
| v = np.tanh(zints / 32.0).astype(np.float64) |
| k = np.arange(v.size, dtype=np.float64) |
| golden = 2 * math.pi / (PHI**2) |
| theta = golden * k + 2.0 * math.pi * (v * 0.25) |
| r = 1.0 + 0.35 * np.abs(v) |
| return (r * np.cos(theta) + 1j * r * np.sin(theta)).astype(np.complex64) |
|
|
| def hologram_spectrum_image(zints: np.ndarray, max_symbols: int = 262144): |
| if zints.size > max_symbols: |
| zints = zints[:max_symbols] |
| syms = ints_to_constellation(zints) |
| N = choose_fib_grid(syms.size) |
| total = N * N |
| if syms.size < total: |
| syms = np.pad(syms, (0, total - syms.size), constant_values=(1+0j)).astype(np.complex64) |
| U = syms.reshape(N, N) |
| F = np.fft.fftshift(np.fft.fft2(U)) |
| mag = np.log1p(np.abs(F)).astype(np.float32) |
| mag -= mag.min() |
| mag /= (mag.max() + 1e-12) |
| return (mag * 255).astype(np.uint8) |
|
|
| def make_hologram_preview(zints: np.ndarray, out_png: str, max_symbols: int = 262144): |
| img = hologram_spectrum_image(zints, max_symbols=max_symbols) |
| Image.fromarray(img).save(out_png) |
|
|
| def overlay_event_horizon_ring(imL: Image.Image, t: int, T: int): |
| """ |
| Draw a glowing event-horizon ring (as 3 concentric ellipses) on the hologram. |
| Radius expands with progress t/T. |
| """ |
| W, H = imL.size |
| cx, cy = W // 2, H // 2 |
| |
| r0 = int(0.08 * min(W, H)) |
| r1 = int((0.45 * min(W, H)) * (t / max(1, T)) + r0) |
|
|
| rgb = imL.convert("RGB") |
| d = ImageDraw.Draw(rgb) |
|
|
| |
| for k, alpha in [(0, 255), (3, 170), (6, 90)]: |
| rr = r1 + k |
| bbox = (cx - rr, cy - rr, cx + rr, cy + rr) |
| |
| col = (255, 215, 0) if alpha == 255 else (220, 180, 0) |
| d.ellipse(bbox, outline=col, width=2) |
|
|
| |
| d.ellipse((cx-2, cy-2, cx+2, cy+2), fill=(255, 215, 0)) |
| return rgb |
|
|
| |
| |
| |
| def fibonacci_spiral_tiles_for_area(n_pixels: int, max_tiles: int = 30): |
| fibs = fibonacci_sequence_std(max_tiles) |
| sizes = [] |
| area = 0 |
| for s in fibs: |
| sizes.append(int(s)) |
| area += int(s) * int(s) |
| if area >= n_pixels: |
| break |
| return sizes |
|
|
| def fibonacci_spiral_tile_positions(sizes): |
| tiles = [] |
| s0 = sizes[0] |
| minx, miny, maxx, maxy = 0, 0, s0, s0 |
| tiles.append((0, 0, s0)) |
|
|
| for i in range(1, len(sizes)): |
| s = sizes[i] |
| d = (i - 1) % 4 |
| if d == 0: |
| x, y = maxx, miny |
| elif d == 1: |
| x, y = maxx - s, maxy |
| elif d == 2: |
| x, y = minx - s, maxy - s |
| else: |
| x, y = minx, miny - s |
|
|
| tiles.append((x, y, s)) |
| minx = min(minx, x) |
| miny = min(miny, y) |
| maxx = max(maxx, x + s) |
| maxy = max(maxy, y + s) |
|
|
| bbox = (minx, miny, maxx, maxy) |
| return tiles, bbox |
|
|
| def bytes_to_fib_spiral_image(data: bytes, max_pixels: int = 262144, want_tiles=False): |
| arr = np.frombuffer(data, dtype=np.uint8) |
| if arr.size > max_pixels: |
| arr = arr[:max_pixels] |
| n = int(arr.size) |
| if n == 0: |
| if want_tiles: |
| return np.zeros((1, 1), dtype=np.uint8), [(0,0,1)], (0,0,1,1) |
| return np.zeros((1, 1), dtype=np.uint8) |
|
|
| sizes = fibonacci_spiral_tiles_for_area(n_pixels=n, max_tiles=32) |
| tiles, (minx, miny, maxx, maxy) = fibonacci_spiral_tile_positions(sizes) |
|
|
| W = int(maxx - minx) |
| H = int(maxy - miny) |
| img = np.zeros((H, W), dtype=np.uint8) |
| idx = 0 |
|
|
| for (x, y, s) in tiles: |
| if idx >= n: |
| break |
| x0 = int(x - minx) |
| y0_up = int(y - miny) |
| y0 = int((H - (y0_up + s))) |
|
|
| take = min(s * s, n - idx) |
| block = arr[idx:idx + take] |
| if take < s * s: |
| block = np.pad(block, (0, s * s - take), constant_values=0) |
| block2d = block.reshape(s, s) |
| img[y0:y0+s, x0:x0+s] = block2d |
| idx += take |
|
|
| if want_tiles: |
| return img, tiles, (minx, miny, maxx, maxy) |
| return img |
|
|
| def overlay_tile_outlines(imL: Image.Image, tiles, bbox_info, outline=(120,120,120)): |
| """ |
| Draw square outlines for the Fibonacci tiling on the right panel. |
| `tiles` are in y-up coords with original bbox min offsets. |
| """ |
| rgb = imL.convert("RGB") |
| d = ImageDraw.Draw(rgb) |
| |
| W, H = rgb.size |
|
|
| |
| minx, miny, maxx, maxy = bbox_info |
| |
| for (x, y, s) in tiles: |
| x0 = int(x - minx) |
| y0_up = int(y - miny) |
| y0 = int((H - (y0_up + s))) |
| d.rectangle((x0, y0, x0 + s - 1, y0 + s - 1), outline=outline, width=1) |
| return rgb |
|
|
| |
| |
| |
| def save_gif(frames, out_gif: str, duration=90): |
| if not frames: |
| return |
| frames[0].save(out_gif, save_all=True, append_images=frames[1:], duration=duration, loop=0, optimize=True) |
|
|
| def make_unzip_gif_from_Q(Q_full: np.ndarray, |
| boundaries, |
| base_step: float, |
| mu: float, |
| n_bytes: int, |
| out_gif: str, |
| max_bands: int = None, |
| gif_scale: int = 2, |
| max_symbols: int = 262144, |
| max_pixels: int = 262144, |
| draw_outlines: bool = True, |
| draw_horizon: bool = True): |
| n_total_bands = len(boundaries) - 1 |
| T = n_total_bands if max_bands is None else max(0, min(int(max_bands), n_total_bands)) |
| if T == 0: |
| T = 1 |
|
|
| frames = [] |
| band_slices = [(boundaries[i], boundaries[i+1]) for i in range(n_total_bands)] |
|
|
| for t in range(1, T + 1): |
| Q_part = np.zeros_like(Q_full) |
| for bi in range(t): |
| a, b = band_slices[bi] |
| if a < b: |
| Q_part[:, a:b] = Q_full[:, a:b] |
|
|
| |
| C_part = band_dequantize_dct(Q_part, boundaries, base_step=base_step, phi=PHI) |
| X_part = idct_blocks_ortho(C_part) |
| x = X_part.reshape(-1)[:n_bytes] |
| x = (x * 127.5) + float(mu) |
| x = np.clip(np.round(x), 0, 255).astype(np.uint8).tobytes() |
|
|
| |
| qflat = Q_part.reshape(-1).astype(np.int64) |
| holo_u8 = hologram_spectrum_image(qflat, max_symbols=max_symbols) |
| A = Image.fromarray(holo_u8).convert("L") |
| if gif_scale != 1: |
| A = A.resize((A.size[0]*gif_scale, A.size[1]*gif_scale), Image.NEAREST) |
|
|
| |
| if draw_horizon: |
| A_rgb = overlay_event_horizon_ring(A, t=t, T=T) |
| else: |
| A_rgb = A.convert("RGB") |
|
|
| |
| field_u8, tiles, bbox_info = bytes_to_fib_spiral_image(x, max_pixels=max_pixels, want_tiles=True) |
| Bimg = Image.fromarray(field_u8).convert("L") |
| if gif_scale != 1: |
| Bimg = Bimg.resize((Bimg.size[0]*gif_scale, Bimg.size[1]*gif_scale), Image.NEAREST) |
|
|
| if draw_outlines: |
| |
| |
| |
| minx, miny, maxx, maxy = bbox_info |
| tiles_s = [(x*gif_scale, y*gif_scale, s*gif_scale) for (x,y,s) in tiles] |
| bbox_s = (minx*gif_scale, miny*gif_scale, maxx*gif_scale, maxy*gif_scale) |
| B_rgb = overlay_tile_outlines(Bimg, tiles_s, bbox_s, outline=(120,120,120)) |
| else: |
| B_rgb = Bimg.convert("RGB") |
|
|
| |
| H = max(A_rgb.size[1], B_rgb.size[1]) |
| def pad_to_h_rgb(im, H): |
| if im.size[1] == H: |
| return im |
| canvas = Image.new("RGB", (im.size[0], H), (0,0,0)) |
| canvas.paste(im, (0, (H - im.size[1])//2)) |
| return canvas |
| A_rgb = pad_to_h_rgb(A_rgb, H) |
| B_rgb = pad_to_h_rgb(B_rgb, H) |
|
|
| W = A_rgb.size[0] |
| canvas = Image.new("RGB", (W*2, H + 44), (0, 0, 0)) |
| canvas.paste(A_rgb, (0, 44)) |
| canvas.paste(B_rgb, (W, 44)) |
|
|
| draw = ImageDraw.Draw(canvas) |
| draw.text((12, 10), f"FLC HOLOGRAPHIC UNZIP | bands {t}/{T} (horizon={T})", fill=(255, 215, 0)) |
| draw.text((12, 26), "LEFT: hologram + event horizon | RIGHT: Fibonacci spiral + tile outlines", fill=(180, 180, 180)) |
| frames.append(canvas) |
|
|
| save_gif(frames, out_gif, duration=90) |
|
|
| |
| |
| |
| def flc_encode_file(in_path: str, |
| out_flc: str, |
| preview_png: str = None, |
| unzip_gif: str = None, |
| block_len: int = 1024, |
| n_bands: int = 10, |
| base_step: float = 0.004, |
| use_mean_center: bool = True, |
| gif_scale: int = 2, |
| gif_horizon: int = None, |
| draw_horizon: bool = True, |
| draw_outlines: bool = True): |
| raw = open(in_path, "rb").read() |
| n_bytes = len(raw) |
|
|
| x = np.frombuffer(raw, dtype=np.uint8).astype(np.float64) |
| mu = float(np.mean(x)) if use_mean_center else 127.5 |
| x = (x - mu) / 127.5 |
|
|
| pad = (-x.size) % block_len |
| if pad: |
| x = np.pad(x, (0, pad), constant_values=0.0) |
| n_blocks = x.size // block_len |
| X = x.reshape(n_blocks, block_len) |
|
|
| C = dct_blocks_ortho(X) |
| boundaries = fibonacci_frequency_boundaries(block_len, n_bands) |
| Q = band_quantize_dct(C, boundaries, base_step=base_step, phi=PHI) |
|
|
| q_flat = Q.reshape(-1).astype(np.int64) |
| d = delta1d(q_flat) |
| payload = rle_fib_encode_ints(d) |
|
|
| header = struct.pack( |
| "<8sH Q I I H d d H", |
| MAGIC, VER_V2, |
| int(n_bytes), |
| int(block_len), |
| int(n_blocks), |
| int(n_bands), |
| float(base_step), |
| float(mu), |
| int(len(boundaries)), |
| ) |
| bnds = struct.pack("<" + "I"*len(boundaries), *[int(b) for b in boundaries]) |
| paylen = struct.pack("<I", int(len(payload))) |
|
|
| with open(out_flc, "wb") as f: |
| f.write(header) |
| f.write(bnds) |
| f.write(paylen) |
| f.write(payload) |
|
|
| if preview_png is not None: |
| make_hologram_preview(q_flat, preview_png) |
|
|
| if unzip_gif is not None: |
| make_unzip_gif_from_Q( |
| Q_full=Q, |
| boundaries=boundaries, |
| base_step=float(base_step), |
| mu=float(mu), |
| n_bytes=int(n_bytes), |
| out_gif=unzip_gif, |
| max_bands=gif_horizon, |
| gif_scale=gif_scale, |
| draw_horizon=draw_horizon, |
| draw_outlines=draw_outlines, |
| ) |
|
|
| return { |
| "n_bytes": int(n_bytes), |
| "block_len": int(block_len), |
| "n_blocks": int(n_blocks), |
| "n_bands": int(n_bands), |
| "base_step": float(base_step), |
| "mu": float(mu), |
| "payload_len": int(len(payload)), |
| "out_flc": out_flc, |
| "preview_png": preview_png, |
| "unzip_gif": unzip_gif, |
| "ratio": (os.path.getsize(out_flc) / max(1, n_bytes)), |
| } |
|
|
| def flc_decode_file(in_flc: str, |
| out_path: str, |
| unzip_gif: str = None, |
| max_bands: int = None, |
| gif_scale: int = 2, |
| draw_horizon: bool = True, |
| draw_outlines: bool = True): |
| blob = open(in_flc, "rb").read() |
| off = 0 |
|
|
| (magic, ver, n_bytes, block_len, n_blocks, n_bands, base_step, mu, bnd_len) = struct.unpack_from( |
| "<8sH Q I I H d d H", blob, off |
| ) |
| off += struct.calcsize("<8sH Q I I H d d H") |
| if magic != MAGIC: |
| raise ValueError("Bad magic (not FLC1)") |
| if ver != VER_V2: |
| raise ValueError(f"Unsupported version {ver}") |
|
|
| boundaries = list(struct.unpack_from("<" + "I"*bnd_len, blob, off)) |
| off += 4 * bnd_len |
|
|
| (payload_len,) = struct.unpack_from("<I", blob, off) |
| off += 4 |
| payload = blob[off:off+payload_len] |
| off += payload_len |
|
|
| n_coeffs = int(n_blocks) * int(block_len) |
| d = rle_fib_decode_ints(payload, n_coeffs) |
| q_flat = inv_delta1d(d).astype(np.int64) |
| Q_full = q_flat.reshape(int(n_blocks), int(block_len)).astype(np.int32) |
|
|
| |
| n_total_bands = len(boundaries) - 1 |
| bands_to_apply = n_total_bands if max_bands is None else max(0, min(int(max_bands), n_total_bands)) |
|
|
| if bands_to_apply < n_total_bands: |
| Q_use = np.zeros_like(Q_full) |
| for bi in range(bands_to_apply): |
| a, b = boundaries[bi], boundaries[bi+1] |
| if a < b: |
| Q_use[:, a:b] = Q_full[:, a:b] |
| else: |
| Q_use = Q_full |
|
|
| C = band_dequantize_dct(Q_use, boundaries, base_step=float(base_step), phi=PHI) |
| X = idct_blocks_ortho(C) |
|
|
| x = X.reshape(-1)[:int(n_bytes)] |
| x = (x * 127.5) + float(mu) |
| x = np.clip(np.round(x), 0, 255).astype(np.uint8) |
|
|
| with open(out_path, "wb") as f: |
| f.write(x.tobytes()) |
|
|
| if unzip_gif is not None: |
| make_unzip_gif_from_Q( |
| Q_full=Q_full, |
| boundaries=boundaries, |
| base_step=float(base_step), |
| mu=float(mu), |
| n_bytes=int(n_bytes), |
| out_gif=unzip_gif, |
| max_bands=(bands_to_apply if bands_to_apply > 0 else 1), |
| gif_scale=gif_scale, |
| draw_horizon=draw_horizon, |
| draw_outlines=draw_outlines, |
| ) |
|
|
| return { |
| "n_bytes": int(n_bytes), |
| "block_len": int(block_len), |
| "n_blocks": int(n_blocks), |
| "n_bands": int(n_bands), |
| "base_step": float(base_step), |
| "mu": float(mu), |
| "payload_len": int(payload_len), |
| "out_path": out_path, |
| "unzip_gif": unzip_gif, |
| "bands_applied": int(bands_to_apply), |
| } |
|
|
| |
| |
| |
| def cosine_similarity_bytes(a: bytes, b: bytes) -> float: |
| x = np.frombuffer(a, dtype=np.uint8).astype(np.float64) |
| y = np.frombuffer(b, dtype=np.uint8).astype(np.float64) |
| n = min(x.size, y.size) |
| x = x[:n]; y = y[:n] |
| x -= x.mean(); y -= y.mean() |
| nx = np.linalg.norm(x) + 1e-12 |
| ny = np.linalg.norm(y) + 1e-12 |
| return float(np.dot(x, y) / (nx * ny)) |
|
|
| |
| |
| |
| def flc_cli(argv=None): |
| ap = argparse.ArgumentParser(description="FLC v1.3: Fibonacci-banded DCT + φ quant + Fibonacci coding + horizon ring + tile outlines") |
| ap.add_argument("inp", type=str) |
| ap.add_argument("out_flc", type=str) |
| ap.add_argument("preview_png", type=str) |
| ap.add_argument("unzip_gif", type=str) |
| ap.add_argument("out_recovered", type=str) |
| ap.add_argument("--block", type=int, default=1024) |
| ap.add_argument("--bands", type=int, default=10) |
| ap.add_argument("--step", type=float, default=0.004) |
| ap.add_argument("--no_center", action="store_true") |
| ap.add_argument("--horizon", type=int, default=None) |
| ap.add_argument("--gif_scale", type=int, default=2) |
| ap.add_argument("--no_ring", action="store_true") |
| ap.add_argument("--no_outlines", action="store_true") |
| args = ap.parse_args(argv) |
|
|
| enc = flc_encode_file( |
| args.inp, args.out_flc, args.preview_png, unzip_gif=args.unzip_gif, |
| block_len=args.block, n_bands=args.bands, base_step=args.step, |
| use_mean_center=(not args.no_center), |
| gif_scale=args.gif_scale, |
| gif_horizon=None, |
| draw_horizon=(not args.no_ring), |
| draw_outlines=(not args.no_outlines), |
| ) |
| print("ENC:", enc) |
|
|
| dec = flc_decode_file( |
| args.out_flc, args.out_recovered, |
| unzip_gif="decode_" + os.path.basename(args.unzip_gif), |
| max_bands=args.horizon, |
| gif_scale=args.gif_scale, |
| draw_horizon=(not args.no_ring), |
| draw_outlines=(not args.no_outlines), |
| ) |
| print("DEC:", dec) |
|
|
| a = open(args.inp, "rb").read() |
| b = open(args.out_recovered, "rb").read() |
| cs = cosine_similarity_bytes(a, b) |
| print("Cosine similarity (bytes):", cs, " => ", cs*100, "%") |
| print("ORIG:", os.path.getsize(args.inp), "FLC:", os.path.getsize(args.out_flc), |
| "RATIO:", os.path.getsize(args.out_flc)/max(1, os.path.getsize(args.inp))) |
|
|
| |
| |
| |
| with open("test_input.bin","wb") as f: |
| f.write(b"Black hole horizon unzip meets Fibonacci spiral. " * 220) |
| enc = flc_encode_file("test_input.bin","test_output.flc","test_preview.png", |
| unzip_gif="test_unzip.gif", |
| block_len=1024, n_bands=10, base_step=0.004, |
| gif_scale=2, draw_horizon=True, draw_outlines=True) |
| print("ENC:", enc) |
| dec = flc_decode_file("test_output.flc","test_recovered.bin", |
| unzip_gif="test_decode_unzip.gif", |
| max_bands=None, gif_scale=2, |
| draw_horizon=True, draw_outlines=True) |
| print("DEC:", dec) |
| a = open("test_input.bin","rb").read() |
| b = open("test_recovered.bin","rb").read() |
| print("Cosine similarity:", cosine_similarity_bytes(a,b)) |
| decp = flc_decode_file("test_output.flc","test_partial.bin", |
| unzip_gif="test_partial_unzip.gif", |
| max_bands=4, gif_scale=2, |
| draw_horizon |