Caron is a real-time visualiser for 2D simulation frames built around a simple idea: a simulation thread pushes frames into a shared buffer, and a visualisation thread consumes them at a (possibly different) rate. The Data layer acts as the “control tower” that keeps things stable when the producer/consumer rates drift apart.
For debugging purposes, Caron can run with a mock simulation stored in a NumPy file (mock_sim.npy) and supports two practical workflows:
- Visualise an existing
.npybuffer (--no_sim) already populated, without constant injection - Inject frames from a
.npybuffer at a fixed FPS while visualising (--fake_injection)
- DearPyGui window with:
- Start / Stop buttons
- FPS slider (visualisation target FPS)
- Shared
Databuffer implemented as a thread-safedeque - Stability logic:
- Overflow protection: pause simulation if buffer grows too large
- Underflow protection: reduce visualisation FPS if buffer becomes too small
- Visualiser calibration phase to estimate maximum achievable visualisation FPS over a configurable time window
Editable install:
python -m pip install -e .Dependencies (also declared in pyproject.toml):
- NumPy
- Matplotlib
- DearPyGui
- Numba
- Jax
python Make_mock_sim.py --num_frames 512 --img_size 512This creates a 3D array with shape approximately (num_frames, img_size, img_size) and saves it as mock_sim.npy.
The details of the mock simulation can be seen by typing
python Make_mock_sim.py -hpython Caron.py --no_sim --sim_file ./mock_sim.npyIn --no_sim mode, the visualiser loads the .npy file and pre-fills the Data buffer, then consumes frames from it at a FPS x, settable with the command --viz_fps x. Note that the maximum achievable FPS is probably limited by the monitor refresh frequency (e.g. 60 Hz)
python Caron.py --fake_injection --fake_sim_fps 40 --viz_fps 60 --sim_file ../mock_sim.npyThis starts:
- a simulation injection thread (
Simulation.run_mock) pushing frames at--fake_sim_fps - a control loop thread monitoring measured rates and buffer state
- the visualiser consuming frames and applying
Datacommands (FPS adjustments, waiting on underflow/overflow)
All options can be seen by typing Caron.py -h.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--sim_size |
int | 512 | Linear grid size (used mainly for initial UI sizing) |
--n_frames |
int | 200 | Placeholder (real simulation not wired yet) |
--viz_fps |
float | 100 | Initial visualisation FPS target |
--calib_time |
float | 3 | Calibration duration (seconds of active “running” time) |
--calib_frames |
int | 50 | Minimum frames required during calibration |
--buffer_safe_max |
int | 300 | Buffer size above which simulation is paused (overflow) |
--sim_file |
str | ../mock_sim.npy |
Path to the mock .npy file |
--no_sim |
flag | off | Disable simulation thread; visualise .npy only |
--no_viz |
flag | off | Currently a placeholder (not wired) |
--fake_injection |
flag | off | Inject .npy frames at fixed FPS in a sim thread |
--fake_sim_fps |
int | 60 | Injection FPS for --fake_injection |
--ctrl_dt |
float | 0.2 | Control-loop tick interval (seconds) |
Important note on --no_viz: the argument exists, but the current Main.run() logic does not actually route into a “simulation-only” code path (the old run_sim_only() is commented out). Integrating the real simulation into the pipeline is coming up in the next few days.
Caron is split into four classes:
Main(Caron.py): parses args, creates objects, starts threads, runs the controller loop.Simulation(caron/simulation.py): produces frames and pushes them intoData. In--fake_injection, it loads a.npybuffer and injects at a fixed rate, while reacting to pause/unpause commands fromData.Visualization(caron/visualization.py): consumes frames fromDataat the required FPS and displays them via DearPyGui.Data(caron/data.py): shared buffer + control logic. It owns thedeque, synchronisation primitives, and the rules for overflow/underflow stabilisation.
- Trigger:
len(buffer) > buffer_safe_max - Action:
Datapauses the simulation (sim_paused=True) until the buffer is healthy again. - Resume: when buffer falls below
(buffer_safe_max - pillow).
- Trigger:
len(buffer) < buffer_safe_min(and simulation not finished). - Action:
Datareduces the visualisation target FPS to roughlysim_rate - viz_margin_fps. - Visualiser: applies the new FPS schedule.
Before normal operation, in the first calib_time seconds of active visualization, the visualiser runs a calibration phase where it updates as fast as possible in order to measure the maximum FPS achievable, and resizes the FPS slider max accordingly. Pausing the calibration does not affect this process.
- The real simulation (
Simulation.run) is not implemented yet (it is ready, but needs to be wired in). - “Simulation-only” mode (
--no_viz) is not wired inMain.run()and it will probably be removed. The simulation has been tested elsewhere.