fly-behavior / core /synth.py
katospiegel's picture
Deploy fly-behavior as an imaging-plaza Gradio Space (SDSC)
e0361cd verified
Raw
History Blame Contribute Delete
2.67 kB
"""Generate a synthetic top-down video of flies walking in a circular arena.
Each fly does a correlated random walk and alternates between active and rest
(sleep-like) states via a 2-state Markov process; it bounces off the arena wall.
Flies are dark Gaussian blobs on a bright background. Self-contained — no external
(licence-encumbered) video needed. Also returns the ground-truth tracks.
"""
from __future__ import annotations
import numpy as np
def fly_arena(T: int = 300, H: int = 256, W: int = 256, n_flies: int = 8,
fps: float = 15.0, seed: int = 0):
rng = np.random.default_rng(seed)
cx, cy, R = W / 2, H / 2, min(H, W) / 2 - 12
# initial positions inside the arena
ang = rng.uniform(0, 2 * np.pi, n_flies)
rad = R * np.sqrt(rng.uniform(0, 0.7, n_flies))
x = cx + rad * np.cos(ang)
y = cy + rad * np.sin(ang)
heading = rng.uniform(0, 2 * np.pi, n_flies)
active = np.ones(n_flies, bool)
tracks = np.zeros((T, n_flies, 2), np.float32) # ground truth (x, y)
states = np.zeros((T, n_flies), np.uint8) # 1 = active
yy, xx = np.mgrid[0:H, 0:W]
frames = np.empty((T, H, W), np.uint8)
for t in range(T):
# Markov active/rest transitions
flip_to_rest = active & (rng.random(n_flies) < 0.012)
flip_to_active = (~active) & (rng.random(n_flies) < 0.04)
active = (active & ~flip_to_rest) | flip_to_active
heading += rng.normal(0, 0.4, n_flies)
speed = np.where(active, rng.normal(2.6, 0.5, n_flies).clip(0.5, 5), 0.0)
x = x + speed * np.cos(heading)
y = y + speed * np.sin(heading)
# bounce off the circular wall
dx, dy = x - cx, y - cy
dist = np.hypot(dx, dy)
out = dist > R
if out.any():
nrm = np.arctan2(dy[out], dx[out])
heading[out] = nrm + np.pi + rng.normal(0, 0.3, out.sum())
x[out] = cx + (R - 1) * np.cos(nrm)
y[out] = cy + (R - 1) * np.sin(nrm)
tracks[t, :, 0] = x
tracks[t, :, 1] = y
states[t] = active.astype(np.uint8)
# render: bright background, arena ring, dark fly blobs
frame = np.full((H, W), 225.0, np.float32)
ring = np.abs(np.hypot(xx - cx, yy - cy) - R) < 1.5
frame[ring] = 150.0
for fx, fy in zip(x, y):
d2 = (xx - fx) ** 2 + (yy - fy) ** 2
frame -= 185.0 * np.exp(-d2 / (2 * 3.0 ** 2))
frame += rng.normal(0, 3, (H, W))
frames[t] = np.clip(frame, 0, 255).astype(np.uint8)
meta = {"fps": fps, "arena": [float(cx), float(cy), float(R)], "n_flies": n_flies}
return frames, tracks, states, meta