Small-lab system for tracking Drosophila vials with QR-coded labels, printer-driven workflow, lineage across flips and crosses, and an auditable history. Runs single-user out of the box and scales to a shared bench tablet with one SQLite file — not a multi-user SaaS.
- Create vials with a compact QR label printed on a Brother QL-820NWB (17 × 54 mm DK-11204), one click from genotype + owner + org-unit.
- Scan vials via USB webcam in the Scan tab; decoded payloads fetch the full detail panel (genotype notation, audit trail, lineage).
- Flip, multiply, decommission, reactivate vials with audit-logged
state transitions.
Multiply…splits one parent into 2–12 children in one click;Reactivateundoes accidental decommissions (with a warning when active flip-descendants would create a forked lineage). - Flip all active vials of a genotype in one click (Genotypes tab →
Flip all active…). Every currently-active vial of the chosen strain is decommissioned and gets one successor + label in a single transaction; the last-used genotype is pre-selected so a repeat cycle (e.g. weekly dark-flies flip) is one motion. - Auditory scan confirmation: a short chirp plays on every successful
QR decode (PipeWire/PulseAudio/ALSA player ladder; mute via
DDB_SCAN_SOUND=0). - Live font-size adjuster: scale every label and dialog in the GUI on
the fly. Settings tab → "Font size" slider, or keyboard shortcuts
Ctrl+=(bigger),Ctrl+-(smaller),Ctrl+0(reset). Persists toDDB_GUI_FONT_SCALE, clamped to 0.7×–2.0×. - Import genotypes by stock-center ID (Bloomington, Vienna/VDRC, Kyoto/DGGR, NIG-Fly, KDRC, FlyORF, NDSSC) from a locally-cached FlyBase catalog — no per-import web calls.
- Reports tab: search/filter by genotype, owner, org-unit, donor, generation; export lineage as CSV.
- Biosafety PDF report: one-click PDF for institutional filings.
Snapshot counts (active / decommissioned vials, in-stock / dropped
genotypes), in-period activity (creates, flips, decommissions,
reactivations), per-owner active-vial breakdown, signature block,
and a full active-genotype annex. CLI takes
--annual YYYY,--from,--to,--org-unit,--owner. - Auditable: every mutation writes an
AuditEventwith actor, action, and before/after payload. - Typer CLI mirrors the GUI for scripted workflows (
ddb vial create,ddb vial flip,ddb printer status, etc.).
Two paths — pick whichever fits.
A. Per-user install (no sudo — recommended on shared tablets). One line clones the repo, creates the conda env, installs the package, and drops a launcher on your Desktop:
curl -sSL \
https://raw.githubusercontent.com/zerotonin/drosodb/main/scripts/install_user.sh \
| bashYou still need conda installed under your account first; the script prints the exact miniforge one-liner if it can't find one. Re-running the script updates the env and refreshes the launcher.
B. Manual install if you're doing something custom:
# one-time
conda env create -f environment.yml
conda activate ddb
pip install -e ".[gui,hardware,dev]"
# initialise the SQLite DB
ddb init-db
# launch the GUI
ddb guiRun the tests to confirm the install:
pytest # ~238 tests, ~10 sDDB can run on a shared bench tablet where every biologist has their
own Linux account. The DB, label PNGs, and everything else live in a
world-reachable directory (/srv/ddb/), the SQLite file uses WAL mode
so two people can use the GUI at the same time without lock storms,
and every mutation is audited to the OS user who made it — no login
prompt, no passwords to manage.
Setup on the tablet (once):
# 1. Create the shared directory + `ddb` group and set the perms.
sudo bash scripts/setup_shared_sqlite.sh
# 2. Add each biologist to the `ddb` group.
sudo usermod -aG ddb alice
sudo usermod -aG ddb bob
# ... they need to log out and back in.
# 3. Point DDB at the shared paths.
cp local_paths.template.json local_paths.json
# then edit local_paths.json → "active_profile": "shared_tablet"
# 4. Run the migrations against the shared file.
DDB_DATABASE_URL=sqlite:////srv/ddb/ddb.sqlite3 alembic upgrade headFrom that point on, launching ddb gui from any of the tablet accounts
just works. The bottom-right of the status bar shows Signed in as: <username> · profile: shared_tablet so nobody wonders whose audit
trail they're about to write into. First launch for a new username
auto-creates a DDB User row (username = Linux username, full name
blank — anyone can fill it in from the Settings tab).
Path resolution — everywhere DDB reads the DB URL or data directory, the order is:
- Env var (
DDB_DATABASE_URL,DDB_DATA_DIR) — one-off override. - Active profile in
local_paths.json— per-machine. - In-repo default (
ddb.sqlite3next to the checkout,./data/for labels) — the single-user case.
local_paths.json is gitignored; the committed template
local_paths.template.json documents both the local and
shared_tablet profile shapes.
Not (yet) supported: a shared-account mode with per-user passwords. The plan when a lab wants that is a Settings toggle that swaps the OS-user resolver for a startup login dialog. Filed as a follow-up.
conda-forge downloads through the campus link comfortably handle one
install at a time — a second tablet user starting install_user.sh
while user #1 is still pulling packages drops both streams to
~100 KB/s. To skip the conda download entirely on user #2 and beyond,
snapshot a working env once and let subsequent installs untar it:
# One-time, from any account that has a working `ddb` conda env:
bash scripts/pack_env.sh
# → produces /srv/ddb/env-packs/ddb-env.tar.gz (~200 MB)From that point on, install_user.sh looks for a pack in this order:
$DDB_ENV_PACKenv-var override./srv/ddb/env-packs/ddb-env.tar.gz(created by the setup script).~/ddb-env.tar.gz(per-user fallback if you copied one by hand).- Fresh conda/mamba install from the network (previous behaviour).
When a pack is found the env is untarred + conda-unpack-ed in seconds
— no channel indexes, no package downloads. Ship a new pack whenever
environment.yml changes.
If the tablet has a Bluetooth label printer, every user needs two
extras on top of what install_user.sh provisions: bluetooth-group
membership (admin, one-off) and the printer .env (self-service or
admin, per user).
Admin (once per new user):
sudo usermod -aG bluetooth <user> # takes effect on their next loginThen either path works for the .env — pick whichever fits.
(a) Self-service — each biologist runs this from their own account, no sudo needed:
bash ~/PyProject/drosodb/scripts/setup_my_printer.sh
# writes .env into your clone, verifies pydantic sees it, reminds you
# to close+reopen the GUI. Idempotent — safe to re-run.(b) Admin batch — for provisioning several users in one go:
sudo bash scripts/write_user_printer_env.sh <user1> <user2> …
# with no args, defaults to the current tablet's user list.Diagnostic when something still doesn't work:
sudo bash scripts/diagnose_user_printer.sh
# for each user: shows their repo clone location, the .env content,
# what pydantic actually loads in a fresh Python, and whether they're
# in the bluetooth group. Names the specific fix per row.The most common gotcha: pydantic reads .env once at process
start, so a GUI that was already running keeps its old settings
until it's fully closed and reopened.
Longer-term this per-user duplication should move to a shared file
in /etc/ddb/, the way the backup config already does — filed as a
follow-up.
| Device | Model | Transport | Notes |
|---|---|---|---|
| Label printer | Brother QL-820NWB | Bluetooth RFCOMM (ch 1) | Also: TCP :9100 (network) or file dump |
| QR camera | any V4L2 webcam | OpenCV (libv4l) | zxing-cpp primary, OpenCV fallback |
| OS | Linux 6.x (tested) | BlueZ 5.72+ | macOS / Windows untested |
Everything is controlled via .env in the project root (or DDB_*
environment variables). The most common knobs:
# Printer
DDB_PRINTER_ENABLED=1
DDB_PRINTER_BACKEND=bluetooth # or "network" or "file"
DDB_PRINTER_BLUETOOTH_MAC=AC:4D:16:EB:B6:44
DDB_PRINTER_AUTO_PRINT=1
# Scanner / camera defaults
DDB_DEFAULT_CAMERA_ROLE=back # or "front"
DDB_SCAN_SOUND=1 # audible 1-Up chirp on every QR decode
# GUI tweaks
DDB_GUI_FONT_SCALE=1.00 # 0.7-2.0; live via Ctrl+=/Ctrl+-/Ctrl+0
# FlyBase genotype-import catalog
DDB_FLYBASE_ENABLED=1
DDB_FLYBASE_REFRESH_MODE=monthly # "manual", "weekly", "monthly"
# Stock-keeper defaults for the New-Vial dialog
DDB_DEFAULT_ORG_UNIT=Geurten lab stock
DDB_DEFAULT_OWNER_USERNAME=stockkeeperSee src/ddb/config.py for the full set — every field is overridable
via DDB_<UPPERCASE_NAME>.
assets/
ddb_logo.png app icon (also used by the .desktop launcher, README, docs)
src/ddb/
models/ SQLModel ORM (Vial, Genotype, Donor, OrgUnit, User, AuditEvent, …)
workflows/ transaction-safe mutations with audit (create/flip/decommission,
drop/reactivate genotype)
printing/ raster pipeline + backends (bluetooth / network / file) +
bt_recovery helpers + _sidecars scripts
scanner/ QR decode (zxing + OpenCV fallback), payload parser,
DB lookup by print-code or FBst
camera/ V4L2 enumeration, role assignment, preview capture
biosec/ biosafety PDF reports — data gatherer (pure SQL) +
reportlab renderer; entry point: `ddb report biosec`
flybase/ local FlyBase stocks-catalog cache (download, lookup,
genotype parser, refresh scheduler, collection→donor mapping)
gui/ PySide6: Scan / Reports / Genotypes / Settings tabs,
dialogs (CreateVial, CreateGenotype, ImportGenotype,
PrinterReconnect, FlybaseDownload), shared widgets
(GenotypeForm, CameraWidget, PrinterStatusLight)
importers/ FlyStockTable CSV importer
cli.py typer CLI entry point
config.py pydantic-settings (env-driven)
db.py engine + session factory
labels.py 17×54mm label rendering with QR
qr.py compact "DDB:<code>" payload + PNG builder
lineage.py vial lineage graph + CSV export
reports.py search + detail queries
tests/ ~238 unit + integration tests
alembic/ SQLite migrations
docs/ design docs + operations walkthrough
ddb init-db # create tables (Alembic)
ddb gui # launch the PySide6 desktop app
ddb scan --role back # stand-alone scanner loop
ddb seed-demo # populate a demo DB
ddb vial create --genotype-id 12 --owner-id 3
ddb vial search --genotype rutabaga --active
ddb vial show <PRINT_CODE>
ddb vial flip <PRINT_CODE> # creates a child vial, audits both
ddb vial decommission <PRINT_CODE> --reason contaminated
ddb vial lineage <PRINT_CODE> --csv lineage.csv
ddb import-genotypes path/to/FlyStockTable.csv
ddb printer status # ESC i S probe via configured backend
ddb printer print-png path/to/label.png
ddb report biosec report.pdf # rolling 3 months, whole DB
ddb report biosec report.pdf --annual 2025 # full calendar year
ddb report biosec lab_a.pdf --org-unit "Lab A" # one lab onlyRun ddb --help or ddb <subcommand> --help for the full set.
docs/droso_db_project_extended.md— full design spec.docs/operations.md— manual reprint + backup walkthrough.docs/index.md— docs landing page (published at zerotonin.github.io/drosodb).
