-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathNotebook_00.py
More file actions
288 lines (245 loc) · 10.6 KB
/
Copy pathNotebook_00.py
File metadata and controls
288 lines (245 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# %% [raw]
# ---
# title: "Data Generation: Drosophila Visual System"
# author: "Allier, Lappalainen, Saalfeld"
# categories:
# - FlyVis
# - Simulation
# - Data Generation
# execute:
# echo: false
# image: "graphs_data/fly/flyvis_noise_free/activity_traces.png"
# description: "Simulate the Drosophila visual system with 13,741 neurons across 65 cell types at three intrinsic noise levels ($\\sigma = 0$, $0.05$, $0.5$) and generate voltage traces for GNN training and testing."
# ---
# %% [markdown]
# ## Simulation
#
# We simulated neural activity in the *Drosophila* visual system using
# [*flyvis*](https://github.com/TuragaLab/flyvis)' pretrained models [1]. The recurrent neural network contains
# 13,741 neurons from 65 cell types and 434,122 synaptic connections,
# corresponding to real neurons and their synapses. We restricted the original
# 721 retinotopic columns to the central subset of 217. Each neuron is modeled
# as a non-spiking compartment governed by
#
# $$\tau_i\frac{dv_i(t)}{dt} = -v_i(t) + V_i^{\text{rest}} + \sum_{j\in\mathcal{N}_i} \mathbf{W}_{ij}\,\text{ReLU}\!\big(v_j(t)\big) + I_i(t) + \sigma\,\xi_i(t),$$
#
# where $\tau_i$ and $V_i^{\text{rest}}$ are cell-type parameters,
# $\mathbf{W}_{ij}$ is the connectome-constrained synaptic weight,
# $I_i(t)$ the visual input, and
# $\xi_i(t) \sim \mathcal{N}(0,1)$ is independent Gaussian noise scaled
# by $\sigma$. The noise term $\sigma\,\xi_i(t)$ models intrinsic stochasticity in the
# membrane dynamics (e.g. channel noise, synaptic variability).
#
# Unlike measurement noise added post hoc, this intrinsic
# noise alters the dynamical trajectory of $v_i(t)$ and couples through the
# connectivity matrix $\mathbf{W}$. As a dynamical perturbation, it widens
# the distribution of visited voltages and propagates through synaptic
# connections, enriching the training signal.
#
# We generated data at three noise levels $\sigma$:
#
# | Dataset | $\sigma$ | Description |
# |---------|----------|-------------|
# | `flyvis_noise_free` | 0.0 | Deterministic (no intrinsic noise) |
# | `flyvis_noise_005` | 0.05 | Low intrinsic noise |
# | `flyvis_noise_05` | 0.5 | High intrinsic noise |
# %%
#| output: false
import os
import warnings
from IPython.display import Image, display
from flyvis_gnn.config import NeuralGraphConfig
from flyvis_gnn.generators.graph_data_generator import data_generate
from flyvis_gnn.utils import set_device, add_pre_folder, load_and_display, graphs_data_path
warnings.filterwarnings("ignore", message="pkg_resources is deprecated as an API")
warnings.filterwarnings("ignore", category=FutureWarning)
def display_image(path, width=700):
"""Display a full-resolution image; width controls inline size (px)."""
if not os.path.isfile(path):
print(f" image not found: {path} (run data generation first)")
return
display(Image(filename=path, width=width))
# %%
#| output: false
datasets = [
('flyvis_noise_free', 'Noise-free'),
('flyvis_noise_005', 'Noise 0.05'),
('flyvis_noise_05', 'Noise 0.5'),
]
config_root = "./config"
configs = {}
graphs_dirs = {}
for config_name, label in datasets:
config_file, pre_folder = add_pre_folder(config_name)
config = NeuralGraphConfig.from_yaml(f"{config_root}/{config_file}.yaml")
config.dataset = pre_folder + config.dataset
config.config_file = pre_folder + config_name
configs[config_name] = config
graphs_dirs[config_name] = graphs_data_path(pre_folder + config_name)
device = set_device(configs[datasets[0][0]].training.device)
for config_name, label in datasets:
print(f"{label}: {graphs_dirs[config_name]}")
# %% [markdown]
# ## Visual Stimulus
#
# The visual input $I_i(t)$ is derived from the DAVIS dataset [2, 3], a
# collection of natural video sequences at 480p resolution originally
# developed for optical flow and video segmentation benchmarks. Each RGB
# frame is center-cropped to 60% of its spatial extent, converted to
# grayscale luminance, and resampled onto the hexagonal photoreceptor
# lattice of 217 columns via a Gaussian box-eye filter (extent 8,
# kernel size 13). Each column feeds 8 photoreceptor types (R1–R8),
# giving 1,736 input neurons. The full set of sequences is augmented with horizontal and vertical
# flips and four rotations (0°, 90°, 180°, 270°) of the hexagonal
# array. The dataset is split 80/20 ensuring that there is no leakage between training and testing.
# %%
#| echo: true
#| output: false
print()
print("=" * 80)
print("GENERATE - Simulating fly visual system at three noise levels")
print("=" * 80)
for config_name, label in datasets:
config = configs[config_name]
graphs_dir = graphs_dirs[config_name]
print()
print(f"--- {label} (noise_model_level={config.simulation.noise_model_level}) ---")
data_exists = os.path.isdir(f'{graphs_dir}/x_list_train') or os.path.isdir(f'{graphs_dir}/x_list_0')
if data_exists:
print(f" data already exists at {graphs_dir}/")
print(" skipping simulation...")
else:
print(f" simulating {config.simulation.n_neurons} neurons, {config.simulation.n_neuron_types} types")
print(f" generating {config.simulation.n_frames} time frames")
print(f" visual input: {config.simulation.visual_input_type}")
print(f" output: {graphs_dir}/")
print()
data_generate(
config,
device=device,
visualize=True,
run_vizualized=0,
style="color",
alpha=1,
erase=False,
save=True,
step=100,
)
# %% [markdown]
# ### Training sequences
# First frames of the shuffled DAVIS sequences assigned to training
# (shown for the noise-free dataset).
# %%
#| lightbox: true
graphs_dir_0 = graphs_dirs[datasets[0][0]]
display_image(f"{graphs_dir_0}/shuffle_first_frames_train.png", width=900)
# %% [markdown]
# ### Test sequences
# First frames of the shuffled DAVIS sequences assigned to testing
# (shown for the noise-free dataset).
# %%
#| lightbox: true
display_image(f"{graphs_dir_0}/shuffle_first_frames_test.png", width=900)
# %% [markdown]
# ### Visual Stimulus Movie
#
# The animation below shows the visual input $I_i(t)$ as seen by the
# 217 hexagonal columns of the compound eye. Each hexagon represents
# one retinotopic column (8 photoreceptors, R1–R8, share the same
# input), and the color encodes the grayscale luminance at each time
# step. The stimulus is derived from the DAVIS natural video dataset
# and resampled onto the hexagonal lattice.
# %%
import zarr
import numpy as np
from IPython.display import Video
from matplotlib.animation import FFMpegWriter
stimulus_video_path = os.path.join(graphs_dir_0, "stimulus_hexagonal.mp4")
if not os.path.isfile(stimulus_video_path):
import matplotlib.pyplot as plt
# Load stimulus and positions from zarr
stim = zarr.open(store=os.path.join(graphs_dir_0, "x_list_train", "stimulus.zarr"), mode='r')
pos = np.array(zarr.open(store=os.path.join(graphs_dir_0, "x_list_train", "pos.zarr"), mode='r'))
n_input = configs[datasets[0][0]].simulation.n_input_neurons # 1736
n_columns = 217
group_size = n_input // n_columns # 8 photoreceptors per column
# Use one photoreceptor per column (first in each group) for display
col_indices = np.arange(0, n_input, group_size)
X = pos[col_indices] # (217, 2)
# Render video: subsample frames (every 2nd frame, first 2000 frames)
frame_step = 2
n_frames_video = min(2000, stim.shape[0])
frame_indices = range(0, n_frames_video, frame_step)
# Precompute color range from a sample
sample_frames = np.array(stim[:n_frames_video:frame_step*10])
vmin = float(sample_frames[:, col_indices].min())
vmax = float(sample_frames[:, col_indices].max())
del sample_frames
from matplotlib.patches import RegularPolygon
from matplotlib.collections import PatchCollection
from matplotlib.cm import ScalarMappable
from matplotlib.colors import Normalize
# Hex radius: half the minimum distance between column centres, scaled up
# to close gaps between adjacent hexagons.
from scipy.spatial import KDTree
dists, _ = KDTree(X).query(X, k=2)
hex_radius = float(dists[:, 1].min()) / 2 * 1.15
norm = Normalize(vmin=vmin, vmax=vmax)
cmap = plt.get_cmap('viridis')
fig, ax = plt.subplots(figsize=(5, 5))
fps = 10
writer = FFMpegWriter(fps=fps, metadata={'title': 'Visual Stimulus'})
with writer.saving(fig, stimulus_video_path, dpi=150):
for k in frame_indices:
ax.clear()
ax.set_axis_off()
frame = np.array(stim[k])
vals = frame[col_indices]
patches = [RegularPolygon((x, y), numVertices=6, radius=hex_radius,
orientation=0) for x, y in X]
pc = PatchCollection(patches, edgecolors='none')
pc.set_facecolor(cmap(norm(vals)))
ax.add_collection(pc)
ax.set_xlim(X[:, 0].min() - hex_radius, X[:, 0].max() + hex_radius)
ax.set_ylim(X[:, 1].min() - hex_radius, X[:, 1].max() + hex_radius)
ax.set_aspect('equal')
ax.set_title(f'frame {k}', fontsize=10)
writer.grab_frame()
plt.close(fig)
# %%
if os.path.isfile(stimulus_video_path):
display(Video(stimulus_video_path, embed=True, width=800))
# %% [markdown]
# ## Activity Traces
#
# Each plot below shows 100 randomly selected voltage traces $v_i(t)$
# over the first 10,000 time steps (out of 64,000 total). The three plots corresponds to the three level of intrinsinc noise used in simulations.
# %% [markdown]
# ### Noise-free ($\sigma = 0$)
# %%
#| lightbox: true
display_image(f"{graphs_dirs['flyvis_noise_free']}/activity_traces.png", width=850)
# %% [markdown]
# ### Low noise ($\sigma = 0.05$)
# %%
#| lightbox: true
display_image(f"{graphs_dirs['flyvis_noise_005']}/activity_traces.png", width=850)
# %% [markdown]
# ### High noise ($\sigma = 0.5$)
# %%
#| lightbox: true
display_image(f"{graphs_dirs['flyvis_noise_05']}/activity_traces.png", width=850)
# %% [markdown]
# ## References
#
# [1] J. K. Lappalainen et al., "Connectome-constrained networks predict
# neural activity across the fly visual system," *Nature*, 2024.
# [doi:10.1038/s41586-024-07939-3](https://doi.org/10.1038/s41586-024-07939-3)
#
# [2] D. J. Butler et al., "A Naturalistic Open Source Movie for Optical
# Flow Evaluation," *ECCV*, 2012.
# [doi:10.1007/978-3-642-33783-3_44](https://doi.org/10.1007/978-3-642-33783-3_44)
#
# [3] F. Perazzi et al., "A Benchmark Dataset and Evaluation Methodology
# for Video Object Segmentation," *CVPR*, 2016.
# [doi:10.1109/CVPR.2016.85](https://doi.org/10.1109/CVPR.2016.85)