Download backend/digitization_runner.py from nsrek/ECGLight: direct link, hf CLI and curl.
- Browser
- Download file 5.85 kB
-
https://huggingface.co/nsrek/ECGLight/resolve/refs%2Fpr%2F1/backend/digitization_runner.py
- Command line
-
hf download hf://nsrek/ECGLight@refs/pr/1/backend/digitization_runner.py
-
curl -L -o digitization_runner.py https://huggingface.co/nsrek/ECGLight/resolve/refs%2Fpr%2F1/backend/digitization_runner.py
5.85 kB
| # backend/digitization_runner.py | |
| """ | |
| Backend adapter for ECG Digitization. | |
| Wraps the ECGImage pipeline from digitization.py and manages model caching. | |
| """ | |
| import os | |
| import sys | |
| import pandas as pd | |
| import numpy as np | |
| import streamlit as st | |
| import torch | |
| # Add parent directory to path to ensure digitization and other local modules are importable | |
| sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) | |
| try: | |
| from digitization import ECGImage | |
| from ultralytics import YOLO | |
| except ImportError as e: | |
| # We will log the error but keep running so the dashboard can load and display elegant warnings | |
| pass | |
| class ModelPathNotSetError(Exception): | |
| """Raised when one or more YOLO model paths are not set in the configuration.""" | |
| pass | |
| class ModelFileNotFoundError(Exception): | |
| """Raised when a specified YOLO model file does not exist on disk.""" | |
| pass | |
| def check_models_configured(config): | |
| """ | |
| Verify that all YOLO model paths are set in the configuration. | |
| """ | |
| missing_configs = [] | |
| if not config.YOLO_BOX_MODEL_PATH: | |
| missing_configs.append("YOLO Box Model (YOLO_BOX_MODEL_PATH)") | |
| if not config.YOLO_LEAD_NAME_MODEL_PATH: | |
| missing_configs.append("YOLO Lead Name Model (YOLO_LEAD_NAME_MODEL_PATH)") | |
| if not config.YOLO_PULSE_MODEL_PATH: | |
| missing_configs.append("YOLO Pulse Model (YOLO_PULSE_MODEL_PATH)") | |
| if not config.YOLO_SEGMENTATION_MODEL_PATH: | |
| missing_configs.append("YOLO Segmentation Model (YOLO_SEGMENTATION_MODEL_PATH)") | |
| if missing_configs: | |
| raise ModelPathNotSetError( | |
| f"The following YOLO model paths are not set in config.py:\n" + | |
| "\n".join([f"- {m}" for m in missing_configs]) + | |
| "\n\nPlease configure these paths in config.py before running digitization." | |
| ) | |
| def check_model_files_exist(config): | |
| """ | |
| Verify that the configured model files actually exist on disk. | |
| """ | |
| missing_files = [] | |
| for name, path in [ | |
| ("Box Model", config.YOLO_BOX_MODEL_PATH), | |
| ("Lead Name Model", config.YOLO_LEAD_NAME_MODEL_PATH), | |
| ("Pulse Model", config.YOLO_PULSE_MODEL_PATH), | |
| ("Segmentation Model", config.YOLO_SEGMENTATION_MODEL_PATH) | |
| ]: | |
| if path and not os.path.exists(path): | |
| missing_files.append(f"{name} at path: '{path}'") | |
| if missing_files: | |
| raise ModelFileNotFoundError( | |
| f"The following model files were not found on disk:\n" + | |
| "\n".join([f"- {f}" for f in missing_files]) + | |
| "\n\nPlease verify that the files are downloaded and located at these paths." | |
| ) | |
| def load_yolo_models(box_path, lead_name_path, pulse_path): | |
| """ | |
| Load YOLO models once and cache them in Streamlit memory. | |
| Returns a dict containing the loaded model objects. | |
| """ | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| # Load each model | |
| box_model = YOLO(box_path) | |
| lead_name_model = YOLO(lead_name_path) | |
| pulse_model = YOLO(pulse_path) | |
| # Move to GPU if available | |
| box_model.to(device) | |
| lead_name_model.to(device) | |
| pulse_model.to(device) | |
| return { | |
| "box_model": box_model, | |
| "lead_name_model": lead_name_model, | |
| "pulse_model": pulse_model, | |
| "device": device | |
| } | |
| def run_digitization_pipeline(image_path, config): | |
| """ | |
| Runs the full digitization pipeline on a single ECG image. | |
| Parameters | |
| ---------- | |
| image_path : str | |
| Path to the uploaded image file. | |
| config : module | |
| The config module containing paths and settings. | |
| Returns | |
| ------- | |
| df : pandas.DataFrame | |
| Digitized ECG signals with lead names as columns and timesteps as rows. | |
| ecg_instance : ECGImage | |
| The raw ECGImage instance for accessing signal_grid, masks, or metadata. | |
| """ | |
| # 1. Validation | |
| check_models_configured(config) | |
| check_model_files_exist(config) | |
| # 2. Load models (cached via st.cache_resource) | |
| models = load_yolo_models( | |
| config.YOLO_BOX_MODEL_PATH, | |
| config.YOLO_LEAD_NAME_MODEL_PATH, | |
| config.YOLO_PULSE_MODEL_PATH | |
| ) | |
| # 3. Instantiate ECGImage | |
| ecg = ECGImage( | |
| box_model=models["box_model"], | |
| segmentation_model=config.YOLO_SEGMENTATION_MODEL_PATH, | |
| lead_name_model=models["lead_name_model"], | |
| pulse_model=models["pulse_model"], | |
| image_path=image_path | |
| ) | |
| # 4. Run the pipeline (without Linux-specific SIGALRM timeouts) | |
| ecg.run_full_pipeline() | |
| # 5. Extract DataFrame from signal_grid | |
| df = extract_dataframe_from_ecg(ecg) | |
| # 6. Clear GPU cache if needed | |
| if torch.cuda.is_available(): | |
| try: | |
| torch.cuda.synchronize() | |
| torch.cuda.empty_cache() | |
| except Exception: | |
| pass | |
| return df, ecg | |
| def extract_dataframe_from_ecg(ecg_instance): | |
| """ | |
| Extracts signal data from an ECGImage instance and converts it to a pandas DataFrame. | |
| """ | |
| signals, lead_names, max_length = [], [], 0 | |
| for row in ecg_instance.signal_grid: | |
| for cell in row: | |
| if 'signal' in cell and 'lead' in cell: | |
| sig = np.asarray(cell['signal'], dtype=float) | |
| signals.append(sig) | |
| lead_names.append(cell['lead']) | |
| max_length = max(max_length, len(sig)) | |
| if not signals: | |
| raise ValueError("No valid lead signals were extracted from the ECG image.") | |
| # Pad shorter signals with NaN to make them uniform length | |
| padded = [np.pad(s, (0, max_length - len(s)), mode='constant', constant_values=np.nan) for s in signals] | |
| # Build DataFrame | |
| df = pd.DataFrame(np.vstack(padded).T, columns=lead_names) | |
| return df | |