Engine Development Guide
This guide covers everything you need to build a VentOCR Engine — a pluggable OCR module that analyzes ventilator display video frames and outputs structured data. Engines are developed outside the VentOCR environment and loaded dynamically at runtime.
1. What an Engine Does
An Engine processes ventilator display video frames via OCR and returns structured DataFrames
containing per-frame measurements (e.g., VTE, EdiPeak) and breath boundaries (BreathMark).
VentOCR loads engines dynamically via
importlib.import_module(f"Engine.{engine_name}.core").
2. Execution Flow
Video uploaded ↓ Extract frames (Main.py saves color JPEGs to disk) ↓ initial_ocr(frames, ...) ← color frames via generator, one at a time ↓ CSV saved per segment ↓ post_ocr(df, ...) ← combined DataFrame from all segments ↓ extract_waves(...) ← optional: per-breath waveform CSVs ↓ Results archived
3. Directory Structure
Engine/<engine_key>/
__init__.py # empty
core.py # ENGINE_VERSION, VENTOCR_TESTED + functions
Settings.py # ROI coordinates, reference paths
schema.py # ENGINE_SETTINGS_SCHEMA
engine_options.json # default settings
ExtractWaves.py # waveform extraction (if extract_waves implemented)
layout.jpg # ventilator layout reference
sample_frame.jpg # reference for abnormal detection
sample.mp4 # REQUIRED test video (3-5 min)
test_spec.json # REQUIRED accuracy specification
test.py # REQUIRED test script
expected_output/ # REQUIRED ground truth CSVs
Frames.csv
Breaths.csv
BreathCSVs/ # if extract_waves implemented
README.md
CHANGELOG.md
CLAUDE.md # definition file for Claude-assisted development
4. Version Management
ENGINE_VERSION = "1.0.0" # engine release version (managed by developer) VENTOCR_TESTED = "0.5.5" # VentOCR version this engine was tested against
ENGINE_VERSION — your engine's own semantic version. Update this with each release.
VENTOCR_TESTED — the VentOCR version you verified compatibility against. Update when you test with a newer VentOCR release.
Maintain a CHANGELOG.md in your engine directory to track changes.
5. Frame Input Specification
frames parameter is a generator of color frames (BGR, numpy array).
Each frame is yielded once and discarded. Do NOT call list(frames) —
a 5-hour recording at 4K would consume several terabytes of memory.
Sequential Processing
for frame_count, color_frame in enumerate(frames):
# color_frame: numpy array (H, W, 3), BGR format
gray = np.dot(color_frame[..., :3], [0.114, 0.587, 0.299]).astype(np.uint8)
# ... process frame
Random Access (Past Frames)
If your algorithm needs to access a previous frame (e.g., midpoint between two detected events),
read it from disk via frames_dir:
frame = cv2.imread(os.path.join(frames_dir, f"frame_{index:08d}.jpg"))
Main.py saves every frame as a JPEG before yielding it to the generator, so all frames are available on disk for random access.
6. initial_ocr() REQUIRED
def initial_ocr(frames, total_frames, subfolder, settings={},
progress_callback=None, frames_dir=None) -> pd.DataFrame
| Parameter | Type | Description |
|---|---|---|
| frames | Generator[np.ndarray] | Color frames (BGR). One at a time. Do not list(). |
| total_frames | int | Total frame count |
| subfolder | str | Segment identifier |
| settings | dict | From engine_options.json |
| progress_callback | callable or None | callback(current, total, message) |
| frames_dir | str | Directory with saved JPEGs for random access |
Required Output Columns
| Column | Type | Description |
|---|---|---|
| Frame | int | 0-indexed frame number |
| BreathMark | int (0 or 1) | 1 = breath boundary detected |
Add engine-specific value columns as needed (e.g., VTE, EdiPeak, VTE_raw, VTE_conf).
7. post_ocr() REQUIRED
def post_ocr(df, Analyze_Id, user_ID, settings={}) -> (df_frames, df_breaths)
| Parameter | Type | Description |
|---|---|---|
| df | pd.DataFrame | Output from initial_ocr() |
| Analyze_Id | int | Analysis ID |
| user_ID | str | User identifier |
| settings | dict | From engine_options.json |
Columns That Must Be Added to df_frames
| Column | Type | Description |
|---|---|---|
| BreathNumber | int | 1-indexed, cumsum of BreathMark |
| Time | str | "HH:MM:SS:CC" format (CC = centiseconds) |
| Alert | str | Empty string or alert reason |
| Abnormal | int (0 or 1) | 1 = abnormal frame |
df_breaths: One row per BreathNumber (typically the first frame of each breath).
8. extract_waves() OPTIONAL
def extract_waves(df_frames, df_breaths, frames_root, output_dir, user_ID,
settings={}, overwrite=False, archive_dir=None) -> (int, pd.DataFrame)
VentOCR checks hasattr(Engine, 'extract_waves') before calling.
Implement this if your engine extracts waveform data (pressure, flow, volume, etc.).
Per-Breath CSV Format
X,Pressure,Flow,Volume 1,12.3,5.6,120.5 2,12.5,5.8,121.0 ...
If archive_dir is specified, write a
fields.txt metadata file listing wave names and output fields.
9. Settings System
schema.py
Defines UI form fields for engine settings. Supported types:
int,
float,
bool,
list,
multiselect
engine_options.json
Default values matching the schema structure.
Loaded by VentOCR and passed as the settings dict.
Settings.py
Static configuration: ROI coordinates, reference image paths, detection parameters.
Use ROI_BY_RESOLUTION for multi-resolution support.
10. Test Specification
The test_spec.json file declares what your engine outputs
and the accuracy threshold for each output. This is verified at registration time.
{
"sample_video": "sample.mp4",
"sample_duration": "3-5 min",
"outputs": {
"VTE": {
"type": "numeric",
"metric": "match_rate",
"threshold": 0.90,
"tolerance": 5.0,
"unit": "mL",
"description": "VTE values within ±5.0 mL of expected"
},
"BreathMark": {
"type": "binary",
"metric": "count_tolerance",
"threshold": 1,
"description": "Total breath count within ±1 of expected"
}
},
"waves": ["Pressure", "Flow", "Volume"]
}
Available Metrics
| Metric | Description | Threshold Meaning |
|---|---|---|
| match_rate | Fraction of values within tolerance | Minimum rate (0.0–1.0) |
| count_tolerance | Absolute difference in total count | Maximum allowed difference |
| rmse | Root mean squared error | Maximum allowed RMSE |
| correlation | Pearson correlation coefficient | Minimum correlation (0.0–1.0) |
Expected Output
Place ground truth CSVs in expected_output/.
Run your engine against sample.mp4 with verified results,
then save the output as the expected baseline.
11. Testing & Verification
CLI Testing (During Development)
cd Engine/your_engine/ python test.py
This runs initial_ocr → post_ocr → extract_waves against sample.mp4, compares output to expected_output/, and reports PASS/FAIL per metric.
Web UI Verification (At Registration)
When registering an engine through VentOCR, the same test is executed automatically. Engines that fail to meet their declared thresholds cannot be registered.
12. Database Registration
-- Engine table
INSERT INTO Engine (Engine_Key, Display_Name, Developer_Name, Ventilator_Family,
Layout_Description, Sort_Order, Is_Active)
VALUES ('your_engine', 'Your Engine Name', 'Developer', 'Ventilator Family',
'Layout description', 0, 1);
-- Engine-Ventilator mapping
INSERT INTO Engine_Ventilator_Map (Engine_Key, Ventilator_ID, Sort_Order, Is_Active)
VALUES ('your_engine', 1, 0, 1);
migrate_db.py.
Contact the VentOCR team for Ventilator_ID assignments.
13. Get Started
Download the scaffold template to start building your engine. It includes all required files with stub implementations, the test framework, and a CLAUDE.md definition file for AI-assisted development.
Download Engine Scaffold (.zip)