Spaces:
Sleeping
Sleeping
| """ | |
| Morphic DNA simulation engine β Gray-Scott reaction-diffusion parameterized by genomic data. | |
| Color palette mirrors the AdaptiveDNA desktop app (#0f172a bg, DNA green/blue/purple). | |
| """ | |
| from __future__ import annotations | |
| import io | |
| from typing import Optional | |
| import numpy as np | |
| from PIL import Image, ImageDraw, ImageFilter, ImageFont | |
| # ββ AdaptiveDNA color palette βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _NAVY = np.array([15, 23, 42], dtype=np.float32) | |
| _GREEN = np.array([20, 180, 118], dtype=np.float32) | |
| _BLUE = np.array([59, 130, 246], dtype=np.float32) | |
| _PURPLE = np.array([168, 85, 247], dtype=np.float32) | |
| _YELLOW = np.array([245, 158, 11], dtype=np.float32) | |
| _CYAN = np.array([6, 182, 212], dtype=np.float32) | |
| _CREAM = np.array([255, 248, 220], dtype=np.float32) | |
| _RED = np.array([239, 68, 68], dtype=np.float32) | |
| _AMBER = np.array([251, 191, 36], dtype=np.float32) | |
| CROP_TINTS: dict[str, np.ndarray] = { | |
| "Rice": _GREEN, | |
| "Maize": _YELLOW, | |
| "Wheat": _AMBER, | |
| "Tomato": _RED, | |
| "Soybean": _PURPLE, | |
| "default": _CYAN, | |
| } | |
| # ββ Color LUT builder βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def build_lut(tint: np.ndarray) -> np.ndarray: | |
| """Build a 256Γ3 uint8 LUT: navy β tint β blue β purple β cyan β cream.""" | |
| stops = [ | |
| (0, _NAVY), | |
| (60, tint * 0.35), | |
| (110, tint * 0.75), | |
| (150, tint), | |
| (185, _BLUE), | |
| (215, _PURPLE), | |
| (238, _CYAN), | |
| (255, _CREAM), | |
| ] | |
| lut = np.zeros((256, 3), dtype=np.float32) | |
| for i in range(len(stops) - 1): | |
| i0, c0 = stops[i] | |
| i1, c1 = stops[i + 1] | |
| span = i1 - i0 | |
| for j in range(i0, i1): | |
| t = (j - i0) / span | |
| lut[j] = c0 * (1 - t) + c1 * t | |
| lut[255] = _CREAM | |
| return np.clip(lut, 0, 255).astype(np.uint8) | |
| # ββ Gray-Scott reaction-diffusion βββββββββββββββββββββββββββββββββββββββββββββ | |
| def _laplacian(z: np.ndarray) -> np.ndarray: | |
| return ( | |
| np.roll(z, 1, axis=0) + np.roll(z, -1, axis=0) + | |
| np.roll(z, 1, axis=1) + np.roll(z, -1, axis=1) - 4.0 * z | |
| ) | |
| def run_gray_scott( | |
| width: int, | |
| height: int, | |
| iterations: int, | |
| feed: float, | |
| kill: float, | |
| seed: int, | |
| n_patches: int, | |
| ) -> np.ndarray: | |
| """Return the v-field (activator) after `iterations` steps.""" | |
| rng = np.random.default_rng(seed) | |
| u = np.ones((height, width), dtype=np.float32) | |
| v = np.zeros((height, width), dtype=np.float32) | |
| r = max(6, min(width, height) // 14) | |
| for _ in range(n_patches): | |
| cx = int(rng.integers(r, width - r)) | |
| cy = int(rng.integers(r, height - r)) | |
| noise = 0.04 * rng.random((2 * r, 2 * r)).astype(np.float32) | |
| u[cy - r:cy + r, cx - r:cx + r] = 0.50 + noise | |
| v[cy - r:cy + r, cx - r:cx + r] = 0.25 + noise | |
| Du, Dv = 0.16, 0.08 | |
| for _ in range(iterations): | |
| uvv = u * v * v | |
| u += Du * _laplacian(u) - uvv + feed * (1.0 - u) | |
| v += Dv * _laplacian(v) + uvv - (feed + kill) * v | |
| np.clip(u, 0.0, 1.0, out=u) | |
| np.clip(v, 0.0, 1.0, out=v) | |
| return v | |
| # ββ Image renderer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _FONT_MONO = None # loaded lazily; gracefully degrades to default | |
| def _get_font(size: int = 11): | |
| global _FONT_MONO | |
| if _FONT_MONO is None: | |
| try: | |
| _FONT_MONO = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", size) | |
| except Exception: | |
| _FONT_MONO = ImageFont.load_default() | |
| return _FONT_MONO | |
| def _render_v_field(v: np.ndarray, lut: np.ndarray, out_size: int) -> Image.Image: | |
| v_min, v_max = v.min(), v.max() | |
| v_norm = (v - v_min) / (v_max - v_min + 1e-9) | |
| # Amplify contrast: sigmoid-like stretch | |
| v_stretched = np.clip(v_norm * 1.8 - 0.1, 0, 1) | |
| idx = (v_stretched * 255).astype(np.uint8) | |
| rgb = lut[idx] | |
| img = Image.fromarray(rgb, mode="RGB") | |
| img = img.resize((out_size, out_size), Image.BILINEAR) | |
| # Subtle glow blend | |
| glow = img.filter(ImageFilter.GaussianBlur(radius=4)) | |
| return Image.blend(img, glow, 0.25) | |
| def _overlay_hud(img: Image.Image, label: str, generation: int, feed: float, kill: float) -> Image.Image: | |
| """Draw a minimal sci-fi HUD onto the image.""" | |
| w, h = img.size | |
| draw = ImageDraw.Draw(img, "RGBA") | |
| font = _get_font(12) | |
| # Corner brackets | |
| bracket_c = (20, 180, 118, 180) # DNA green | |
| blen = 24 | |
| for bx, by in [(6, 6), (w - 6, 6), (6, h - 6), (w - 6, h - 6)]: | |
| sx = 1 if bx < w // 2 else -1 | |
| sy = 1 if by < h // 2 else -1 | |
| draw.line([(bx, by), (bx + sx * blen, by)], fill=bracket_c, width=2) | |
| draw.line([(bx, by), (bx, by + sy * blen)], fill=bracket_c, width=2) | |
| # Top banner | |
| draw.rectangle([(0, 0), (w, 26)], fill=(15, 23, 42, 200)) | |
| draw.text((10, 6), "ADAPTIVEDNA Β· 2ND BRAIN", fill=(20, 180, 118, 230), font=font) | |
| gen_txt = f"GEN {generation:03d}" | |
| draw.text((w - 70, 6), gen_txt, fill=(168, 85, 247, 200), font=font) | |
| # Bottom banner | |
| draw.rectangle([(0, h - 26), (w, h)], fill=(15, 23, 42, 200)) | |
| draw.text((10, h - 19), label, fill=(200, 220, 255, 200), font=font) | |
| param_txt = f"f={feed:.4f} k={kill:.4f}" | |
| draw.text((w - 130, h - 19), param_txt, fill=(100, 150, 200, 180), font=font) | |
| return img | |
| def generate_morphic_image( | |
| gc_content: float = 50.0, | |
| avg_score: float = 0.70, | |
| dominant_crop: str = "default", | |
| session_count: int = 1, | |
| pattern_generation: int = 1, | |
| out_size: int = 512, | |
| grid: int = 220, | |
| iterations: int = 900, | |
| ) -> bytes: | |
| """ | |
| Generate a morphic DNA simulation PNG. | |
| Parameters are mapped to Gray-Scott feed/kill rates so the visual evolves | |
| as genomic data accumulates in the 2nd Brain. | |
| """ | |
| gc_norm = max(0.0, min(1.0, gc_content / 100.0)) | |
| score_norm = max(0.0, min(1.0, avg_score)) | |
| # Parameter mapping: | |
| # High GC β more worm-like patterns (higher feed) | |
| # High guide score β tighter, more structured patterns (higher kill) | |
| feed = 0.029 + gc_norm * 0.022 # 0.029 β 0.051 | |
| kill = 0.053 + score_norm * 0.012 # 0.053 β 0.065 | |
| seed = (session_count * 13 + pattern_generation * 7 + 42) % (2 ** 31) | |
| n_patches = min(3 + session_count // 8, 12) | |
| v = run_gray_scott(grid, grid, iterations, feed, kill, seed, n_patches) | |
| tint = CROP_TINTS.get(dominant_crop, CROP_TINTS["default"]) | |
| lut = build_lut(tint) | |
| img = _render_v_field(v, lut, out_size) | |
| label = f"{dominant_crop} Β· GC {gc_content:.1f}% Β· Score {avg_score:.2f}" | |
| img = _overlay_hud(img, label, pattern_generation, feed, kill) | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG", optimize=True) | |
| return buf.getvalue() | |
| def generate_brain_heatmap( | |
| gc_history: list[float], | |
| score_history: list[float], | |
| crop_frequency: dict[str, int], | |
| out_size: int = 512, | |
| ) -> bytes: | |
| """ | |
| Generate a 2-panel heatmap visualization of the brain's accumulated state: | |
| left = GC history strip, right = crop frequency bars. | |
| """ | |
| img = Image.new("RGB", (out_size, out_size), color=(15, 23, 42)) | |
| draw = ImageDraw.Draw(img) | |
| font = _get_font(12) | |
| w, h = out_size, out_size | |
| # ββ Title ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| draw.rectangle([(0, 0), (w, 30)], fill=(20, 30, 60)) | |
| draw.text((10, 8), "ADAPTIVEDNA 2ND BRAIN β STATE SNAPSHOT", fill=(20, 180, 118), font=font) | |
| # ββ GC history strip (left half) βββββββββββββββββββββββββββββββββββββββββ | |
| panel_w = w // 2 - 10 | |
| strip_top, strip_bot = 50, h - 60 | |
| strip_h = strip_bot - strip_top | |
| draw.text((10, 36), "GC CONTENT HISTORY", fill=(100, 150, 255), font=font) | |
| hist = gc_history[-panel_w:] if len(gc_history) > panel_w else gc_history | |
| if hist: | |
| for i, gc in enumerate(hist): | |
| t = max(0.0, min(1.0, gc / 100.0)) | |
| r = int(59 + (168 - 59) * t) | |
| g = int(130 + (85 - 130) * t) | |
| b = int(246 + (247 - 246) * t) | |
| bar_h = int(strip_h * t) | |
| x = 10 + i | |
| draw.line([(x, strip_bot), (x, strip_bot - bar_h)], fill=(r, g, b), width=1) | |
| draw.rectangle([(8, strip_top), (10 + panel_w, strip_bot)], outline=(40, 60, 100), width=1) | |
| # ββ Crop frequency bars (right half) βββββββββββββββββββββββββββββββββββββ | |
| rx_start = w // 2 + 5 | |
| draw.text((rx_start, 36), "CROP FREQUENCY", fill=(100, 150, 255), font=font) | |
| crop_colors = { | |
| "Rice": (20, 180, 118), | |
| "Maize": (245, 158, 11), | |
| "Wheat": (251, 191, 36), | |
| "Tomato": (239, 68, 68), | |
| "Soybean": (168, 85, 247), | |
| } | |
| max_freq = max(crop_frequency.values()) if crop_frequency else 1 | |
| bar_w = (w - rx_start - 15) // max(len(crop_frequency), 1) | |
| bar_x = rx_start + 5 | |
| for crop, freq in crop_frequency.items(): | |
| ratio = freq / max(max_freq, 1) | |
| bar_h = int((strip_h - 20) * ratio) | |
| color = crop_colors.get(crop, (100, 100, 200)) | |
| draw.rectangle([(bar_x, strip_bot - bar_h), (bar_x + bar_w - 6, strip_bot)], fill=color) | |
| draw.text((bar_x, strip_bot + 4), crop[:4], fill=(180, 200, 255), font=font) | |
| draw.text((bar_x, strip_bot - bar_h - 14), str(freq), fill=(220, 220, 220), font=font) | |
| bar_x += bar_w | |
| # ββ Score history line (bottom strip) βββββββββββββββββββββββββββββββββββββ | |
| bot_top = h - 56 | |
| draw.text((10, bot_top - 16), "GUIDE SCORE HISTORY", fill=(100, 150, 255), font=font) | |
| scores = score_history[-(w - 20):] if len(score_history) > w - 20 else score_history | |
| if len(scores) > 1: | |
| pts = [ | |
| (10 + int(i * (w - 20) / len(scores)), | |
| bot_top + int((1 - s) * 40)) | |
| for i, s in enumerate(scores) | |
| ] | |
| for i in range(len(pts) - 1): | |
| draw.line([pts[i], pts[i + 1]], fill=(6, 182, 212), width=2) | |
| draw.rectangle([(8, bot_top), (w - 8, h - 12)], outline=(40, 60, 100), width=1) | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG", optimize=True) | |
| return buf.getvalue() | |