Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| from scipy.ndimage import gaussian_filter | |
| from matplotlib.colors import ListedColormap | |
| import io | |
| from PIL import Image | |
| # --------------------------------------------------------------------- | |
| # 1) Logic & Helper Functions (Kept mostly identical to your script) | |
| # --------------------------------------------------------------------- | |
| def all_conditions(condition_name: str): | |
| name = condition_name.strip() | |
| if name == "Control": | |
| return (0.001, 0.055, 0.95, 0.02, 0.0002) | |
| elif name == "Aggressive": | |
| return (0.001, 0.065, 0.95, 0.025, 0.0004) | |
| elif name == "DrugTreatment": | |
| return (0.001, 0.025, 0.95, 0.01, 0.0001) | |
| else: | |
| # Fallback default | |
| return (0.001, 0.055, 0.95, 0.02, 0.0002) | |
| def grid_to_rgb(grid): | |
| """ | |
| Convert the integer grid (1-6) to an RGB image array for fast display. | |
| 1: Normal (White) | |
| 2: Tumor (Blue) | |
| 3: Grown Vessel (Pink) | |
| 4: Sprouting Vessel (Dark Red) | |
| 5: Original Vessel (Pink) | |
| 6: Mutated Tumor (Light Blue) | |
| """ | |
| h, w = grid.shape | |
| rgb = np.ones((h, w, 3), dtype=np.uint8) * 255 # Default white | |
| # Define colors (R, G, B) 0-255 | |
| colors = { | |
| 2: (0, 0, 255), # Blue | |
| 3: (255, 191, 178), # Pink | |
| 4: (128, 0, 0), # Dark Red | |
| 5: (255, 191, 178), # Pink | |
| 6: (128, 128, 255) # Light Blue | |
| } | |
| for val, color in colors.items(): | |
| mask = (grid == val) | |
| rgb[mask] = color | |
| return rgb | |
| # --------------------------------------------------------------------- | |
| # 2) Main Simulation Logic (Generator) | |
| # --------------------------------------------------------------------- | |
| def run_simulation(condition_name, num_iterations, grid_size): | |
| # Retrieve parameters | |
| (break_threshold, tumor_chance, death_chance, | |
| mutation_chance, metastasis_chance) = all_conditions(condition_name) | |
| # Simulation constants | |
| initial_tumor_size = 2 | |
| neighborhood_size = 1 | |
| vessel_humanizer = 0.5 | |
| min_tumor_cell_count_for_metastasis = 50 | |
| treatment_time = [] | |
| # Setup Grid | |
| grid = np.ones((grid_size, grid_size), dtype=np.int32) | |
| center = (grid_size // 2, grid_size // 2) | |
| yy, xx = np.meshgrid(np.arange(grid_size), np.arange(grid_size), indexing="ij") | |
| radius_sq = (initial_tumor_size / 2.0) ** 2 | |
| circ_mask = (xx - center[1]) ** 2 + (yy - center[0]) ** 2 <= radius_sq | |
| grid[circ_mask] = 2 | |
| # Setup Vessel | |
| vessel_width = 5 | |
| vessel_height = int(grid_size * 0.75) # Scale vessel with grid | |
| start_row = 0 | |
| start_col = int(grid_size * 0.7) | |
| vessel_mask = np.zeros_like(grid, dtype=bool) | |
| # Bounds check | |
| end_row = min(start_row + vessel_height, grid_size) | |
| end_col = min(start_col + vessel_width, grid_size) | |
| vessel_mask[start_row:end_row, start_col:end_col] = True | |
| grid[vessel_mask] = 5 | |
| # Setup Map / Polarization | |
| rng = np.random.default_rng(1234) | |
| raw_map = rng.random((grid_size, grid_size)) | |
| raw_map = gaussian_filter(raw_map, sigma=1) | |
| mn, mx = raw_map.min(), raw_map.max() | |
| polarized = ((raw_map - mn) / (mx - mn)) ** 2 | |
| growth_rate_map = np.zeros_like(polarized) | |
| growth_rate_map[polarized <= 0.3] = 1.0 | |
| growth_rate_map[(polarized > 0.3) & (polarized <= 0.6)] = 0.02 | |
| growth_rate_map[polarized > 0.6] = 0.02 | |
| adjusted_tumor_chance = tumor_chance * growth_rate_map | |
| # Tracking arrays | |
| normal_tumor_count = np.zeros(num_iterations, dtype=int) | |
| mutated_tumor_count = np.zeros(num_iterations, dtype=int) | |
| net_survival = np.zeros(num_iterations, dtype=float) | |
| number_of_breaks = 0 | |
| max_distance = abs(center[1] - start_col) or 1 | |
| metastasis_flag = False | |
| # Neighbors offset | |
| d = np.arange(-neighborhood_size, neighborhood_size + 1) | |
| dy, dx = np.meshgrid(d, d, indexing="ij") | |
| neigh_offsets = np.stack([dy.ravel(), dx.ravel()], axis=1) | |
| neigh_offsets = neigh_offsets[~((neigh_offsets[:, 0] == 0) & (neigh_offsets[:, 1] == 0))] | |
| # --- ITERATION LOOP --- | |
| for it in range(num_iterations): | |
| new_grid = grid.copy() | |
| grow_mask = np.zeros_like(grid, dtype=bool) | |
| # We perform a simplified pass to speed up Python loops | |
| # (Optimizing the double loop for strict Python speed in web app) | |
| # 1. Identify active cells to reduce loop overhead | |
| # Cells that are 2, 6 (tumor) or 5 (vessel) or 4 (sprout) | |
| active_mask = (grid != 1) | |
| # Dilate mask to check neighbors | |
| # For simplicity in this demo, we scan the full grid or use list of coords | |
| # Using full scan for fidelity to original logic, but be mindful of speed. | |
| # To keep UI responsive, we process logic exactly as provided: | |
| for i in range(grid_size): | |
| for j in range(grid_size): | |
| cell_val = grid[i, j] | |
| # Tumor growth | |
| if cell_val in (2, 6): | |
| # Check neighbors | |
| for off in neigh_offsets: | |
| ni, nj = i + off[0], j + off[1] | |
| if 0 <= ni < grid_size and 0 <= nj < grid_size and grid[ni, nj] == 1: | |
| # Check vessel adjacency | |
| adj = False | |
| for off2 in neigh_offsets: | |
| vi, vj = ni + off2[0], nj + off2[1] | |
| if 0 <= vi < grid_size and 0 <= vj < grid_size: | |
| if grid[vi, vj] in (3, 5): | |
| adj = True | |
| break | |
| gch = adjusted_tumor_chance[ni, nj] * (1.5 if adj else 1.0) | |
| if rng.random() <= gch: | |
| grow_mask[ni, nj] = True | |
| # Vessel break -> sprout | |
| # Calculate local space only if needed | |
| if cell_val == 5 and (grid_size * 0.2 < i < grid_size * 0.8): | |
| # Quick empty space check (center only) | |
| if rng.random() < 0.5: # optim | |
| i0, i1 = max(0, i-1), min(grid_size, i+2) | |
| j0, j1 = max(0, j-1), min(grid_size, j+2) | |
| local_patch = grid[i0:i1, j0:j1] | |
| empty_space = np.sum(local_patch == 1) | |
| if empty_space != 0: | |
| if number_of_breaks == 0 and rng.random() <= break_threshold: | |
| new_grid[i, j] = 4 | |
| number_of_breaks += 1 | |
| elif number_of_breaks > 0: | |
| reduced_p = break_threshold / (10.0 * number_of_breaks) | |
| if rng.random() <= reduced_p: | |
| new_grid[i, j] = 4 | |
| number_of_breaks += 1 | |
| # Sprout movement | |
| if cell_val == 4: | |
| # check local tumor count | |
| i0, i1 = max(0, i-1), min(grid_size, i+2) | |
| j0, j1 = max(0, j-1), min(grid_size, j+2) | |
| local_tumor_count = np.sum((grid[i0:i1, j0:j1] == 2) | (grid[i0:i1, j0:j1] == 6)) | |
| if local_tumor_count == 0: | |
| # Move sprout | |
| # Find closest tumor cell (expensive, but necessary for logic) | |
| tumor_locs = np.argwhere((grid == 2) | (grid == 6)) | |
| if tumor_locs.size > 0: | |
| dists = (tumor_locs[:, 0] - i)**2 + (tumor_locs[:, 1] - j)**2 | |
| idx = np.argmin(dists) | |
| ta, tb = tumor_locs[idx] | |
| else: | |
| ta, tb = i, j # Stay put if no tumor | |
| best_dist = 1e30 | |
| grow_c, grow_d = i, j | |
| # Look for best move | |
| for ci in range(i-1, i+2): | |
| for cj in range(j-1, j+2): | |
| if 0 <= ci < grid_size and 0 <= cj < grid_size: | |
| dtmp = (ci - ta)**2 + (cj - tb)**2 # squared dist is enough | |
| if dtmp < best_dist: | |
| best_dist = dtmp | |
| grow_c, grow_d = ci, cj | |
| # Humanizer | |
| if rng.random() <= vessel_humanizer: | |
| grow_c = np.clip(i + rng.integers(-1, 2), 0, grid_size-1) | |
| grow_d = np.clip(j + rng.integers(-1, 2), 0, grid_size-1) | |
| # Draw vessel trail | |
| bv_width = int(5 * abs(j - center[1]) / max_distance) | |
| # limit width | |
| bv_width = max(0, min(bv_width, 3)) | |
| for w in range(-bv_width, bv_width + 1): | |
| ii = i + w | |
| if 0 <= ii < grid_size: | |
| new_grid[ii, j] = 3 | |
| new_grid[grow_c, grow_d] = 4 | |
| else: | |
| # Touched tumor | |
| new_grid[i, j] = 3 | |
| metastasis_flag = True | |
| # Apply growth | |
| new_grid[grow_mask] = 2 | |
| # Mutation | |
| mut_rand = rng.random((grid_size, grid_size)) | |
| mutate_mask = (grid == 2) & (mut_rand <= mutation_chance) | |
| new_grid[mutate_mask] = 6 | |
| # Drug (Not fully implemented in input logic time array, but logic exists) | |
| # If user wanted drugs, we'd add `if it in treatment_time:` logic here. | |
| # For this demo, we assume treatment_time is empty list as per init. | |
| # Metastasis | |
| total_tumor_now = np.sum((grid == 2) | (grid == 6)) | |
| if total_tumor_now >= min_tumor_cell_count_for_metastasis and metastasis_flag: | |
| vessel_cells = np.argwhere((grid == 3) | (grid == 5)) | |
| if vessel_cells.size > 0: | |
| num_sites = rng.integers(1, 4) | |
| for _ in range(num_sites): | |
| vr, vc = vessel_cells[rng.integers(0, vessel_cells.shape[0])] | |
| for off in neigh_offsets: | |
| nr, nc = vr + off[0], vc + off[1] | |
| if 0 <= nr < grid_size and 0 <= nc < grid_size: | |
| if new_grid[nr, nc] == 1 and rng.random() <= metastasis_chance: | |
| new_grid[nr, nc] = 2 | |
| break | |
| grid = new_grid | |
| # Stats | |
| normal_tumor_count[it] = np.sum(grid == 2) | |
| mutated_tumor_count[it] = np.sum(grid == 6) | |
| total_cells = normal_tumor_count[it] + mutated_tumor_count[it] | |
| net_survival[it] = max(0.0, 100.0 * (1 - total_cells / 20000.0)) | |
| # Yield results for Animation (every 5 frames to speed up UI) | |
| if it % 5 == 0 or it == num_iterations - 1: | |
| # Convert grid to Image for Gradio | |
| img_array = grid_to_rgb(grid) | |
| yield img_array, None, None, f"Iteration {it}/{num_iterations}" | |
| # --- FINAL PLOTS --- | |
| # 1. Growth Curve | |
| fig1, ax1 = plt.subplots(figsize=(6, 4)) | |
| x = np.arange(1, num_iterations + 1) | |
| ax1.plot(x, normal_tumor_count, label="Normal tumor (2)", color='blue') | |
| ax1.plot(x, mutated_tumor_count, label="Mutated tumor (6)", color='cyan') | |
| ax1.set_xlabel("Iteration") | |
| ax1.set_ylabel("Count") | |
| ax1.set_title(f"Tumor Growth: {condition_name}") | |
| ax1.legend() | |
| ax1.grid(True, alpha=0.3) | |
| # 2. Survival Curve | |
| fig2, ax2 = plt.subplots(figsize=(6, 4)) | |
| ax2.plot(x, net_survival, label="Net survival (%)", color="red") | |
| ax2.set_ylim(0, 100) | |
| ax2.set_xlabel("Iteration") | |
| ax2.set_ylabel("Survival (%)") | |
| ax2.set_title(f"Survival: {condition_name}") | |
| ax2.legend() | |
| ax2.grid(True, alpha=0.3) | |
| # Return final state | |
| img_array = grid_to_rgb(grid) | |
| yield img_array, fig1, fig2, "Simulation Complete" | |
| # --------------------------------------------------------------------- | |
| # 3) Gradio Interface | |
| # --------------------------------------------------------------------- | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Tumor Growth Cellular Automaton") | |
| gr.Markdown("Python port of `camodel.m`. Simulates tumor growth, angiogenesis, and mutation.") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| cond_dd = gr.Dropdown(["Control", "Aggressive", "DrugTreatment"], value="Control", label="Condition") | |
| iters_sld = gr.Slider(50, 500, value=200, step=10, label="Iterations") | |
| size_sld = gr.Slider(50, 300, value=150, step=10, label="Grid Size") | |
| btn = gr.Button("Run Simulation", variant="primary") | |
| status_txt = gr.Textbox(label="Status", value="Ready") | |
| with gr.Column(scale=2): | |
| # Output grid as an image (updates live) | |
| grid_out = gr.Image(label="Live Grid State", elem_id="grid_viz") | |
| with gr.Row(): | |
| plot1 = gr.Plot(label="Growth Statistics") | |
| plot2 = gr.Plot(label="Survival Rate") | |
| # Connect function | |
| btn.click( | |
| fn=run_simulation, | |
| inputs=[cond_dd, iters_sld, size_sld], | |
| outputs=[grid_out, plot1, plot2, status_txt] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |