A modern, browser-based electronic lab notebook for in-vivo electrophysiology experiments.
# 1. Install dependencies
pip install -r requirements.txt
# 2. Import your existing Excel data (one-time, per user)
python import_data.py Master_DB__3_.xlsx RM
# 3. Start the server
python server.pyThen open http://localhost:8000 in your browser.
eln/
├── server.py # Flask entry point — start here
├── config.py # All configurable settings (paths, users, schemas)
├── import_data.py # One-time Excel → SQLite/JSON converter
├── requirements.txt
│
├── models/ # Data layer (framework-agnostic)
│ ├── database.py # SQLite CRUD with FK enforcement
│ ├── notebook.py # JSON notebook CRUD (session → pipette → sweep)
│ └── timeline.py # JSON timeline CRUD (task → subtask)
│
├── routes_flask/ # Backup logic
│ └── backup.py # Rolling backup creation & listing
│
├── static/ # Frontend (vanilla JS, no build step)
│ ├── index.html # Single-page app shell
│ ├── css/main.css # Dark theme, all styles
│ └── js/
│ ├── utils.js # API helper, modals, toasts, form generation
│ ├── app.js # Login, tabs, initialization
│ ├── notebook.js # Collapsible tree view + CRUD forms
│ ├── database.js # Table browser + relational editor
│ └── timeline.js # Gantt chart renderer + CRUD
│
└── data/ # Created at runtime
└── users/
└── <username>/
├── databases/ # experiments.db (SQLite)
├── notebooks/ # <project>.json per notebook
├── timelines/ # <timeline>.json per timeline
└── backups/ # Rolling backups (up to 3)
Collapsible tree view: Session → Pipette → Sweep.
- Session: Date, Animal ID, Sex, Day, Overall Notes + custom columns
- Pipette: Pipette #, Rp, Depth, Ri, Bridge, Vm
- Sweep: Sweep #, Test type, Sweep Notes + custom columns
Projects can define custom columns (e.g., NDNF V1 has gen, inj_date,
opto_protocol). Create new projects with any column schema.
Stored as JSON files — human-readable, editable, git-friendly.
Relational browser for the experiment database hierarchy:
Animals → Sessions → Cells → Recordings
└── Plasticity Experiments (standalone)
- Foreign key enforcement: Can't add a Session without a valid Animal, can't add a Cell without a valid Session, etc.
- Parent selector: When adding a child row, pick from a dropdown of valid parent IDs.
- Cascade protection: Can't delete a parent that has children — remove children first.
Stored as SQLite with full relational integrity.
Gantt chart for experiment scheduling:
- Tasks = top-level experiments (e.g., "ChRmine-oScarlet for patching")
- Subtasks = steps with dates and durations (Injections, Expression, Patching…)
- Workweek-aware: Mark subtasks as workweek-dependent (skip weekends) or background processes (calendar days)
- Today line: Visual marker for the current date
- Weekend shading: Grey stripes on Sat/Sun
Stored as JSON.
Edit config.py to customize:
| Setting | Default | Description |
|---|---|---|
LOCAL_DATA_DIR |
./data |
Primary save location |
SERVER_DATA_DIR |
None |
Secondary save (set to network path) |
MAX_BACKUPS |
3 |
Rolling backup count per user |
USERS |
["RM", "WL", "CY"] |
Dropdown list at login |
HOST |
0.0.0.0 |
Server bind address |
PORT |
8000 |
Server port |
Set SERVER_DATA_DIR to a network drive path:
# config.py
SERVER_DATA_DIR = Path("//foodissues.mit.edu/rmojica/eln_data")
# or
SERVER_DATA_DIR = Path("/mnt/server/eln_data")Every save writes to both locations automatically.
Users can be added in config.py or by typing a new name at the login
screen. New users get their own independent data directory.
- Created automatically on every login (app launch)
- Also available via the 💾 Backup button in the top bar
- Rolling window: keeps the 3 most recent (configurable)
- Backs up: SQLite databases + JSON notebooks + JSON timelines
- Saved to both local and server paths
python import_data.py <path_to_xlsx> <username>This is a one-time operation per user. It converts:
WT V1,NDNF V1,WT RSCsheets → JSON notebook filesAnimals,Sessions,Cells,Recordings→ SQLite tablesPlast. Grat. DB→ SQLiteplasticity_experimentstableMS Experiments Timeline→ JSON timeline
Re-running will skip duplicates (uses INSERT OR IGNORE).
Click + New in the Notebook sidebar. Specify custom columns for sessions and sweeps.
- Add the schema to
DB_SCHEMASinconfig.py - Add FK relationship to
FK_RELATIONSHIPSif applicable - Add table name to
DB_HIERARCHY - Add the
CREATE TABLEstatement inmodels/database.py → init_db() - The UI will pick it up automatically
All frontend code is in static/ — vanilla HTML/CSS/JS with no build
step. Edit and refresh.