Visualization
12 publication-quality spike train plots: raster, voltage, ISI, cross-correlogram, PSD, firing rate, phase portrait, population activity, instantaneous rate, spike comparison, network graph, weight matrix.
from sc_neurocore.viz import plot_raster, plot_voltage, plot_isi
sc_neurocore.viz
sc_neurocore.viz -- Tier: research (experimental / research).
NeuroArtGenerator
dataclass
Generates Art (Images) from Neural State.
Source code in src/sc_neurocore/viz/neuro_art.py
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 | @dataclass
class NeuroArtGenerator:
"""
Generates Art (Images) from Neural State.
"""
resolution: int = 256
def generate_visual(self, state_vector: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]:
"""
Maps a 1D state vector to a 2D RGB abstract image.
Uses the state to seed a generative pattern.
"""
# Seed random generator with state hash to be deterministic per state
# but chaotic
seed = int(np.sum(np.abs(state_vector)) * 10000) % (2**32)
rng = np.random.default_rng(seed)
# Create base canvas
img = np.zeros((self.resolution, self.resolution, 3), dtype=np.uint8)
# 'Painters' driven by state elements
num_painters = min(10, len(state_vector))
for i in range(num_painters):
val = state_vector[i]
# Map value to color
color = rng.integers(0, 255, 3)
# Map value to position/size
x = rng.integers(0, self.resolution)
y = rng.integers(0, self.resolution)
radius = int(abs(val) * 50) + 5
# Draw circle (naive)
y_grid, x_grid = np.ogrid[: self.resolution, : self.resolution]
mask = (x_grid - x) ** 2 + (y_grid - y) ** 2 <= radius**2
img[mask] = color
return img
|
generate_visual(state_vector)
Maps a 1D state vector to a 2D RGB abstract image.
Uses the state to seed a generative pattern.
Source code in src/sc_neurocore/viz/neuro_art.py
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 | def generate_visual(self, state_vector: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]:
"""
Maps a 1D state vector to a 2D RGB abstract image.
Uses the state to seed a generative pattern.
"""
# Seed random generator with state hash to be deterministic per state
# but chaotic
seed = int(np.sum(np.abs(state_vector)) * 10000) % (2**32)
rng = np.random.default_rng(seed)
# Create base canvas
img = np.zeros((self.resolution, self.resolution, 3), dtype=np.uint8)
# 'Painters' driven by state elements
num_painters = min(10, len(state_vector))
for i in range(num_painters):
val = state_vector[i]
# Map value to color
color = rng.integers(0, 255, 3)
# Map value to position/size
x = rng.integers(0, self.resolution)
y = rng.integers(0, self.resolution)
radius = int(abs(val) * 50) + 5
# Draw circle (naive)
y_grid, x_grid = np.ogrid[: self.resolution, : self.resolution]
mask = (x_grid - x) ** 2 + (y_grid - y) ** 2 <= radius**2
img[mask] = color
return img
|
WebVisualizer
Generates a standalone HTML file to visualize the SC Network.
Source code in src/sc_neurocore/viz/web_viz.py
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 | class WebVisualizer:
"""
Generates a standalone HTML file to visualize the SC Network.
"""
@staticmethod
def generate_html(layers: list[Any], filename="network_viz.html") -> None:
# 1. Build Graph Data
nodes = []
links = []
# Input Node
nodes.append({"id": "Input", "group": 0})
for i, layer in enumerate(layers):
layer_name = f"L{i}_{layer.__class__.__name__}"
# Layer Node (representing the whole layer for simplicity)
nodes.append(
{"id": layer_name, "group": i + 1, "neurons": getattr(layer, "n_neurons", "?")}
)
# Link from prev
prev = "Input" if i == 0 else f"L{i - 1}_{layers[i - 1].__class__.__name__}"
links.append({"source": prev, "target": layer_name, "value": 1})
data = {"nodes": nodes, "links": links}
json_str = json.dumps(data)
# 2. Embed in HTML Template
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SC-NeuroCore Visualization</title>
<style>
body {{ font-family: sans-serif; background: #111; color: #eee; }}
#graph {{ width: 800px; height: 600px; border: 1px solid #333; margin: 20px auto; }}
.node {{ fill: #69b3a2; stroke: #fff; }}
.link {{ stroke: #999; stroke-opacity: 0.6; }}
text {{ fill: #eee; font-size: 12px; }}
</style>
</head>
<body>
<h1 style="text-align:center">SC-NeuroCore Topology</h1>
<div id="graph_container" style="text-align:center">
<canvas id="graph" width="800" height="600"></canvas>
</div>
<script>
const graphData = {json_str};
const canvas = document.getElementById('graph');
const ctx = canvas.getContext('2d');
// Simple Force Layout Simulation
const nodes = graphData.nodes;
const links = graphData.links;
// Initial random positions
nodes.forEach(n => {{
n.x = Math.random() * 800;
n.y = Math.random() * 600;
n.vx = 0; n.vy = 0;
}});
function draw() {{
ctx.fillStyle = "#111";
ctx.fillRect(0,0,800,600);
// Draw Links
ctx.strokeStyle = "#555";
links.forEach(l => {{
const src = nodes.find(n => n.id === l.source);
const tgt = nodes.find(n => n.id === l.target);
if(src && tgt) {{
ctx.beginPath();
ctx.moveTo(src.x, src.y);
ctx.lineTo(tgt.x, tgt.y);
ctx.stroke();
}}
}});
// Draw Nodes
nodes.forEach(n => {{
ctx.fillStyle = n.group === 0 ? "#ff5555" : "#55aaff";
ctx.beginPath();
ctx.arc(n.x, n.y, 10, 0, 2*Math.PI);
ctx.fill();
ctx.fillStyle = "#fff";
ctx.fillText(n.id, n.x + 12, n.y + 4);
if(n.neurons) ctx.fillText(n.neurons + " neurons", n.x + 12, n.y + 16);
}});
}}
function update() {{
// Very simple layout: Linear positioning by group
const groups = [...new Set(nodes.map(n => n.group))];
const layerHeight = 500 / groups.length;
nodes.forEach(n => {{
// Target Y based on group
const ty = 50 + n.group * 100;
const tx = 400; // Center X
n.x += (tx - n.x) * 0.1;
n.y += (ty - n.y) * 0.1;
}});
draw();
requestAnimationFrame(update);
}}
update();
</script>
</body>
</html>
"""
with open(filename, "w") as f:
f.write(html_content)
logger.info("Generated Visualization: %s", filename)
|
cross_correlogram(spike_monitor, i, j, max_lag_ms=50, ax=None)
Spike cross-correlation between neurons i and j.
Source code in src/sc_neurocore/viz/plots.py
99
100
101
102
103
104
105
106
107
108 | def cross_correlogram(spike_monitor, i, j, max_lag_ms=50, ax=None):
"""Spike cross-correlation between neurons *i* and *j*."""
_require_mpl()
ax = _get_ax(ax)
corr, lags = spike_monitor.cross_correlation(i, j, max_lag=int(max_lag_ms))
ax.bar(lags, corr, width=1.0, color="teal", edgecolor="none")
ax.set_xlabel("Lag (timesteps)")
ax.set_ylabel("Correlation")
ax.set_title(f"Cross-Correlogram N{i}–N{j}")
return ax
|
firing_rate_plot(spike_monitor, bin_ms=10, ax=None)
Population firing rate histogram (spikes per bin).
Source code in src/sc_neurocore/viz/plots.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 | def firing_rate_plot(spike_monitor, bin_ms=10, ax=None):
"""Population firing rate histogram (spikes per bin)."""
_require_mpl()
ax = _get_ax(ax)
times, _ = spike_monitor.raster_data()
if times.size == 0:
ax.set_title("Firing Rate (no spikes)")
return ax
t_max = int(times.max()) + 1
bins = np.arange(0, t_max + bin_ms, bin_ms)
ax.hist(times, bins=bins, color="steelblue", edgecolor="none")
ax.set_xlabel("Timestep")
ax.set_ylabel("Spike count")
ax.set_title("Population Firing Rate")
return ax
|
instantaneous_rate_plot(spike_monitor, neuron_id, sigma_ms=20, ax=None)
Gaussian-kernel-smoothed instantaneous firing rate.
Source code in src/sc_neurocore/viz/plots.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241 | def instantaneous_rate_plot(spike_monitor, neuron_id, sigma_ms=20, ax=None):
"""Gaussian-kernel-smoothed instantaneous firing rate."""
_require_mpl()
ax = _get_ax(ax)
trains = spike_monitor.spike_trains
ts = trains.get(neuron_id, np.array([], dtype=np.int64))
if ts.size == 0:
ax.set_title(f"Rate N{neuron_id} (no spikes)")
return ax
t_max = int(ts.max()) + 1
binary = np.zeros(t_max, dtype=np.float64)
binary[ts] = 1.0
kernel_width = int(4 * sigma_ms)
kernel_x = np.arange(-kernel_width, kernel_width + 1, dtype=np.float64)
kernel = np.exp(-0.5 * (kernel_x / sigma_ms) ** 2)
kernel /= kernel.sum()
rate = np.convolve(binary, kernel, mode="same")
ax.plot(np.arange(t_max), rate, linewidth=0.8)
ax.set_xlabel("Timestep")
ax.set_ylabel("Rate (spikes/step)")
ax.set_title(f"Instantaneous Rate — Neuron {neuron_id}")
return ax
|
isi_histogram(spike_monitor, neuron_id, bins=50, ax=None)
Inter-spike interval distribution for a single neuron.
Source code in src/sc_neurocore/viz/plots.py
84
85
86
87
88
89
90
91
92
93
94
95
96 | def isi_histogram(spike_monitor, neuron_id, bins=50, ax=None):
"""Inter-spike interval distribution for a single neuron."""
_require_mpl()
ax = _get_ax(ax)
intervals = spike_monitor.isi(neuron_id)
if intervals.size == 0:
ax.set_title(f"ISI N{neuron_id} (no spikes)")
return ax
ax.hist(intervals, bins=bins, color="coral", edgecolor="none")
ax.set_xlabel("ISI (timesteps)")
ax.set_ylabel("Count")
ax.set_title(f"ISI Distribution — Neuron {neuron_id}")
return ax
|
network_graph(network, ax=None)
Node/edge diagram of populations and projections.
Source code in src/sc_neurocore/viz/plots.py
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 | def network_graph(network, ax=None):
"""Node/edge diagram of populations and projections."""
_require_mpl()
ax = _get_ax(ax)
pops = network.populations
n = len(pops)
if n == 0:
ax.set_title("Network (empty)")
return ax
angles = np.linspace(0, 2 * np.pi, n, endpoint=False)
xs, ys = np.cos(angles), np.sin(angles)
for idx, pop in enumerate(pops):
ax.scatter(xs[idx], ys[idx], s=200, zorder=3)
ax.annotate(pop.label, (xs[idx], ys[idx]), ha="center", va="bottom", fontsize=8)
pop_index = {id(p): idx for idx, p in enumerate(pops)}
for proj in network.projections:
si = pop_index.get(id(proj.source))
ti = pop_index.get(id(proj.target))
if si is not None and ti is not None:
ax.annotate(
"",
xy=(xs[ti], ys[ti]),
xytext=(xs[si], ys[si]),
arrowprops=dict(arrowstyle="->", color="gray"),
)
ax.set_aspect("equal")
ax.set_title("Network Graph")
ax.axis("off")
return ax
|
phase_portrait(state_monitor, var_x='v', var_y='w', neuron_id=0, ax=None)
2-D phase-plane trajectory for a single neuron.
Source code in src/sc_neurocore/viz/plots.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 | def phase_portrait(state_monitor, var_x="v", var_y="w", neuron_id=0, ax=None):
"""2-D phase-plane trajectory for a single neuron."""
_require_mpl()
ax = _get_ax(ax)
traces = state_monitor.traces
x = traces.get(var_x, np.empty((0, 0)))
y = traces.get(var_y, np.empty((0, 0)))
if x.size == 0 or y.size == 0:
ax.set_title("Phase Portrait (no data)")
return ax
ax.plot(x[:, neuron_id], y[:, neuron_id], linewidth=0.8)
ax.set_xlabel(var_x)
ax.set_ylabel(var_y)
ax.set_title(f"Phase Portrait — Neuron {neuron_id}")
return ax
|
population_activity(spike_monitor, bin_ms=5, ax=None)
Heatmap of binned spike counts per neuron.
Source code in src/sc_neurocore/viz/plots.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130 | def population_activity(spike_monitor, bin_ms=5, ax=None):
"""Heatmap of binned spike counts per neuron."""
_require_mpl()
ax = _get_ax(ax)
times, ids = spike_monitor.raster_data()
if times.size == 0:
ax.set_title("Population Activity (no spikes)")
return ax
n_neurons = spike_monitor.population.n
t_max = int(times.max()) + 1
n_bins = max(1, t_max // bin_ms)
mat = np.zeros((n_neurons, n_bins), dtype=np.float64)
for t, nid in zip(times, ids):
b = min(int(t) // bin_ms, n_bins - 1)
mat[nid, b] += 1
ax.imshow(mat, aspect="auto", origin="lower", interpolation="nearest")
ax.set_xlabel(f"Time bin ({bin_ms} steps)")
ax.set_ylabel("Neuron ID")
ax.set_title("Population Activity")
return ax
|
psd_plot(spike_monitor, neuron_id, ax=None)
Power spectral density of a single neuron's spike train.
Source code in src/sc_neurocore/viz/plots.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 | def psd_plot(spike_monitor, neuron_id, ax=None):
"""Power spectral density of a single neuron's spike train."""
_require_mpl()
ax = _get_ax(ax)
trains = spike_monitor.spike_trains
ts = trains.get(neuron_id, np.array([], dtype=np.int64))
if ts.size < 2:
ax.set_title(f"PSD N{neuron_id} (too few spikes)")
return ax
t_max = int(ts.max()) + 1
binary = np.zeros(t_max, dtype=np.float64)
binary[ts] = 1.0
freqs = np.fft.rfftfreq(t_max)
power = np.abs(np.fft.rfft(binary - binary.mean())) ** 2
ax.semilogy(freqs[1:], power[1:], linewidth=0.7)
ax.set_xlabel("Frequency (cycles/step)")
ax.set_ylabel("Power")
ax.set_title(f"PSD — Neuron {neuron_id}")
return ax
|
raster_plot(spike_monitor, ax=None, color='k', marker='.', s=1)
Spike raster from a SpikeMonitor.
Source code in src/sc_neurocore/viz/plots.py
35
36
37
38
39
40
41
42
43
44 | def raster_plot(spike_monitor, ax=None, color="k", marker=".", s=1):
"""Spike raster from a SpikeMonitor."""
_require_mpl()
ax = _get_ax(ax)
times, ids = spike_monitor.raster_data()
ax.scatter(times, ids, c=color, marker=marker, s=s, linewidths=0)
ax.set_xlabel("Timestep")
ax.set_ylabel("Neuron ID")
ax.set_title("Spike Raster")
return ax
|
spike_train_comparison(trains, labels=None, ax=None)
Overlay multiple spike trains as event plots.
Parameters
trains : list of np.ndarray
Each element is a 1-D array of spike timesteps.
labels : list of str, optional
Labels for each train.
Source code in src/sc_neurocore/viz/plots.py
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264 | def spike_train_comparison(trains, labels=None, ax=None):
"""Overlay multiple spike trains as event plots.
Parameters
----------
trains : list of np.ndarray
Each element is a 1-D array of spike timesteps.
labels : list of str, optional
Labels for each train.
"""
_require_mpl()
ax = _get_ax(ax)
for idx, tr in enumerate(trains):
lbl = labels[idx] if labels else f"Train {idx}"
y_vals = np.full_like(tr, idx, dtype=np.float64)
ax.scatter(tr, y_vals, s=2, label=lbl, linewidths=0)
ax.set_xlabel("Timestep")
ax.set_ylabel("Train index")
ax.set_title("Spike Train Comparison")
ax.legend(fontsize="small")
return ax
|
voltage_trace(state_monitor, neuron_ids=None, ax=None)
Membrane voltage traces from a StateMonitor.
Source code in src/sc_neurocore/viz/plots.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 | def voltage_trace(state_monitor, neuron_ids=None, ax=None):
"""Membrane voltage traces from a StateMonitor."""
_require_mpl()
ax = _get_ax(ax)
traces = state_monitor.traces
v = traces.get("v", np.empty((0, 0)))
if v.size == 0:
return ax
t = state_monitor.t
if neuron_ids is None:
neuron_ids = range(v.shape[1])
for nid in neuron_ids:
ax.plot(t, v[:, nid], label=f"N{nid}")
ax.set_xlabel("Timestep")
ax.set_ylabel("Membrane voltage")
ax.set_title("Voltage Traces")
ax.legend(fontsize="small")
return ax
|
weight_matrix(projection, ax=None, cmap='RdBu_r')
Connectivity weight heatmap from a Projection's CSR data.
Source code in src/sc_neurocore/viz/plots.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 | def weight_matrix(projection, ax=None, cmap="RdBu_r"):
"""Connectivity weight heatmap from a Projection's CSR data."""
_require_mpl()
ax = _get_ax(ax)
n_src = projection.source.n
n_tgt = projection.target.n
dense = np.zeros((n_src, n_tgt), dtype=np.float64)
for i in range(n_src):
for k in range(projection.indptr[i], projection.indptr[i + 1]):
dense[i, projection.indices[k]] = projection.data[k]
im = ax.imshow(dense, aspect="auto", cmap=cmap, interpolation="nearest")
ax.figure.colorbar(im, ax=ax, fraction=0.046)
ax.set_xlabel("Target neuron")
ax.set_ylabel("Source neuron")
ax.set_title("Weight Matrix")
return ax
|