Open-source projects

Software & Code

Tools I build for computer vision, hardware, and personal efficiency—from Python and Godot libraries to a desktop task and time tracker.

RapidTag

Rust · Python / PyO3 · Apache-2.0

Track markers. Keep the frame rate.

ArUco and AprilTag detection implemented in Rust and exposed to Python, without an OpenCV runtime dependency. Built for camera tracking, robotics, and experiments that need fast marker detection.

  • Single-frame and parallel batch detection
  • ChArUco and chessboard detection
  • PnP, RANSAC, and multi-marker rigid-body pose
  • Wheels for Windows, Linux, and macOS

Pre-1.0 · Active development; intended for research and prototyping.

pip install rapidtag
Python example
import rapidtag

# A grayscale or BGR uint8 NumPy image
corners, ids = rapidtag.detect_markers(
    image, "DICT_APRILTAG_36h11"
)

# Process a stereo pair or a recording across cores
results = rapidtag.detect_markers_batch(
    [left_image, right_image], "DICT_APRILTAG_36h11"
)

Detection throughput

Local measurement · 17 Oct 2026
Single frame 1.91×
RapidTag
488
OpenCV
256
Stereo pair 3.38×
RapidTag
763
OpenCV
226
Offline batch 6.45×
RapidTag
1,426
OpenCV
221

Frames / second · higher is better

1280 × 800 monochrome · AprilTag 36h11 · 200 frames per camera · 3 repetitions. Stereo and batch bars count individual frames, not pairs or batches.

2.05 ms / frame

Single-frame latency in this run

0.0000 px

Maximum corner difference on 400 matched markers

Benchmark conditions & measured timings

13th Gen Intel(R) Core(TM) i7-13700HX · 24 logical CPUs · Windows-11-10.0.26200-SP0. Python 3.13.5, RapidTag 0.1.8, OpenCV 5.0.0 (24 configured threads). 20 warmup frames; Median of per-repetition mean wall-clock times; disk I/O excluded.

OpenCV uses ArucoDetector defaults. Stereo and batch baselines call OpenCV sequentially; RapidTag uses its parallel batch API. These comparisons include the benefit of batch parallelism. Disk I/O is excluded.

Median of mean latency per call, over 3 repetitions
WorkloadRapidTagOpenCVRatio
Single frame2.05 ms3.91 ms1.91×
Stereo pair2.62 ms8.87 ms3.38×
Offline batch (400 frames)280.42 ms1809.92 ms6.45×

In 400 tested frames: 400 matched markers, 0 RapidTag-only and 0 OpenCV-only detections. This is one recording on one machine, using installed builds; speed and agreement depend on data, settings, and hardware.

Pose comparison on a longer stereo recording
Six plots showing OpenCV and RapidTag X, Y, and Z position estimates overlapping across two cameras.
Existing repository analysis, separate from the timing run above: the same OpenCV solvePnP pipeline receives corners from each detector. The repository reports a 0.00 mm median position difference on both cameras across approximately 1,700 frames. Analysis script ↗

GdSerial

Rust · Godot 4.4+ / GDExtension · MIT

Bring real hardware into Godot.

Connect Arduino, ESP32, sensors, and serial devices directly to a Godot application. Use a familiar PySerial-style API for simple tasks, or a multi-port manager with background readers and Godot signals.

  • Discover ports and configure serial connections
  • Read and write binary data or text
  • Raw, line-buffered, and custom-delimiter receive modes
  • Asynchronous multi-port reads with disconnect signals
GDScript example
extends Node

var serial := GdSerialManager.new()

func _ready():
    serial.data_received.connect(_on_data)
    serial.open("COM3", 115200, 1000,
        GdSerialManager.MODE_LINE_BUFFERED)

func _process(_delta):
    serial.poll_events()  # Dispatch queued signals

func _on_data(port: String, data: PackedByteArray):
    print(port, ": ", data.get_string_from_utf8())

func _exit_tree():
    serial.close("COM3")

Choose your device's port and baud rate. Line-buffered mode waits for a newline; call poll_events() every frame to dispatch received data.

From a sensor to a scene

A serial device sends data to a Rust background reader, then through a channel to Godot's main thread, where poll_events dispatches signals.
The reader thread receives and buffers data. Your scene calls poll_events() to emit data_received and handle disconnections. Implementation ↗

Platform support

Windows
x64, ARM64
Linux
x64, ARM64
macOS
Intel, Apple Silicon

Serial wire-time calculator

Theoretical · 8N1 framing

Explore how baud rate and message size affect the time on the wire.

5.56 ms minimum wire time / message

11,520 bytes/s maximum wire payload rate

1 start + 8 data + 1 stop bit = 10 bits per byte. Time = bytes × 10 ÷ baud. Excludes USB, driver, buffering, protocol, and Godot frame delays. This estimates the wire limit; it is not a measured GdSerial benchmark. Device support determines usable baud rates.

Todoz

Rust · Tauri / Svelte · SQLite

Why I built Todoz

I built Todoz so I could track my own efficiency and get a clearer picture of where my time goes. It gives me one place to keep my tasks, time what I'm working on, and look back at how much time I've spent on each project.

I can plan tasks in a calendar, pause and resume timers, and review daily summaries. There are optional break reminders and app-activity tracking, too. It's a personal project I'm still working on.

The idea behind it

Todoz workflow: organize projects and tasks, track time with task timers, then review daily and project time summaries.
The idea is simple: plan what I want to do, track the time I spend, then look back at the day.

Where the time goes

Illustrative data · 4h 30m total
Research2h 20m
Code1h 40m
Admin30m
A small example of how time can be split across projects. These are sample numbers, rather than a record of my own day.
Found something useful? Explore the code, open an issue, or contribute on GitHub ↗.