rnba 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rnba-0.2.1 → rnba-0.2.2}/PKG-INFO +3 -1
- {rnba-0.2.1 → rnba-0.2.2}/README.md +2 -0
- {rnba-0.2.1 → rnba-0.2.2}/pyproject.toml +1 -1
- {rnba-0.2.1 → rnba-0.2.2}/rnba/__init__.py +1 -1
- {rnba-0.2.1 → rnba-0.2.2}/rnba/visualize_activity_flow.py +209 -39
- {rnba-0.2.1 → rnba-0.2.2}/.gitignore +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/LICENSE +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/NBA24_QA_sentence_input.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/NBA_SGall_sentence_input.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/NBA_read_architecture.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/.gitignore +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/CMakeLists.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/CMakePresets.json +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/README.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/data/README.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/data/example_snb1_contract.json +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/data/model_smoke_contract.json +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/data/network.schema.json +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/data/snb15_contract.json +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/data/snb20_contract.json +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/include/model.hpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/include/output.hpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/include/parser.hpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/include/simulator.hpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/include/sparse_matrix.hpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/src/main.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/src/model.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/src/output.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/src/parser.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/src/simulator.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/src/sparse_matrix.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tests/CMakeLists.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tests/data/model_smoke_contract.json +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tests/data/snb20_contract.json +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tests/test_golden.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tests/test_parser.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tests/test_simulator.cpp +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tools/export_network.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tools/plot_cpp_qa.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tools/plot_ncv_output.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/cpp-sim/tools/plot_trace_comparison.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/analysis/QA_GENERATED_SIMULATION_WALKTHROUGH.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/analysis/README.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/analysis/collapse_verification.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/analysis/rowviz.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/analysis/schematic_inclusion_matrix.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/analysis/visualize_core_circuits.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/bindings-file-format.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/instruction-file-formats.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/snb15_population_categories.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/docs/templates/schematic_template_manifest.md +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/Collapsed_network_force.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/Collapsed_network_force_2026-05-22_noring_v1.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/Example_01_WIlsonCowan.ipynb +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/Example_02_BasicCircuits.ipynb +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/collapse_bgates.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/colour_gexf.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/demo_network_visualization.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/example_2.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/example_3.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/example_clamp.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/example_nCV_generated.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/example_network_creation.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/example_qa_create_network.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/example_qa_generated.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/example_qa_simulation.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_circle_16k.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_circle_ei_32k.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_ei_grey_32k.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_ei_grey_seeded_32k.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_force_16k.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_force_ei_32k.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_grey_16k.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_sfdp.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_sfdp_ei.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_sfdp_grey.gephi +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_network_sfdp_rows12.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/full_scale_sfdp_visualization.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/gexf_roles.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/layout_rows.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/render_collapsed.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/render_gexf.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/rnba_roles_row1.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/rnba_roles_snb15.gexf +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/row_visualization.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/seed_circle_ext.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/seed_ei_gexf.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/examples/visualize_architecture.sh +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/_sim.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/architecture.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/blackboard.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/circuit.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/circuits/__init__.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/circuits/core.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/cli.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/constants.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/converters/__init__.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/converters/architecture.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/inputs.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/network.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/nodes.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/plotting.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/schematic_visualization.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/simulation.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/rnba/visualize.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/QA_3NVN_O_fail.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/QA_3NVN_O_success.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/RNBA_SGall_SNB15_Bindings_Sentence_3NVN.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/RNBA_arch_SGall_SNB15.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/RNBA_arch_SGall_SNB20.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/RNBA_arch_SGall_SNB30.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/Sentence_2CV.txt +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/conftest.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/integration/__init__.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/integration/test_architecture_vs_gexf.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_architecture.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_basic.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_blackboard.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_circuit.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_circuits.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_clamp_integration.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_cpp_ncv_golden.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_instruction_unified.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_instruction_window_semantics.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_network.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_nodes.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_rnba_arch_converter.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_rnba_simulation_launcher.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_simulation_engines.py +0 -0
- {rnba-0.2.1 → rnba-0.2.2}/tests/test_visualize.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: rnba
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Recurrent Neural Blackboard Architecture - a simulation framework for neuro-cognitive modelling.
|
|
5
5
|
Author-Email: RNBA Contributors <m.dekamps@leeds.ac.uk>
|
|
6
6
|
License: MIT
|
|
@@ -163,6 +163,8 @@ python -m rnba.visualize_activity_flow \
|
|
|
163
163
|
|
|
164
164
|
To zoom in on the top of the blackboard, add `--first-rows N` (e.g. `--first-rows 5`): the view is cropped to the first N rows and markers are enlarged for legibility, while the layout and trace mapping stay identical to the full 15-row view. This works for both the single-frame preview (`--preview-time-ms`) and full video rendering, and mirrors the `--first-rows` option of the static schematic renderer (see below).
|
|
165
165
|
|
|
166
|
+
Frame rendering dominates the runtime. Pass `--workers N` to render frames across N processes (each holds its own copy of the base schematic); output is identical to a sequential run. `--dpi` trades resolution for size but has little effect on speed, since most per-frame cost is drawing the schematic rather than rasterising pixels.
|
|
167
|
+
|
|
166
168
|
See `python -m rnba.visualize_activity_flow --help` for the full option set (codec, styling, frame selection).
|
|
167
169
|
|
|
168
170
|
## Quick Start: Replicating the QA Simulation
|
|
@@ -126,6 +126,8 @@ python -m rnba.visualize_activity_flow \
|
|
|
126
126
|
|
|
127
127
|
To zoom in on the top of the blackboard, add `--first-rows N` (e.g. `--first-rows 5`): the view is cropped to the first N rows and markers are enlarged for legibility, while the layout and trace mapping stay identical to the full 15-row view. This works for both the single-frame preview (`--preview-time-ms`) and full video rendering, and mirrors the `--first-rows` option of the static schematic renderer (see below).
|
|
128
128
|
|
|
129
|
+
Frame rendering dominates the runtime. Pass `--workers N` to render frames across N processes (each holds its own copy of the base schematic); output is identical to a sequential run. `--dpi` trades resolution for size but has little effect on speed, since most per-frame cost is drawing the schematic rather than rasterising pixels.
|
|
130
|
+
|
|
129
131
|
See `python -m rnba.visualize_activity_flow --help` for the full option set (codec, styling, frame selection).
|
|
130
132
|
|
|
131
133
|
## Quick Start: Replicating the QA Simulation
|
|
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rnba"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Recurrent Neural Blackboard Architecture - a simulation framework for neuro-cognitive modelling."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -14,11 +14,12 @@ from __future__ import annotations
|
|
|
14
14
|
import argparse
|
|
15
15
|
import csv
|
|
16
16
|
import io
|
|
17
|
+
import multiprocessing as mp
|
|
17
18
|
import re
|
|
18
19
|
import subprocess
|
|
19
20
|
from dataclasses import dataclass
|
|
20
21
|
from pathlib import Path
|
|
21
|
-
from typing import Dict, Iterable, List, Optional
|
|
22
|
+
from typing import Dict, Iterable, List, Optional, Tuple
|
|
22
23
|
|
|
23
24
|
import matplotlib.pyplot as plt
|
|
24
25
|
from matplotlib.collections import LineCollection
|
|
@@ -1255,24 +1256,33 @@ def write_dynamics_audit_csv(
|
|
|
1255
1256
|
)
|
|
1256
1257
|
|
|
1257
1258
|
|
|
1258
|
-
|
|
1259
|
+
@dataclass
|
|
1260
|
+
class _OverlayContext:
|
|
1261
|
+
"""Static rendering state shared by every frame of one video.
|
|
1262
|
+
|
|
1263
|
+
The base schematic figure is expensive to build (thousands of artists);
|
|
1264
|
+
it is created once here, and per-frame overlays are added and removed
|
|
1265
|
+
on top of it.
|
|
1266
|
+
"""
|
|
1267
|
+
|
|
1268
|
+
fig: "plt.Figure"
|
|
1269
|
+
ax: "plt.Axes"
|
|
1270
|
+
symbols: List[Symbol]
|
|
1271
|
+
edges: List[SymbolEdge]
|
|
1272
|
+
by_symbol: Dict[str, MappingResult]
|
|
1273
|
+
symbol_pos: Dict[str, Tuple[float, float]]
|
|
1274
|
+
hidden_ids: set
|
|
1275
|
+
overlay_marker_scale: float
|
|
1276
|
+
overlay_line_scale: float
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def _build_overlay_context(
|
|
1259
1280
|
symbols: List[Symbol],
|
|
1260
1281
|
mappings: List[MappingResult],
|
|
1261
1282
|
edges: List[SymbolEdge],
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
base_grey: str,
|
|
1266
|
-
base_alpha: float,
|
|
1267
|
-
dpi: int,
|
|
1268
|
-
base_size: float,
|
|
1269
|
-
activation_threshold: float,
|
|
1270
|
-
activation_gamma: float,
|
|
1271
|
-
active_size_max: float,
|
|
1272
|
-
supersample_scale: float,
|
|
1273
|
-
first_rows: Optional[int] = None,
|
|
1274
|
-
crop_edges: bool = False,
|
|
1275
|
-
) -> None:
|
|
1283
|
+
first_rows: Optional[int],
|
|
1284
|
+
crop_edges: bool,
|
|
1285
|
+
) -> _OverlayContext:
|
|
1276
1286
|
by_symbol = {m.symbol_id: m for m in mappings}
|
|
1277
1287
|
symbol_pos = {s.symbol_id: (s.x, s.y) for s in symbols}
|
|
1278
1288
|
hidden_families = {"WORD_INHIB"}
|
|
@@ -1298,7 +1308,41 @@ def render_preview(
|
|
|
1298
1308
|
show_control_nodes=True,
|
|
1299
1309
|
activity_flow_theme=True,
|
|
1300
1310
|
)
|
|
1301
|
-
|
|
1311
|
+
return _OverlayContext(
|
|
1312
|
+
fig=fig,
|
|
1313
|
+
ax=fig.axes[0],
|
|
1314
|
+
symbols=symbols,
|
|
1315
|
+
edges=edges,
|
|
1316
|
+
by_symbol=by_symbol,
|
|
1317
|
+
symbol_pos=symbol_pos,
|
|
1318
|
+
hidden_ids=hidden_ids,
|
|
1319
|
+
overlay_marker_scale=overlay_marker_scale,
|
|
1320
|
+
overlay_line_scale=overlay_line_scale,
|
|
1321
|
+
)
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
def _draw_activity_overlay(
|
|
1325
|
+
ctx: _OverlayContext,
|
|
1326
|
+
trace_values: Dict[int, float],
|
|
1327
|
+
vmax: float,
|
|
1328
|
+
activation_threshold: float,
|
|
1329
|
+
activation_gamma: float,
|
|
1330
|
+
active_size_max: float,
|
|
1331
|
+
) -> list:
|
|
1332
|
+
"""Draw one frame's activity overlay onto the base figure.
|
|
1333
|
+
|
|
1334
|
+
Returns the created artists so a video loop can ``remove()`` them
|
|
1335
|
+
before drawing the next frame.
|
|
1336
|
+
"""
|
|
1337
|
+
ax = ctx.ax
|
|
1338
|
+
by_symbol = ctx.by_symbol
|
|
1339
|
+
symbol_pos = ctx.symbol_pos
|
|
1340
|
+
hidden_ids = ctx.hidden_ids
|
|
1341
|
+
edges = ctx.edges
|
|
1342
|
+
symbols = ctx.symbols
|
|
1343
|
+
overlay_marker_scale = ctx.overlay_marker_scale
|
|
1344
|
+
overlay_line_scale = ctx.overlay_line_scale
|
|
1345
|
+
artists: list = []
|
|
1302
1346
|
|
|
1303
1347
|
# Source representative activity drives outgoing edge intensity.
|
|
1304
1348
|
src_activity: Dict[str, float] = {}
|
|
@@ -1349,6 +1393,7 @@ def render_preview(
|
|
|
1349
1393
|
zorder=5,
|
|
1350
1394
|
)
|
|
1351
1395
|
ax.add_collection(active_edge_collection)
|
|
1396
|
+
artists.append(active_edge_collection)
|
|
1352
1397
|
|
|
1353
1398
|
x_act: List[float] = []
|
|
1354
1399
|
y_act: List[float] = []
|
|
@@ -1398,8 +1443,18 @@ def render_preview(
|
|
|
1398
1443
|
s_act.append(size)
|
|
1399
1444
|
|
|
1400
1445
|
if x_act:
|
|
1401
|
-
ax.scatter(x_act, y_act, s=s_act, c=c_act, edgecolors="none", zorder=6)
|
|
1446
|
+
scatter_artist = ax.scatter(x_act, y_act, s=s_act, c=c_act, edgecolors="none", zorder=6)
|
|
1447
|
+
artists.append(scatter_artist)
|
|
1448
|
+
|
|
1449
|
+
return artists
|
|
1402
1450
|
|
|
1451
|
+
|
|
1452
|
+
def _save_frame(
|
|
1453
|
+
fig: "plt.Figure",
|
|
1454
|
+
out_png: Path,
|
|
1455
|
+
dpi: int,
|
|
1456
|
+
supersample_scale: float,
|
|
1457
|
+
) -> None:
|
|
1403
1458
|
out_png.parent.mkdir(parents=True, exist_ok=True)
|
|
1404
1459
|
scale = supersample_scale if supersample_scale > 1.0 else 1.0
|
|
1405
1460
|
target_w = max(1, int(round(fig.get_figwidth() * dpi)))
|
|
@@ -1432,7 +1487,39 @@ def render_preview(
|
|
|
1432
1487
|
fig.savefig(out_png, dpi=dpi)
|
|
1433
1488
|
else:
|
|
1434
1489
|
fig.savefig(out_png, dpi=super_dpi)
|
|
1435
|
-
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
def render_preview(
|
|
1493
|
+
symbols: List[Symbol],
|
|
1494
|
+
mappings: List[MappingResult],
|
|
1495
|
+
edges: List[SymbolEdge],
|
|
1496
|
+
trace_values: Dict[int, float],
|
|
1497
|
+
out_png: Path,
|
|
1498
|
+
vmax: float,
|
|
1499
|
+
base_grey: str,
|
|
1500
|
+
base_alpha: float,
|
|
1501
|
+
dpi: int,
|
|
1502
|
+
base_size: float,
|
|
1503
|
+
activation_threshold: float,
|
|
1504
|
+
activation_gamma: float,
|
|
1505
|
+
active_size_max: float,
|
|
1506
|
+
supersample_scale: float,
|
|
1507
|
+
first_rows: Optional[int] = None,
|
|
1508
|
+
crop_edges: bool = False,
|
|
1509
|
+
) -> None:
|
|
1510
|
+
ctx = _build_overlay_context(symbols, mappings, edges, first_rows, crop_edges)
|
|
1511
|
+
try:
|
|
1512
|
+
_draw_activity_overlay(
|
|
1513
|
+
ctx,
|
|
1514
|
+
trace_values,
|
|
1515
|
+
vmax=vmax,
|
|
1516
|
+
activation_threshold=activation_threshold,
|
|
1517
|
+
activation_gamma=activation_gamma,
|
|
1518
|
+
active_size_max=active_size_max,
|
|
1519
|
+
)
|
|
1520
|
+
_save_frame(ctx.fig, out_png, dpi=dpi, supersample_scale=supersample_scale)
|
|
1521
|
+
finally:
|
|
1522
|
+
plt.close(ctx.fig)
|
|
1436
1523
|
|
|
1437
1524
|
|
|
1438
1525
|
def _iter_sampled_trace_rows(
|
|
@@ -1479,6 +1566,54 @@ def _row_to_values(row: List[str], col_to_idx: Dict[int, int]) -> Dict[int, floa
|
|
|
1479
1566
|
return values
|
|
1480
1567
|
|
|
1481
1568
|
|
|
1569
|
+
# ----------------------------------------------------------------------
|
|
1570
|
+
# Parallel frame rendering
|
|
1571
|
+
# ----------------------------------------------------------------------
|
|
1572
|
+
# Each worker process builds its own base schematic once (the figure is a
|
|
1573
|
+
# live matplotlib object and cannot cross the process boundary), then
|
|
1574
|
+
# renders the frames it is handed. Frames are fully independent, so this
|
|
1575
|
+
# scales close to linearly with the worker count.
|
|
1576
|
+
|
|
1577
|
+
_FRAME_WORKER_STATE: Dict[str, object] = {}
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
def _frame_worker_init(
|
|
1581
|
+
symbols, mappings, edges, first_rows, crop_edges,
|
|
1582
|
+
frames_dir, dpi, supersample_scale,
|
|
1583
|
+
vmax, activation_threshold, activation_gamma, active_size_max,
|
|
1584
|
+
) -> None:
|
|
1585
|
+
ctx = _build_overlay_context(symbols, mappings, edges, first_rows, crop_edges)
|
|
1586
|
+
_FRAME_WORKER_STATE.update(
|
|
1587
|
+
ctx=ctx,
|
|
1588
|
+
frames_dir=frames_dir,
|
|
1589
|
+
dpi=dpi,
|
|
1590
|
+
supersample_scale=supersample_scale,
|
|
1591
|
+
vmax=vmax,
|
|
1592
|
+
activation_threshold=activation_threshold,
|
|
1593
|
+
activation_gamma=activation_gamma,
|
|
1594
|
+
active_size_max=active_size_max,
|
|
1595
|
+
)
|
|
1596
|
+
|
|
1597
|
+
|
|
1598
|
+
def _frame_worker_render(task) -> int:
|
|
1599
|
+
idx, values = task
|
|
1600
|
+
st = _FRAME_WORKER_STATE
|
|
1601
|
+
ctx = st["ctx"]
|
|
1602
|
+
overlay_artists = _draw_activity_overlay(
|
|
1603
|
+
ctx,
|
|
1604
|
+
values,
|
|
1605
|
+
vmax=st["vmax"],
|
|
1606
|
+
activation_threshold=st["activation_threshold"],
|
|
1607
|
+
activation_gamma=st["activation_gamma"],
|
|
1608
|
+
active_size_max=st["active_size_max"],
|
|
1609
|
+
)
|
|
1610
|
+
frame_path = st["frames_dir"] / f"frame_{idx:06d}.png"
|
|
1611
|
+
_save_frame(ctx.fig, frame_path, dpi=st["dpi"], supersample_scale=st["supersample_scale"])
|
|
1612
|
+
for artist in overlay_artists:
|
|
1613
|
+
artist.remove()
|
|
1614
|
+
return idx
|
|
1615
|
+
|
|
1616
|
+
|
|
1482
1617
|
def render_video(
|
|
1483
1618
|
symbols: List[Symbol],
|
|
1484
1619
|
mappings: List[MappingResult],
|
|
@@ -1510,6 +1645,7 @@ def render_video(
|
|
|
1510
1645
|
video_out: Path,
|
|
1511
1646
|
first_rows: Optional[int] = None,
|
|
1512
1647
|
crop_edges: bool = False,
|
|
1648
|
+
workers: int = 1,
|
|
1513
1649
|
) -> None:
|
|
1514
1650
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
1515
1651
|
frames_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -1522,31 +1658,57 @@ def render_video(
|
|
|
1522
1658
|
raise ValueError(f"No sampled rows found in {trace_csv}")
|
|
1523
1659
|
|
|
1524
1660
|
total = len(sampled_rows)
|
|
1661
|
+
|
|
1662
|
+
# Build per-frame activity maps sequentially, up front: the optional
|
|
1663
|
+
# input aligner advances a monotonic cursor, so it must see frames in
|
|
1664
|
+
# time order regardless of how they are later rendered.
|
|
1665
|
+
frame_tasks: List[Tuple[int, Dict[int, float]]] = []
|
|
1525
1666
|
for idx, (t_ms, row) in enumerate(sampled_rows):
|
|
1526
1667
|
values = _row_to_values(row, col_to_idx)
|
|
1527
1668
|
if input_aligner is not None:
|
|
1528
1669
|
values.update(input_aligner.values_at(t_ms))
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
dpi=dpi,
|
|
1540
|
-
base_size=base_size,
|
|
1541
|
-
activation_threshold=activation_threshold,
|
|
1542
|
-
activation_gamma=activation_gamma,
|
|
1543
|
-
active_size_max=active_size_max,
|
|
1544
|
-
supersample_scale=supersample_scale,
|
|
1545
|
-
first_rows=first_rows,
|
|
1546
|
-
crop_edges=crop_edges,
|
|
1670
|
+
frame_tasks.append((idx, values))
|
|
1671
|
+
|
|
1672
|
+
workers = max(1, int(workers))
|
|
1673
|
+
if workers > 1:
|
|
1674
|
+
workers = min(workers, total)
|
|
1675
|
+
print(f"Rendering {total} frames with {workers} workers ...")
|
|
1676
|
+
init_args = (
|
|
1677
|
+
symbols, mappings, edges, first_rows, crop_edges,
|
|
1678
|
+
frames_dir, dpi, supersample_scale,
|
|
1679
|
+
vmax, activation_threshold, activation_gamma, active_size_max,
|
|
1547
1680
|
)
|
|
1548
|
-
|
|
1549
|
-
|
|
1681
|
+
done = 0
|
|
1682
|
+
with mp.Pool(
|
|
1683
|
+
processes=workers,
|
|
1684
|
+
initializer=_frame_worker_init,
|
|
1685
|
+
initargs=init_args,
|
|
1686
|
+
) as pool:
|
|
1687
|
+
for _ in pool.imap_unordered(_frame_worker_render, frame_tasks, chunksize=4):
|
|
1688
|
+
done += 1
|
|
1689
|
+
if done % 25 == 0 or done == total:
|
|
1690
|
+
print(f"Rendered {done}/{total} frames")
|
|
1691
|
+
else:
|
|
1692
|
+
# Single-process path: reuse one base schematic across all frames.
|
|
1693
|
+
ctx = _build_overlay_context(symbols, mappings, edges, first_rows, crop_edges)
|
|
1694
|
+
try:
|
|
1695
|
+
for idx, values in frame_tasks:
|
|
1696
|
+
overlay_artists = _draw_activity_overlay(
|
|
1697
|
+
ctx,
|
|
1698
|
+
values,
|
|
1699
|
+
vmax=vmax,
|
|
1700
|
+
activation_threshold=activation_threshold,
|
|
1701
|
+
activation_gamma=activation_gamma,
|
|
1702
|
+
active_size_max=active_size_max,
|
|
1703
|
+
)
|
|
1704
|
+
frame_path = frames_dir / f"frame_{idx:06d}.png"
|
|
1705
|
+
_save_frame(ctx.fig, frame_path, dpi=dpi, supersample_scale=supersample_scale)
|
|
1706
|
+
for artist in overlay_artists:
|
|
1707
|
+
artist.remove()
|
|
1708
|
+
if (idx + 1) % 25 == 0 or (idx + 1) == total:
|
|
1709
|
+
print(f"Rendered {idx + 1}/{total} frames")
|
|
1710
|
+
finally:
|
|
1711
|
+
plt.close(ctx.fig)
|
|
1550
1712
|
|
|
1551
1713
|
cmd = [
|
|
1552
1714
|
"ffmpeg",
|
|
@@ -1661,6 +1823,13 @@ def main() -> None:
|
|
|
1661
1823
|
parser.add_argument("--fps", type=int, default=25, help="Output video frames per second")
|
|
1662
1824
|
parser.add_argument("--step-ms", type=float, default=10.0, help="Trace sampling stride in ms for video frames")
|
|
1663
1825
|
parser.add_argument("--max-frames", type=int, default=0, help="Optional cap on rendered frames (0 means no cap)")
|
|
1826
|
+
parser.add_argument(
|
|
1827
|
+
"--workers",
|
|
1828
|
+
type=int,
|
|
1829
|
+
default=1,
|
|
1830
|
+
help="Parallel frame-rendering worker processes (1 = sequential). "
|
|
1831
|
+
"Each worker holds its own copy of the base schematic.",
|
|
1832
|
+
)
|
|
1664
1833
|
parser.add_argument(
|
|
1665
1834
|
"--frames-dir",
|
|
1666
1835
|
type=Path,
|
|
@@ -1774,6 +1943,7 @@ def main() -> None:
|
|
|
1774
1943
|
video_out=args.video_out,
|
|
1775
1944
|
first_rows=args.first_rows,
|
|
1776
1945
|
crop_edges=args.crop_edges,
|
|
1946
|
+
workers=args.workers,
|
|
1777
1947
|
)
|
|
1778
1948
|
print(f"Wrote: {args.video_out}")
|
|
1779
1949
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|