{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":97984,"databundleVersionId":14096757,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:54:29.698206Z","iopub.execute_input":"2025-12-07T14:54:29.698773Z","iopub.status.idle":"2025-12-07T14:54:29.704049Z","shell.execute_reply.started":"2025-12-07T14:54:29.698748Z","shell.execute_reply":"2025-12-07T14:54:29.702858Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import AutoMinorLocator\nimport os\n\n# ==========================================\n# --- MANUAL CONTROL PANEL ---\n# ==========================================\n# Adjust these to fix overlaps or messy signals manually.\n\n# 1. MOVE LEADS UP/DOWN (Positive = UP, Negative = DOWN)\nMANUAL_Y_OFFSETS = {\n    'I': 1.0,   'aVR': 0.450,  'V1': 0.10,  'V4': 0.20,\n    'II': 0.30,  'aVL': 0,  'V2': -0.40,  'V5': 0.350,\n    'III': 0.0, 'aVF': -0.10,  'V3': -0.9,  'V6': 0.0\n}\n\n# 2. SIGNAL SIZE (Gain)\n# 1.0 = Standard. Increase to 1.5 for taller waves. Decrease to 0.5 to stop overlapping.\nMANUAL_GAINS = {\n    'I': 1.2,   'aVR': .5,  'V1': 1.2,  'V4': 1.2,\n    'II': 1.2,  'aVL': .5,  'V2': 1.2,  'V5': 1.2,\n    'III': 1.2, 'aVF': .5,  'V3': 1.2,  'V6': 1.2,\n    'Rhythm': 1.2 \n}\n\n# ==========================================\n# --- HELPER FUNCTIONS ---\n# ==========================================\n\ndef load_and_fix_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    \n    # 1. Load valid data\n    for col in df_raw.columns:\n        clean_col = col.strip() \n        valid_data = df_raw[col].dropna().values\n        if len(valid_data) > 0:\n            signals[clean_col] = valid_data\n\n    # 2. Calculate Missing Leads (I, II, III relationship)\n    if 'II' in signals and 'I' in signals and 'III' not in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        signals['III'] = signals['II'][:n] - signals['I'][:n]\n        \n    # 3. Calculate Missing Augmented Leads (aVR, aVL, aVF)\n    # Using Lead I and II (Standard Goldberger equations)\n    if 'I' in signals and 'II' in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        sI = signals['I'][:n]\n        sII = signals['II'][:n]\n        \n        # If missing or flat-lined, calculate them\n        for lead in ['aVR', 'aVL', 'aVF']:\n            is_missing = lead not in signals\n            is_flat = not is_missing and np.std(signals[lead]) < 0.05\n            \n            if is_missing or is_flat:\n                if lead == 'aVR': signals['aVR'] = -(sI + sII) / 2\n                if lead == 'aVL': signals['aVL'] = sI - (sII / 2)\n                if lead == 'aVF': signals['aVF'] = sII - (sI / 2)\n\n    return signals\n\ndef get_sampling_rate(signals):\n    \"\"\"\n    Auto-detects sampling rate based on Lead II length.\n    Dataset Description: Lead II is always 10 seconds.\n    \"\"\"\n    if 'II' in signals:\n        count = len(signals['II'])\n        fs = count / 10.0 # Infer frequency\n        print(f\"Detected Sampling Rate: {fs:.1f} Hz (based on {count} samples in Lead II)\")\n        return fs\n    return 500.0 # Default fallback\n\ndef normalize_and_center(signal):\n    if len(signal) == 0: return signal\n    sig_min = np.min(signal)\n    sig_max = np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    return (signal - sig_min) / (sig_max - sig_min)\n\n# ==========================================\n# --- PLOTTING FUNCTION ---\n# ==========================================\n\ndef plot_ecg_final(signals, output_path=\"ecg_final.png\"):\n    \n    # 1. Auto-detect Sample Rate\n    sample_rate = get_sampling_rate(signals)\n    \n    # 2. Setup Figure (A4 Landscape Ratio)\n    fig = plt.figure(figsize=(16, 10))\n    ax = fig.add_subplot(111)\n    \n    # 3. Grid Setup (Standard Medical 3x4 + 1)\n    ax.set_xlim(0, 10)\n    ax.set_ylim(0, 12)\n    \n    # Major Grid (0.2s) - Red\n    ax.xaxis.set_major_locator(plt.MultipleLocator(0.2))\n    ax.yaxis.set_major_locator(plt.MultipleLocator(1.0))\n    ax.grid(which='major', color='#ff5050', linestyle='-', linewidth=0.8, alpha=0.6)\n\n    # Minor Grid (0.04s) - Pink\n    ax.xaxis.set_minor_locator(AutoMinorLocator(5))\n    ax.yaxis.set_minor_locator(AutoMinorLocator(5))\n    ax.grid(which='minor', color='#ff5050', linestyle='-', linewidth=0.3, alpha=0.3)\n    \n    # 4. Layout Configuration\n    layout = [\n        ['I', 'aVR', 'V1', 'V4'],\n        ['II', 'aVL', 'V2', 'V5'],\n        ['III', 'aVF', 'V3', 'V6']\n    ]\n    \n    # Row Centers (y-axis positions)\n    # TWEAKED: Slightly spaced out to handle the higher-res signal better\n    row_centers = [10.5, 7.5, 4.5] \n    segment_duration = 2.5\n    \n    # --- PLOT MAIN 12 LEADS ---\n    for r, row_leads in enumerate(layout):\n        base_y = row_centers[r]\n        \n        for c, lead_name in enumerate(row_leads):\n            x_start = c * segment_duration\n            \n            if lead_name in signals:\n                data = signals[lead_name]\n                \n                # CRITICAL: Calculate exact samples needed based on detected FS\n                samples_needed = int(segment_duration * sample_rate)\n                segment = data[:samples_needed]\n                \n                # Normalize\n                norm_sig = normalize_and_center(segment)\n                \n                # Apply Manual Adjustments\n                gain = MANUAL_GAINS.get(lead_name, 1.0)\n                y_adjust = MANUAL_Y_OFFSETS.get(lead_name, 0.0)\n                \n                # Scale & Center\n                scaled_sig = (norm_sig - 0.5) * gain\n                final_y = scaled_sig + base_y + y_adjust\n                \n                # Time Vector\n                t = np.linspace(x_start, x_start + segment_duration, len(scaled_sig))\n                \n                # Plot\n                ax.plot(t, final_y, color='black', linewidth=0.75)\n                ax.text(x_start + 0.06, base_y + y_adjust + 1.0, lead_name, fontsize=11, fontweight='bold', color='navy')\n\n    # --- PLOT RHYTHM STRIP (Lead II) ---\n    if 'II' in signals:\n        data = signals['II']\n        norm_sig = normalize_and_center(data)\n        \n        gain = MANUAL_GAINS.get('Rhythm', 1.2)\n        scaled_sig = (norm_sig - 0.5) * gain\n        \n        t_full = np.linspace(0, 10, len(scaled_sig))\n        \n        # Plot at bottom (y=1.5)\n        ax.plot(t_full, scaled_sig + 1.5, color='black', linewidth=0.75)\n        ax.text(0.06, 2.5, \"II (Rhythm)\", fontsize=11, fontweight='bold', color='navy')\n\n    # Cleanup\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values(): spine.set_visible(False)\n    ax.set_axisbelow(True)\n\n    plt.tight_layout(pad=1.0)\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.show()\n    print(f\"ECG Generated: {output_path}\")\n\n# ==========================================\n# --- EXECUTION ---\n# ==========================================\nfilename = '1006427285.csv'\ncsv_file_path = None\n\n# Find file in Kaggle Input\nfor root, dirs, files in os.walk('/kaggle/input/physionet-ecg-image-digitization/train/1006427285'):\n    if filename in files:\n        csv_file_path = os.path.join(root, filename)\n        break\n\nif csv_file_path:\n    print(f\"Loading {csv_file_path}...\")\n    clean_signals = load_and_fix_data(csv_file_path)\n    plot_ecg_final(clean_signals)\nelse:\n    print(\"Error: CSV file not found. Check your dataset.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:54:29.705494Z","iopub.execute_input":"2025-12-07T14:54:29.705837Z","iopub.status.idle":"2025-12-07T14:54:32.457357Z","shell.execute_reply.started":"2025-12-07T14:54:29.705808Z","shell.execute_reply":"2025-12-07T14:54:32.456677Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Mf said they will Tune iT","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import AutoMinorLocator\nfrom PIL import Image\nimport os\n\n# ==========================================\n# --- 1. YOUR MANUAL CONTROLS ---\n# ==========================================\n# Tweak these values. Re-run. Watch the Green Lines move.\n# If the Green line is too high, decrease the offset.\n\nMANUAL_Y_OFFSETS = {\n    'I': 1.0,   'aVR': 0.450,  'V1': 0.10,  'V4': 0.20,\n    'II': 0.30,  'aVL': 0.0,   'V2': -0.40, 'V5': 0.350,\n    'III': 0.0,  'aVF': -0.10, 'V3': -0.9,  'V6': 0.0\n}\n\nMANUAL_GAINS = {\n    'I': 1.2,   'aVR': 0.5,  'V1': 1.2,  'V4': 1.2,\n    'II': 1.2,  'aVL': 0.5,  'V2': 1.2,  'V5': 1.2,\n    'III': 1.2, 'aVF': 0.5,  'V3': 1.2,  'V6': 1.2,\n    'Rhythm': 1.2 \n}\n\n# --- BACKGROUND IMAGE SETTINGS ---\n# You might need to tweak these ONCE to get the background grid to align with the plot grid.\n# [Left, Right, Bottom, Top]\n# Try changing Bottom/Top if the image looks squashed or shifted.\nBACKGROUND_EXTENT = [0, 10, 0, 13.5] \n\n# ==========================================\n# --- HELPER FUNCTIONS ---\n# ==========================================\n\ndef load_and_fix_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    for col in df_raw.columns:\n        clean_col = col.strip() \n        valid_data = df_raw[col].dropna().values\n        if len(valid_data) > 0:\n            signals[clean_col] = valid_data\n\n    # Math Fixes for missing leads\n    if 'II' in signals and 'I' in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        if 'III' not in signals:\n            signals['III'] = signals['II'][:n] - signals['I'][:n]\n        \n        sI = signals['I'][:n]\n        sII = signals['II'][:n]\n        for lead in ['aVR', 'aVL', 'aVF']:\n            if lead not in signals or np.std(signals[lead]) < 0.05:\n                if lead == 'aVR': signals['aVR'] = -(sI + sII) / 2\n                if lead == 'aVL': signals['aVL'] = sI - (sII / 2)\n                if lead == 'aVF': signals['aVF'] = sII - (sI / 2)\n    return signals\n\ndef get_sampling_rate(signals):\n    if 'II' in signals:\n        return len(signals['II']) / 10.0\n    return 500.0\n\ndef normalize_and_center(signal):\n    if len(signal) == 0: return signal\n    sig_min = np.min(signal)\n    sig_max = np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    return (signal - sig_min) / (sig_max - sig_min)\n\n# ==========================================\n# --- SUPERIMPOSE PLOT ---\n# ==========================================\n\ndef plot_overlay(signals, bg_image_path, output_path=\"ecg_overlay_comparison.png\"):\n    \n    sample_rate = get_sampling_rate(signals)\n    \n    # Setup Figure\n    fig = plt.figure(figsize=(16, 10))\n    ax = fig.add_subplot(111)\n    \n    # 1. PLOT BACKGROUND IMAGE (The \"Answer Key\")\n    try:\n        img = Image.open(bg_image_path)\n        # Plot image with 50% transparency so you can see your grid too\n        ax.imshow(img, extent=BACKGROUND_EXTENT, aspect='auto', alpha=0.6)\n    except Exception as e:\n        print(f\"Could not load background image: {e}\")\n\n    # 2. SETUP PLOT GRID\n    ax.set_xlim(0, 10)\n    ax.set_ylim(0, 12)\n    \n    # Turn OFF the plot grid so it doesn't clash with the image grid\n    # We only want to see the signals\n    ax.axis('off')\n\n    # 3. PLOT YOUR SIGNALS (Neon Green for high contrast)\n    layout = [\n        ['I', 'aVR', 'V1', 'V4'],\n        ['II', 'aVL', 'V2', 'V5'],\n        ['III', 'aVF', 'V3', 'V6']\n    ]\n    \n    row_centers = [10.5, 7.5, 4.5] \n    segment_duration = 2.5\n    \n    for r, row_leads in enumerate(layout):\n        base_y = row_centers[r]\n        for c, lead_name in enumerate(row_leads):\n            x_start = c * segment_duration\n            \n            if lead_name in signals:\n                data = signals[lead_name]\n                samples = int(segment_duration * sample_rate)\n                segment = data[:samples]\n                \n                # Normalize & Adjust\n                norm = normalize_and_center(segment)\n                gain = MANUAL_GAINS.get(lead_name, 1.0)\n                y_adj = MANUAL_Y_OFFSETS.get(lead_name, 0.0)\n                \n                final_sig = ((norm - 0.5) * gain) + base_y + y_adj\n                t = np.linspace(x_start, x_start + segment_duration, len(final_sig))\n                \n                # PLOT IN NEON GREEN\n                ax.plot(t, final_sig, color='#00FF00', linewidth=1.5, alpha=0.9)\n                # Plot a thin black outline to make it pop\n                ax.plot(t, final_sig, color='black', linewidth=0.5, alpha=0.5)\n\n    # Rhythm Strip\n    if 'II' in signals:\n        data = signals['II']\n        norm = normalize_and_center(data)\n        gain = MANUAL_GAINS.get('Rhythm', 1.2)\n        final_sig = ((norm - 0.5) * gain) + 1.5\n        t_full = np.linspace(0, 10, len(final_sig))\n        \n        ax.plot(t_full, final_sig, color='#00FF00', linewidth=1.5, alpha=0.9)\n\n    plt.tight_layout()\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.show()\n    print(f\"Overlay created! Check {output_path}\")\n\n# ==========================================\n# --- EXECUTION ---\n# ==========================================\n# 1. Find Data\ncsv_path = None\nimg_path = None\n\nprint(\"Searching for files...\")\nfor root, dirs, files in os.walk('/kaggle/input/physionet-ecg-image-digitization/train/1006427285'):\n    if '1006427285.csv' in files:\n        csv_path = os.path.join(root, '1006427285.csv')\n    if '1006427285-0001.png' in files:\n        img_path = os.path.join(root, '1006427285-0001.png')\n\nif csv_path and img_path:\n    print(\"Files found!\")\n    print(f\"Data: {csv_path}\")\n    print(f\"Image: {img_path}\")\n    \n    signals = load_and_fix_data(csv_path)\n    plot_overlay(signals, img_path)\nelse:\n    print(\"Could not find both the CSV and the -0001.png image.\")\n    print(\"Please check your input directory.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:54:32.458843Z","iopub.execute_input":"2025-12-07T14:54:32.459099Z","iopub.status.idle":"2025-12-07T14:54:38.937310Z","shell.execute_reply.started":"2025-12-07T14:54:32.459078Z","shell.execute_reply":"2025-12-07T14:54:38.936340Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\n\n# ==========================================\n# --- 1. SETTINGS ---\n# ==========================================\n\n# HOW MUCH TO CUT OFF THE LEFT SIDE (The \"Before\" part)\n# Try increasing/decreasing this to slide the background image left/right.\n# 200 is a good starting guess for the calibration pulse width.\nCROP_LEFT_PIXELS = 120 \n\n# YOUR TUNED PARAMETERS (Paste your best ones here)\nMANUAL_Y_OFFSETS = {\n    'I': 1.0,   'aVR': 0.450,  'V1': 0.10,  'V4': 0.20,\n    'II': 0.30,  'aVL': 0.0,   'V2': -0.40, 'V5': 0.350,\n    'III': 0.0,  'aVF': -0.10, 'V3': -0.9,  'V6': 0.0\n}\n\nMANUAL_GAINS = {\n    'I': 1.2,   'aVR': 0.5,  'V1': 1.2,  'V4': 1.2,\n    'II': 1.2,  'aVL': 0.5,  'V2': 1.2,  'V5': 1.2,\n    'III': 1.2, 'aVF': 0.5,  'V3': 1.2,  'V6': 1.2,\n    'Rhythm': 1.2 \n}\n\n# ==========================================\n# --- HELPER FUNCTIONS ---\n# ==========================================\n\ndef load_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    for col in df_raw.columns:\n        signals[col.strip()] = df_raw[col].dropna().values\n\n    # Math Fixes\n    if 'II' in signals and 'I' in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        if 'III' not in signals: signals['III'] = signals['II'][:n] - signals['I'][:n]\n        sI, sII = signals['I'][:n], signals['II'][:n]\n        for lead in ['aVR', 'aVL', 'aVF']:\n            if lead not in signals or np.std(signals[lead]) < 0.05:\n                if lead == 'aVR': signals['aVR'] = -(sI + sII) / 2\n                if lead == 'aVL': signals['aVL'] = sI - (sII / 2)\n                if lead == 'aVF': signals['aVF'] = sII - (sI / 2)\n    return signals\n\ndef normalize_and_center(signal):\n    if len(signal) == 0: return signal\n    sig_min, sig_max = np.min(signal), np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    return (signal - sig_min) / (sig_max - sig_min)\n\n# ==========================================\n# --- CROP & COMPARE PLOT ---\n# ==========================================\n\ndef plot_cropped_overlay(signals, img_path):\n    \n    # 1. Load and Crop Image\n    img = Image.open(img_path)\n    img_arr = np.array(img)\n    \n    # CROP THE LEFT SIDE\n    # We slice the array: [All Rows, From CROP_PIXELS to End, All Channels]\n    cropped_img = img_arr[:, CROP_LEFT_PIXELS:, :]\n    \n    # 2. Setup Plot\n    fig = plt.figure(figsize=(16, 10))\n    ax = fig.add_subplot(111)\n    \n    # 3. Display Background (The Cropped Image)\n    # We map this cropped image to the 0-10 second, 0-12 unit coordinate system\n    # If the image looks squashed vertically, adjust the '13.5' number below.\n    ax.imshow(cropped_img, extent=[0, 10, 0, 13.5], aspect='auto', alpha=0.5)\n    \n    # 4. Plot Signals (Neon Green)\n    sample_rate = len(signals['II']) / 10.0\n    layout = [['I', 'aVR', 'V1', 'V4'], ['II', 'aVL', 'V2', 'V5'], ['III', 'aVF', 'V3', 'V6']]\n    row_centers = [10.5, 7.5, 4.5] \n    \n    for r, row_leads in enumerate(layout):\n        base_y = row_centers[r]\n        for c, lead_name in enumerate(row_leads):\n            if lead_name in signals:\n                data = signals[lead_name]\n                samples = int(2.5 * sample_rate)\n                segment = data[:samples]\n                \n                # Apply params\n                norm = normalize_and_center(segment)\n                gain = MANUAL_GAINS.get(lead_name, 1.0)\n                offset = MANUAL_Y_OFFSETS.get(lead_name, 0.0)\n                \n                final_sig = ((norm - 0.5) * gain) + base_y + offset\n                t = np.linspace(c*2.5, (c+1)*2.5, len(final_sig))\n                \n                ax.plot(t, final_sig, color='#00FF00', linewidth=1.5, alpha=0.8)\n\n    # Rhythm Strip\n    if 'II' in signals:\n        norm = normalize_and_center(signals['II'])\n        final_sig = ((norm - 0.5) * MANUAL_GAINS.get('Rhythm', 1.2)) + 1.5\n        t = np.linspace(0, 10, len(final_sig))\n        ax.plot(t, final_sig, color='#00FF00', linewidth=1.5, alpha=0.8)\n\n    ax.axis('off')\n    plt.tight_layout()\n    plt.show()\n    print(f\"Showing comparison with LEFT CROP of {CROP_LEFT_PIXELS} pixels.\")\n\n# ==========================================\n# --- EXECUTION ---\n# ==========================================\ncsv_path = None\nimg_path = None\nfor root, dirs, files in os.walk('/kaggle/input/physionet-ecg-image-digitization/train/1006427285'):\n    if '1006427285.csv' in files: csv_path = os.path.join(root, '1006427285.csv')\n    if '1006427285-0001.png' in files: img_path = os.path.join(root, '1006427285-0001.png')\n\nif csv_path and img_path:\n    plot_cropped_overlay(load_data(csv_path), img_path)\nelse:\n    print(\"Files not found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T15:01:40.287626Z","iopub.execute_input":"2025-12-07T15:01:40.288411Z","iopub.status.idle":"2025-12-07T15:01:41.486054Z","shell.execute_reply.started":"2025-12-07T15:01:40.288380Z","shell.execute_reply":"2025-12-07T15:01:41.485070Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"He showned a funny way","metadata":{}},{"cell_type":"markdown","source":"# We'll TUUUUUUUUUNE it","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\n\n# ==========================================\n# --- 1. SETTINGS & TUNING ---\n# ==========================================\n\n# LEFT CROP: 120 pixels is a good start.\n# If signals are too far LEFT, INCREASE this number.\n# If signals are too far RIGHT, DECREASE this number.\nCROP_LEFT_PIXELS = 120 \n\n# VERTICAL EXTENT: Controls vertical squashing.\n# If the background grid boxes look too TALL vs wide, LOWER this (e.g., 12.5).\n# If the background grid boxes look too FLAT, RAISE this (e.g., 14.5).\nBG_Y_EXTENT = 13.5 \n\n# ==========================================\n# --- TUNED PARAMETERS (ADJUSTED) ---\n# ==========================================\n\n# CHANGES MADE:\n# 1. TOP ROW (I, aVR, V1, V4): Decreased values by ~0.3 to move them DOWN.\n# 2. MIDDLE ROW (II, aVL, V2, V5): Slight adjustment.\n# 3. BOTTOM ROW (III, aVF, V3, V6): Increased values by ~0.3 to move them UP.\nONE = -3\nTWO = -2\nTHREE = -1\nMANUAL_Y_OFFSETS = {\n    # --- ROW 1 (TOP) - MOVED DOWN ---\n    'I': ONE+0.70,    'aVR':ONE+ 0.15,   'V1': ONE -0.20,  'V4': ONE -0.10,\n    \n    # --- ROW 2 (MIDDLE) ---\n    'II': TWO+ 0.30,   'aVL':TWO+ 0.0,    'V2': TWO-0.40,  'V5': TWO+0.35,\n    \n    # --- ROW 3 (BOTTOM) - MOVED UP ---\n    'III':THREE+ 0.30,  'aVF':THREE+ 0.20,   'V3':THREE -0.60,  'V6':THREE+ 0.30\n}\n\n# Keep Gains the same for now\nMANUAL_GAINS = {\n    'I': 1,   'aVR': 0.5,  'V1': 1,  'V4': 1,\n    'II': 1,  'aVL': 0.5,  'V2': 1,  'V5': 1,\n    'III': 1, 'aVF': 0.5,  'V3': 1,  'V6': 1,\n    'Rhythm': 1 \n}\n\n# ==========================================\n# --- HELPER FUNCTIONS ---\n# ==========================================\n\ndef load_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    for col in df_raw.columns:\n        signals[col.strip()] = df_raw[col].dropna().values\n\n    # Math Fixes\n    if 'II' in signals and 'I' in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        if 'III' not in signals: signals['III'] = signals['II'][:n] - signals['I'][:n]\n        sI, sII = signals['I'][:n], signals['II'][:n]\n        for lead in ['aVR', 'aVL', 'aVF']:\n            if lead not in signals or np.std(signals[lead]) < 0.05:\n                if lead == 'aVR': signals['aVR'] = -(sI + sII) / 2\n                if lead == 'aVL': signals['aVL'] = sI - (sII / 2)\n                if lead == 'aVF': signals['aVF'] = sII - (sI / 2)\n    return signals\n\ndef normalize_and_center(signal):\n    if len(signal) == 0: return signal\n    sig_min, sig_max = np.min(signal), np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    return (signal - sig_min) / (sig_max - sig_min)\n\n# ==========================================\n# --- CROP & COMPARE PLOT ---\n# ==========================================\n\ndef plot_cropped_overlay(signals, img_path):\n    \n    # 1. Load Image\n    img = Image.open(img_path)\n    img_arr = np.array(img)\n    full_height, full_width, _ = img_arr.shape\n    \n    # 2. CROP THE LEFT SIDE\n    if CROP_LEFT_PIXELS < full_width:\n        cropped_img = img_arr[:, CROP_LEFT_PIXELS:, :]\n    else:\n        print(\"Error: Crop amount is larger than image width!\")\n        return\n\n    # 3. Calculate Aspect Ratio Correction\n    # If we crop 120 pixels, the remaining image represents 10 seconds.\n    # We need to map that correctly.\n    \n    fig = plt.figure(figsize=(14, 10))\n    ax = fig.add_subplot(111)\n    \n    # Plot Background\n    # extent=[left, right, bottom, top]\n    ax.imshow(cropped_img, extent=[0, 10, 0, BG_Y_EXTENT], aspect='auto', alpha=0.5)\n    \n    # Setup Plot Boundaries\n    ax.set_xlim(0, 10)\n    ax.set_ylim(0, 12)\n    \n    # 4. Plot Signals (Neon Green)\n    # We assume Lead II is the full 10s reference for sample rate\n    if 'II' in signals:\n        sample_rate = len(signals['II']) / 10.0\n    else:\n        sample_rate = 500.0 # Fallback\n        \n    layout = [['I', 'aVR', 'V1', 'V4'], ['II', 'aVL', 'V2', 'V5'], ['III', 'aVF', 'V3', 'V6']]\n    row_centers = [10.5, 7.5, 4.5] \n    \n    for r, row_leads in enumerate(layout):\n        base_y = row_centers[r]\n        for c, lead_name in enumerate(row_leads):\n            if lead_name in signals:\n                data = signals[lead_name]\n                samples = int(2.5 * sample_rate)\n                segment = data[:samples]\n                \n                # Apply params\n                norm = normalize_and_center(segment)\n                gain = MANUAL_GAINS.get(lead_name, 1.0)\n                offset = MANUAL_Y_OFFSETS.get(lead_name, 0.0)\n                \n                final_sig = ((norm - 0.5) * gain) + base_y + offset\n                t = np.linspace(c*2.5, (c+1)*2.5, len(final_sig))\n                \n                # Plot with shadow for visibility\n                ax.plot(t, final_sig, color='black', linewidth=2.5, alpha=0.3) # Shadow\n                ax.plot(t, final_sig, color='#00FF00', linewidth=1.2, alpha=0.9) # Signal\n\n    # Rhythm Strip\n    if 'II' in signals:\n        norm = normalize_and_center(signals['II'])\n        final_sig = ((norm - 0.5) * MANUAL_GAINS.get('Rhythm', 1.2)) + 1.5\n        t = np.linspace(0, 10, len(final_sig))\n        ax.plot(t, final_sig, color='black', linewidth=2.5, alpha=0.3)\n        ax.plot(t, final_sig, color='#00FF00', linewidth=1.2, alpha=0.9)\n\n    ax.axis('off')\n    plt.tight_layout()\n    plt.show()\n    print(f\"Comparison Generated. Left Crop: {CROP_LEFT_PIXELS}px\")\n\n# ==========================================\n# --- EXECUTION ---\n# ==========================================\n# Hardcoded specific search for your file to ensure it runs fast\ncsv_path = None\nimg_path = None\n\nsearch_dir = '/kaggle/input/physionet-ecg-image-digitization/train/1006427285'\nprint(\"Searching for specific patient files...\")\n\nfor root, dirs, files in os.walk(search_dir):\n    if '1006427285.csv' in files: \n        csv_path = os.path.join(root, '1006427285.csv')\n    if '1006427285-0001.png' in files: \n        img_path = os.path.join(root, '1006427285-0001.png')\n    if csv_path and img_path:\n        break\n\nif csv_path and img_path:\n    print(f\"Found Data: {csv_path}\")\n    print(f\"Found Image: {img_path}\")\n    plot_cropped_overlay(load_data(csv_path), img_path)\nelse:\n    print(\"Files not found. Please verify the dataset is attached to the notebook.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T15:40:08.959869Z","iopub.execute_input":"2025-12-07T15:40:08.960227Z","iopub.status.idle":"2025-12-07T15:40:10.071574Z","shell.execute_reply.started":"2025-12-07T15:40:08.960203Z","shell.execute_reply":"2025-12-07T15:40:10.070597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile bestshot.py\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\n\n# ==========================================\n# --- 1. SETTINGS & TUNING ---\n# ==========================================\n\n# LEFT CROP ADJUSTMENT\n# You wanted Green to shift LEFT.\n# This means the Black was to the Left of Green.\n# To fix: We move Black RIGHT by cropping LESS.\n# (Previous was 120 -> New is 90)\nCROP_LEFT_PIXELS = 120 \n\n# VERTICAL EXTENT\nBG_Y_EXTENT = 13.5 \n\n# ==========================================\n# --- YOUR STYLE PARAMETERS (PRESERVED) ---\n# ==========================================\n\nONE = -3.0\nTWO = -2.0\nTHREE = -1.0\n\n# Adjusted based on your feedback (\"Little Small\")\n# I added a \"- 0.05\" to the offsets to fine-tune the alignment\nMANUAL_Y_OFFSETS = {\n    # --- ROW 1 (TOP) ---\n    'I': ONE + 0.70,     'aVR': ONE + 0.15,    'V1': ONE - 0.20,   'V4': ONE - 0.10,\n    \n    # --- ROW 2 (MIDDLE) ---\n    'II': TWO + 0.30,    'aVL': TWO + 0.0,     'V2': TWO - 0.40,   'V5': TWO + 0.35,\n    \n    # --- ROW 3 (BOTTOM) ---\n    'III': THREE + 0.30, 'aVF': THREE + 0.20,  'V3': THREE - 0.60, 'V6': THREE + 0.30\n}\n\n# GAINS (SIZE)\n# \"A little small maybe\" -> Reduced factor to 1.0 (Standard)\nSIZE_FACTOR = 1.0 \n\nMANUAL_GAINS = {\n    'I': SIZE_FACTOR,   'aVR': 0.5,           'V1': SIZE_FACTOR,  'V4': SIZE_FACTOR,\n    'II': SIZE_FACTOR,  'aVL': 0.5,           'V2': SIZE_FACTOR,  'V5': SIZE_FACTOR,\n    'III': SIZE_FACTOR, 'aVF': 0.5,           'V3': SIZE_FACTOR,  'V6': SIZE_FACTOR,\n    'Rhythm': SIZE_FACTOR \n}\n\n# ==========================================\n# --- HELPER FUNCTIONS ---\n# ==========================================\n\ndef load_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    for col in df_raw.columns:\n        signals[col.strip()] = df_raw[col].dropna().values\n\n    # Math Fixes\n    if 'II' in signals and 'I' in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        if 'III' not in signals: signals['III'] = signals['II'][:n] - signals['I'][:n]\n        sI, sII = signals['I'][:n], signals['II'][:n]\n        for lead in ['aVR', 'aVL', 'aVF']:\n            if lead not in signals or np.std(signals[lead]) < 0.05:\n                if lead == 'aVR': signals['aVR'] = -(sI + sII) / 2\n                if lead == 'aVL': signals['aVL'] = sI - (sII / 2)\n                if lead == 'aVF': signals['aVF'] = sII - (sI / 2)\n    return signals\n\ndef normalize_and_center(signal):\n    if len(signal) == 0: return signal\n    sig_min, sig_max = np.min(signal), np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    return (signal - sig_min) / (sig_max - sig_min)\n\n# ==========================================\n# --- SUPERIMPOSITION PLOT ---\n# ==========================================\n\ndef plot_superimposed(signals, img_path):\n    \n    # 1. Load Image\n    img = Image.open(img_path)\n    img_arr = np.array(img)\n    full_height, full_width, _ = img_arr.shape\n    \n    # 2. CROP LEFT (Control horizontal alignment)\n    if CROP_LEFT_PIXELS < full_width:\n        cropped_img = img_arr[:, CROP_LEFT_PIXELS:, :]\n    else:\n        print(\"Error: Crop too large.\")\n        return\n\n    # 3. Setup Figure\n    fig = plt.figure(figsize=(16, 10))\n    ax = fig.add_subplot(111)\n    \n    # 4. SHOW BLACK BACKGROUND (The Original)\n    # alpha=0.6 makes it visible but allows grid to show through if needed\n    ax.imshow(cropped_img, extent=[0, 10, 0, BG_Y_EXTENT], aspect='auto', alpha=0.6)\n    \n    # 5. PLOT GREEN SIGNALS (The Generated Code)\n    ax.set_xlim(0, 10)\n    ax.set_ylim(0, 12)\n    \n    if 'II' in signals:\n        sample_rate = len(signals['II']) / 10.0\n    else:\n        sample_rate = 500.0 \n        \n    layout = [['I', 'aVR', 'V1', 'V4'], ['II', 'aVL', 'V2', 'V5'], ['III', 'aVF', 'V3', 'V6']]\n    row_centers = [10.5, 7.5, 4.5] \n    \n    for r, row_leads in enumerate(layout):\n        base_y = row_centers[r]\n        for c, lead_name in enumerate(row_leads):\n            if lead_name in signals:\n                data = signals[lead_name]\n                samples = int(2.5 * sample_rate)\n                segment = data[:samples]\n                \n                # Apply params\n                norm = normalize_and_center(segment)\n                gain = MANUAL_GAINS.get(lead_name, 1.0)\n                offset = MANUAL_Y_OFFSETS.get(lead_name, 0.0)\n                \n                final_sig = ((norm - 0.5) * gain) + base_y + offset\n                t = np.linspace(c*2.5, (c+1)*2.5, len(final_sig))\n                \n                # Plot NEON GREEN on top\n                # Added a slight black outline/shadow to separate Green from Black\n                ax.plot(t, final_sig, color='black', linewidth=2.5, alpha=0.4) \n                ax.plot(t, final_sig, color='#00FF00', linewidth=1.2, alpha=0.9) \n\n    # Rhythm Strip\n    if 'II' in signals:\n        norm = normalize_and_center(signals['II'])\n        final_sig = ((norm - 0.5) * MANUAL_GAINS.get('Rhythm', 1.0)) + 1.5\n        t = np.linspace(0, 10, len(final_sig))\n        ax.plot(t, final_sig, color='black', linewidth=2.5, alpha=0.4)\n        ax.plot(t, final_sig, color='#00FF00', linewidth=1.2, alpha=0.9)\n\n    ax.axis('off')\n    plt.tight_layout()\n    plt.show()\n    print(f\"Superimposed: Green (Code) on Black (Original). Crop: {CROP_LEFT_PIXELS}\")\n\n# ==========================================\n# --- EXECUTION ---\n# ==========================================\ncsv_path = None\nimg_path = None\n\nsearch_dir = '/kaggle/input/physionet-ecg-image-digitization/train/1006427285'\nprint(\"Searching files...\")\n\nfor root, dirs, files in os.walk(search_dir):\n    if '1006427285.csv' in files: \n        csv_path = os.path.join(root, '1006427285.csv')\n    if '1006427285-0001.png' in files: \n        img_path = os.path.join(root, '1006427285-0001.png')\n    if csv_path and img_path:\n        break\n\nif csv_path and img_path:\n    plot_superimposed(load_data(csv_path), img_path)\nelse:\n    print(\"Files not found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T15:42:55.902038Z","iopub.execute_input":"2025-12-07T15:42:55.902977Z","iopub.status.idle":"2025-12-07T15:42:55.911722Z","shell.execute_reply.started":"2025-12-07T15:42:55.902949Z","shell.execute_reply":"2025-12-07T15:42:55.910383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nfrom scipy.stats import linregress\nimport os\n\n# ==========================================\n# --- 1. CONFIGURATION ---\n# ==========================================\n# The layout of the 12 leads in the image (Rows x Cols)\nLAYOUT = [\n    ['I', 'aVR', 'V1', 'V4'],\n    ['II', 'aVL', 'V2', 'V5'],\n    ['III', 'aVF', 'V3', 'V6']\n]\n\n# ==========================================\n# --- 2. COMPUTER VISION FUNCTIONS ---\n# ==========================================\n\ndef extract_signal_from_image(img_path, rows=3, cols=4):\n    \"\"\"\n    Reads the image, ignores the red grid, and returns the Y-position \n    of the black signal for each lead box.\n    \"\"\"\n    # 1. Load Image\n    img = cv2.imread(img_path)\n    if img is None:\n        raise FileNotFoundError(\"Image not found\")\n    \n    # 2. Extract RED Channel (Grid is Red/White -> High Value; Signal is Black -> Low Value)\n    # OpenCV loads as BGR, so Red is index 2.\n    red_channel = img[:, :, 2]\n    \n    # 3. Threshold: Signal (Black) will be close to 0. Background/Grid (Red/White) close to 255.\n    # Invert so Signal is High (White) and Background is Low (Black)\n    _, thresh = cv2.threshold(red_channel, 50, 255, cv2.THRESH_BINARY_INV)\n    \n    h, w = thresh.shape\n    box_h = h // rows\n    box_w = w // cols\n    \n    extracted_traces = {}\n    \n    for r in range(rows):\n        for c in range(cols):\n            lead_name = LAYOUT[r][c]\n            \n            # Crop to the specific lead's box\n            y_start, y_end = r * box_h, (r + 1) * box_h\n            x_start, x_end = c * box_w, (c + 1) * box_w\n            \n            crop = thresh[y_start:y_end, x_start:x_end]\n            \n            # Extract 1D signal: Find the center of mass of white pixels for each column\n            trace_y = []\n            valid_columns = []\n            \n            for col_idx in range(crop.shape[1]):\n                column_pixels = np.where(crop[:, col_idx] > 0)[0]\n                if len(column_pixels) > 0:\n                    # Average Y position of the black ink in this column\n                    # We flip Y so it matches Cartesian plot (0 at bottom)\n                    avg_y = box_h - np.mean(column_pixels) \n                    \n                    # Add global offset to match plotting coordinates later\n                    # (This part is relative, the regression fixes the absolute)\n                    trace_y.append(avg_y) \n                    valid_columns.append(col_idx)\n            \n            if len(trace_y) > 100: # Ensure we found a valid signal\n                extracted_traces[lead_name] = np.array(trace_y)\n                \n    return extracted_traces, (h, w)\n\n# ==========================================\n# --- 3. AUTO-ALIGNMENT MATH ---\n# ==========================================\n\ndef auto_tune_parameters(csv_path, img_path):\n    print(f\"Analyzing {img_path}...\")\n    \n    # --- A. Get Ground Truth from Image ---\n    image_traces, img_dim = extract_signal_from_image(img_path)\n    img_h, img_w = img_dim\n    \n    # --- B. Get Raw Data from CSV ---\n    df = pd.read_csv(csv_path)\n    signals = {}\n    for col in df.columns:\n        signals[col.strip()] = df[col].dropna().values\n\n    # Fix Missing Leads\n    if 'II' in signals and 'I' in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        if 'III' not in signals: signals['III'] = signals['II'][:n] - signals['I'][:n]\n        sI, sII = signals['I'][:n], signals['II'][:n]\n        if 'aVR' not in signals: signals['aVR'] = -(sI + sII) / 2\n        if 'aVL' not in signals: signals['aVL'] = sI - (sII / 2)\n        if 'aVF' not in signals: signals['aVF'] = sII - (sI / 2)\n\n    # --- C. Solve for Gain & Offset ---\n    # We want: Image_Y = (CSV_Value * GAIN) + OFFSET\n    \n    calculated_offsets = {}\n    calculated_gains = {}\n    \n    # Conversion factor: Image Pixels to Matplotlib Coordinates\n    # In our plot, Height is 12 units. Image height is img_h pixels.\n    pixel_to_plot_scale = 12.0 / img_h\n    \n    print(\"\\n--- OPTIMIZED PARAMETERS FOUND ---\")\n    \n    for lead, img_trace in image_traces.items():\n        if lead in signals:\n            csv_sig = signals[lead]\n            \n            # Resample CSV to match Image Trace length\n            # (Simple linear interpolation)\n            x_csv = np.linspace(0, 1, len(csv_sig))\n            x_img = np.linspace(0, 1, len(img_trace))\n            csv_resampled = np.interp(x_img, x_csv, csv_sig)\n            \n            # Convert Image Pixel Y to Plot Coordinate Y\n            img_trace_plot_units = img_trace * pixel_to_plot_scale\n            \n            # --- LINEAR REGRESSION ---\n            # Slope = Gain, Intercept = Absolute Y Position\n            slope, intercept, r_value, p_value, std_err = linregress(csv_resampled, img_trace_plot_units)\n            \n            # The 'intercept' is the absolute Y position on the 0-12 grid.\n            # We need the \"Offset\" relative to the row center.\n            # Row centers were [10.5, 7.5, 4.5] roughly. \n            # We can just output the raw intercept and use that as the absolute base, \n            # OR calculate the specific relative offset. Let's provide the relative for your dictionary.\n            \n            # Determine Row Center\n            row_idx = -1\n            for r, row in enumerate(LAYOUT):\n                if lead in row: row_idx = r\n            \n            # Standard Centers from our previous code\n            ideal_centers = [10.5, 7.5, 4.5]\n            row_center = ideal_centers[row_idx]\n            \n            relative_offset = intercept - row_center\n            \n            calculated_gains[lead] = round(slope, 3)\n            calculated_offsets[lead] = round(relative_offset, 3)\n            \n            # Debug Quality Check\n            # print(f\"{lead}: R^2={r_value**2:.2f} (Good fit if close to 1.0)\")\n\n    return calculated_offsets, calculated_gains\n\n# ==========================================\n# --- EXECUTION ---\n# ==========================================\n# Find files\ncsv_file = None\nimg_file = None\nfor root, dirs, files in os.walk('/kaggle/input/physionet-ecg-image-digitization/train/1006427285'):\n    if '1006427285.csv' in files: csv_file = os.path.join(root, '1006427285.csv')\n    if '1006427285-0001.png' in files: img_file = os.path.join(root, '1006427285-0001.png')\n\nif csv_file and img_file:\n    offsets, gains = auto_tune_parameters(csv_file, img_file)\n    \n    print(\"\\n# COPY AND PASTE THIS INTO YOUR MAIN CODE:\")\n    print(\"-\" * 40)\n    print(\"MANUAL_Y_OFFSETS = {\")\n    for k, v in offsets.items():\n        print(f\"    '{k}': {v},\")\n    print(\"}\")\n    \n    print(\"\\nMANUAL_GAINS = {\")\n    for k, v in gains.items():\n        print(f\"    '{k}': {v},\")\n    print(\"}\")\n    print(\"-\" * 40)\n    \nelse:\n    print(\"Files not found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:54:40.090567Z","iopub.execute_input":"2025-12-07T14:54:40.090941Z","iopub.status.idle":"2025-12-07T14:54:40.307748Z","shell.execute_reply.started":"2025-12-07T14:54:40.090918Z","shell.execute_reply":"2025-12-07T14:54:40.306832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!git clone https://github.com/Sanjidh090/ecg-image-kit.git","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:54:40.309198Z","iopub.execute_input":"2025-12-07T14:54:40.309498Z","iopub.status.idle":"2025-12-07T14:55:06.742089Z","shell.execute_reply.started":"2025-12-07T14:54:40.309476Z","shell.execute_reply":"2025-12-07T14:55:06.740896Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%pip install wfdb","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:06.743559Z","iopub.execute_input":"2025-12-07T14:55:06.743973Z","iopub.status.idle":"2025-12-07T14:55:15.706839Z","shell.execute_reply.started":"2025-12-07T14:55:06.743932Z","shell.execute_reply":"2025-12-07T14:55:15.705491Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport csv\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import AutoMinorLocator\n\ndef load_and_clean_data(csv_path):\n    \"\"\"\n    Reads the CSV. Since the data is sequential (different leads appear \n    in different rows), we extract the valid non-NaN data for each lead \n    independently.\n    \"\"\"\n    df_raw = pd.read_csv(csv_path)\n    \n    # Create a dictionary to hold clean signal arrays\n    signals = {}\n    \n    # Standard 12 leads usually expected\n    target_leads = ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6']\n    \n    for col in df_raw.columns:\n        # Strip whitespace from headers if any\n        clean_col = col.strip() \n        \n        # Get all non-NaN values for this column\n        valid_data = df_raw[col].dropna().values\n        \n        if len(valid_data) > 0:\n            signals[clean_col] = valid_data\n\n    # Reconstruct missing leads if necessary (Standard Calculation)\n    # Lead II = I + III  ->  I = II - III\n    if 'II' in signals and 'III' in signals and 'I' not in signals:\n        signals['I'] = signals['II'] - signals['III']\n        \n    return signals\n\ndef normalize(signal):\n    \"\"\"\n    Normalize signal to range [0, 1] for easier plotting on the grid,\n    then scale it to fit within a 1-unit visual box.\n    \"\"\"\n    if len(signal) == 0:\n        return signal\n    sig_min = np.min(signal)\n    sig_max = np.max(signal)\n    \n    if sig_max == sig_min:\n        return np.zeros_like(signal)\n        \n    return (signal - sig_min) / (sig_max - sig_min)\n\ndef plot_ecg(signals, output_path=\"ecg_output.png\", sample_rate=500):\n    \"\"\"\n    Generates the standard 3x4 + 1 Rhythm Strip layout.\n    \"\"\"\n    # Setup Figure (Landscape)\n    fig = plt.figure(figsize=(20, 12)) # Standard A4-ish ratio\n    \n    # Create a grid for manual plotting\n    # We want a 0-10 second x-axis, and appropriate y-axis\n    ax = fig.add_subplot(111)\n    \n    # --- GRID SETTINGS ---\n    # Standard ECG: 1 small box = 0.04s, 1 big box = 0.2s\n    ax.set_xlim(0, 10) # 10 seconds total\n    ax.set_ylim(0, 12) # Vertical space for rows\n    \n    # Major grid (0.2s red lines)\n    ax.xaxis.set_major_locator(plt.MultipleLocator(0.2))\n    ax.yaxis.set_major_locator(plt.MultipleLocator(1))\n    ax.grid(which='major', color='red', linestyle='-', linewidth=0.7, alpha=0.5)\n\n    # Minor grid (0.04s pink lines)\n    ax.xaxis.set_minor_locator(AutoMinorLocator(5))\n    ax.yaxis.set_minor_locator(AutoMinorLocator(5))\n    ax.grid(which='minor', color='red', linestyle='-', linewidth=0.3, alpha=0.3)\n    \n    # Layout Config: 4 columns of leads, 3 rows deep\n    # Row 1: I, aVR, V1, V4\n    # Row 2: II, aVL, V2, V5\n    # Row 3: III, aVF, V3, V6\n    layout = [\n        ['I', 'aVR', 'V1', 'V4'],   # Top Row\n        ['II', 'aVL', 'V2', 'V5'],  # Middle Row\n        ['III', 'aVF', 'V3', 'V6']  # Bottom Row\n    ]\n    \n    # Vertical offsets for rows (plotting from top down)\n    row_offsets = [9, 6, 3] \n    \n    # 2.5 seconds per segment in the 3x4 grid\n    segment_duration = 2.5 \n    \n    # --- PLOT 3x4 GRID ---\n    for r, row_leads in enumerate(layout):\n        y_base = row_offsets[r]\n        \n        for c, lead_name in enumerate(row_leads):\n            x_offset = c * segment_duration\n            \n            if lead_name in signals:\n                # Get signal data\n                data = signals[lead_name]\n                \n                # Determine how many samples fit in 2.5 seconds\n                samples_needed = int(segment_duration * sample_rate)\n                \n                # Take the first N samples (or as many as available)\n                segment = data[:samples_needed]\n                \n                # Normalize specifically for this plot box\n                # We scale it by 1.5 to make it visible but not overlapping too much\n                norm_seg = normalize(segment) * 1.5 \n                \n                # Time vector for this segment\n                t = np.linspace(x_offset, x_offset + segment_duration, len(norm_seg))\n                \n                # Plot\n                # Add 0.5 to center it in the \"row\"\n                ax.plot(t, norm_seg + y_base - 0.25, color='black', linewidth=1.2)\n                \n                # Add Label\n                ax.text(x_offset + 0.1, y_base + 1, lead_name, fontsize=12, fontweight='bold', color='blue')\n\n    # --- PLOT RHYTHM STRIP (Usually Lead II) ---\n    rhythm_lead = 'II'\n    if rhythm_lead in signals:\n        data = signals[rhythm_lead]\n        # We want full 10 seconds if available\n        norm_data = normalize(data) * 1.5\n        t_full = np.linspace(0, 10, len(norm_data))\n        \n        # Plot at bottom (y_base around 0 or 1)\n        ax.plot(t_full, norm_data + 0.5, color='black', linewidth=1.2)\n        ax.text(0.1, 2, rhythm_lead + \" (Rhythm)\", fontsize=12, fontweight='bold', color='blue')\n\n    # Remove axis numbers/spines for clean look\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    # Keep the box frame? Usually ECGs have no outer frame, just grid.\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    plt.tight_layout()\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.show()\n    print(f\"ECG Image generated at: {output_path}\")\n\n# --- EXECUTION BLOCK ---\n# Replace this path with your actual file path in Kaggle\ncsv_file_path = '/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv'\n\ntry:\n    print(\"Loading data...\")\n    clean_signals = load_and_clean_data(csv_file_path)\n    print(f\"Leads found: {list(clean_signals.keys())}\")\n    \n    print(\"Generating Image...\")\n    plot_ecg(clean_signals, output_path='my_ecg_print.png')\n    \nexcept FileNotFoundError:\n    print(\"Error: CSV file not found. Please check the path.\")\nexcept Exception as e:\n    print(f\"An error occurred: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:15.708483Z","iopub.execute_input":"2025-12-07T14:55:15.709004Z","iopub.status.idle":"2025-12-07T14:55:19.059657Z","shell.execute_reply.started":"2025-12-07T14:55:15.708967Z","shell.execute_reply":"2025-12-07T14:55:19.058735Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> try again...alomost There\n> ","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import AutoMinorLocator\nimport os\n\n# --- 1. Data Loading & Cleaning Function (Same as before) ---\ndef load_and_clean_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    target_leads = ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6']\n    \n    for col in df_raw.columns:\n        clean_col = col.strip() \n        # Get all non-NaN values for this column individually\n        valid_data = df_raw[col].dropna().values\n        if len(valid_data) > 0:\n            signals[clean_col] = valid_data\n\n    # Reconstruct missing leads if necessary\n    if 'II' in signals and 'III' in signals and 'I' not in signals:\n        signals['I'] = signals['II'] - signals['III']\n        \n    return signals\n\n# --- 2. Normalization Function (Same as before) ---\ndef normalize(signal):\n    \"\"\"Normalize signal to range [0, 1]\"\"\"\n    if len(signal) == 0: return signal\n    sig_min = np.min(signal)\n    sig_max = np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    return (signal - sig_min) / (sig_max - sig_min)\n\n# --- 3. Plotting Function (ADJUSTED POSITIONING) ---\ndef plot_ecg_tight_layout(signals, output_path=\"ecg_output_tight.png\", sample_rate=500):\n    \n    # 1. Setup Figure (Standard A4 landscape ratio)\n    fig = plt.figure(figsize=(16, 10)) \n    ax = fig.add_subplot(111)\n    \n    # 2. Grid Settings (Standard ECG Grid)\n    # Y-axis: 0 to 12 big boxes height\n    ax.set_ylim(0, 13) \n    ax.set_xlim(0, 10) # 10 seconds total width\n    \n    # Major grid (Red, bold)\n    ax.xaxis.set_major_locator(plt.MultipleLocator(0.2))\n    ax.yaxis.set_major_locator(plt.MultipleLocator(1.0))\n    ax.grid(which='major', color='red', linestyle='-', linewidth=0.8, alpha=0.6)\n\n    # Minor grid (Pink, fine)\n    ax.xaxis.set_minor_locator(AutoMinorLocator(5)) # 5 subdivisions = 0.04s\n    ax.yaxis.set_minor_locator(AutoMinorLocator(5)) # 5 subdivisions\n    ax.grid(which='minor', color='red', linestyle='-', linewidth=0.3, alpha=0.3)\n    \n    # 3. standard 3x4 Layout Configuration\n    layout = [\n        ['I', 'aVR', 'V1', 'V4'],\n        ['II', 'aVL', 'V2', 'V5'],\n        ['III', 'aVF', 'V3', 'V6']\n    ]\n    \n    # --- KEY ADJUSTMENTS HERE ---\n    # Tighter row center baselines (moved up and closer together)\n    row_centers = [10.5, 7.5, 4.5] \n    \n    # Segment duration in the grid (2.5s per lead)\n    segment_duration = 2.5 \n    \n    # --- PLOT THE 3x4 GRID ---\n    for r, row_leads in enumerate(layout):\n        # Get the center line for this row\n        y_center = row_centers[r]\n        \n        for c, lead_name in enumerate(row_leads):\n            x_offset = c * segment_duration\n            \n            if lead_name in signals:\n                data = signals[lead_name]\n                samples_needed = int(segment_duration * sample_rate)\n                segment = data[:samples_needed]\n                \n                # Normalize [0,1]\n                norm_0_1 = normalize(segment)\n                \n                # --- VISUAL SCALING ADJUSTMENT ---\n                # Scale factor 1.0 means the signal generally fits in 1 big box height.\n                # Subtract 0.5 to center the signal on its baseline.\n                scaled_seg = (norm_0_1 - 0.5) * 1.0 \n                \n                t = np.linspace(x_offset, x_offset + segment_duration, len(scaled_seg))\n                \n                # Plot centered on the row baseline\n                ax.plot(t, scaled_seg + y_center, color='black', linewidth=1.1)\n                \n                # Label position tweaked to be closer to the trace\n                ax.text(x_offset + 0.05, y_center + 0.8, lead_name, fontsize=11, fontweight='bold', color='blue')\n\n    # --- PLOT RHYTHM STRIP (Bottom Row) ---\n    rhythm_lead = 'II'\n    # Center baseline for rhythm strip at bottom\n    rhythm_center = 1.5 \n    \n    if rhythm_lead in signals:\n        data = signals[rhythm_lead]\n        norm_0_1 = normalize(data)\n        # Same scaling as above\n        scaled_data = (norm_0_1 - 0.5) * 1.0\n        t_full = np.linspace(0, 10, len(scaled_data))\n        \n        # Plot at bottom baseline\n        ax.plot(t_full, scaled_data + rhythm_center, color='black', linewidth=1.1)\n        ax.text(0.05, rhythm_center + 0.8, rhythm_lead + \" Rhythm\", fontsize=11, fontweight='bold', color='blue')\n\n    # Cleanup look\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values(): spine.set_visible(False)\n    # Ensure grid lines are on top of white background but below signals\n    ax.set_axisbelow(True) \n\n    plt.tight_layout(pad=0.5)\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.show()\n    print(f\"Adjusted ECG Image generated at: {output_path}\")\n\n\n# =========================================\n# --- EXECUTION BLOCK (Update Path Here) ---\n# =========================================\n\n# 1. Find the file path automatically (for Kaggle)\nfilename = '1006427285.csv'\ncsv_file_path = None\nprint(f\"Searching for {filename}...\")\nfor root, dirs, files in os.walk('/kaggle/input/physionet-ecg-image-digitization/train/1006427285'):\n    if filename in files:\n        csv_file_path = os.path.join(root, filename)\n        print(f\"Found found: {csv_file_path}\")\n        break\n\n# 2. Run the generation if file found\nif csv_file_path:\n    try:\n        print(\"Processing data...\")\n        clean_signals = load_and_clean_data(csv_file_path)\n        \n        print(\"Generating tight-layout image...\")\n        # Using a new filename to distinguish it\n        plot_ecg_tight_layout(clean_signals, output_path='ecg_tight_print.png')\n        \n    except Exception as e:\n        print(f\"An error occurred: {e}\")\nelse:\n    print(\"\\nERROR: CSV file not found.\")\n    print(\"Please ensure the dataset is added to your Kaggle notebook.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:19.060851Z","iopub.execute_input":"2025-12-07T14:55:19.061148Z","iopub.status.idle":"2025-12-07T14:55:21.777895Z","shell.execute_reply.started":"2025-12-07T14:55:19.061127Z","shell.execute_reply":"2025-12-07T14:55:21.776802Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> We want better\n> ","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import AutoMinorLocator\nimport os\n\n# --- 1. Data Loading & Force Calculation ---\ndef load_and_clean_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    \n    # 1. Extract raw data (stripping whitespace from headers)\n    for col in df_raw.columns:\n        clean_col = col.strip() \n        valid_data = df_raw[col].dropna().values\n        if len(valid_data) > 0:\n            signals[clean_col] = valid_data\n\n    # 2. CRITICAL FIX: Calculate Lead I if missing (Standard Triangle)\n    # I = II - III\n    if 'II' in signals and 'III' in signals and 'I' not in signals:\n        signals['I'] = signals['II'] - signals['III']\n\n    # 3. CRITICAL FIX: Force Calculate Augmented Leads (The \"Middle Ones\")\n    # Many CSVs leave these empty. We calculate them to avoid flat lines.\n    # aVR = -(I + II) / 2\n    # aVL = I - (II / 2)\n    # aVF = II - (I / 2)\n    \n    if 'I' in signals and 'II' in signals:\n        # We perform the math on the available signals\n        # We trim to the shortest length to perform the math safely\n        min_len = min(len(signals['I']), len(signals['II']))\n        sI = signals['I'][:min_len]\n        sII = signals['II'][:min_len]\n        \n        # Calculate/Overwrite if missing or if data seems too flat (std dev close to 0)\n        if 'aVR' not in signals or np.std(signals['aVR']) < 0.01:\n            signals['aVR'] = -(sI + sII) / 2\n        \n        if 'aVL' not in signals or np.std(signals['aVL']) < 0.01:\n            signals['aVL'] = sI - (sII / 2)\n            \n        if 'aVF' not in signals or np.std(signals['aVF']) < 0.01:\n            signals['aVF'] = sII - (sI / 2)\n            \n    return signals\n\ndef normalize(signal):\n    if len(signal) == 0: return signal\n    sig_min = np.min(signal)\n    sig_max = np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    return (signal - sig_min) / (sig_max - sig_min)\n\n# --- 2. Plotting (Super Compressed Layout) ---\ndef plot_ecg_super_tight(signals, output_path=\"ecg_final_tight.png\", sample_rate=500):\n    \n    # A4 Landscape\n    fig = plt.figure(figsize=(11.69, 8.27)) \n    ax = fig.add_subplot(111)\n    \n    # --- GRID ---\n    # We use a 0-12 scale but compress the signals into specific bands\n    ax.set_ylim(0, 12) \n    ax.set_xlim(0, 10) \n    \n    # Major (Bold Red)\n    ax.xaxis.set_major_locator(plt.MultipleLocator(0.2))\n    ax.yaxis.set_major_locator(plt.MultipleLocator(1.0))\n    ax.grid(which='major', color='red', linestyle='-', linewidth=0.6, alpha=0.5)\n\n    # Minor (Pink)\n    ax.xaxis.set_minor_locator(AutoMinorLocator(5)) \n    ax.yaxis.set_minor_locator(AutoMinorLocator(5)) \n    ax.grid(which='minor', color='red', linestyle='-', linewidth=0.3, alpha=0.25)\n    \n    layout = [\n        ['I', 'aVR', 'V1', 'V4'],\n        ['II', 'aVL', 'V2', 'V5'],\n        ['III', 'aVF', 'V3', 'V6']\n    ]\n    \n    # --- ALIGNMENT ADJUSTMENT ---\n    # These Y-positions are chosen to put the signals \"closely in the same line\"\n    # relative to the grid blocks, minimizing wasted white space.\n    row_baselines = [10, 7, 4] \n    \n    segment_duration = 2.5 \n    \n    for r, row_leads in enumerate(layout):\n        y_base = row_baselines[r]\n        \n        for c, lead_name in enumerate(row_leads):\n            x_offset = c * segment_duration\n            \n            if lead_name in signals:\n                data = signals[lead_name]\n                samples_needed = int(segment_duration * sample_rate)\n                segment = data[:samples_needed]\n                \n                # Normalize 0-1\n                norm = normalize(segment)\n                \n                # SCALING: High Gain (1.5) to fill the gap\n                # Centering: Subtract 0.4 to pull it slightly down to the grid line\n                scaled_seg = (norm - 0.4) * 1.5 \n                \n                t = np.linspace(x_offset, x_offset + segment_duration, len(scaled_seg))\n                \n                # Plot\n                ax.plot(t, scaled_seg + y_base, color='black', linewidth=0.7)\n                \n                # Label: Positioned inside the grid box for compactness\n                ax.text(x_offset + 0.04, y_base + 1.2, lead_name, fontsize=10, fontweight='bold', color='navy')\n\n    # --- RHYTHM STRIP ---\n    rhythm_lead = 'II'\n    rhythm_base = 1.0 # Bottom row\n    \n    if rhythm_lead in signals:\n        data = signals[rhythm_lead]\n        norm = normalize(data)\n        scaled_data = (norm - 0.4) * 1.5\n        t_full = np.linspace(0, 10, len(scaled_data))\n        \n        ax.plot(t_full, scaled_data + rhythm_base, color='black', linewidth=0.7)\n        ax.text(0.04, rhythm_base + 1.2, rhythm_lead + \" Rhythm\", fontsize=10, fontweight='bold', color='navy')\n\n    # Cleanup\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values(): spine.set_visible(False)\n    ax.set_axisbelow(True) \n\n    plt.tight_layout(pad=0.2)\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.show()\n    print(f\"Final ECG Image generated at: {output_path}\")\n\n# =========================================\n# --- EXECUTION BLOCK ---\n# =========================================\nfilename = '1006427285.csv'\ncsv_file_path = None\nprint(f\"Searching for {filename}...\")\nfor root, dirs, files in os.walk('/kaggle/input/physionet-ecg-image-digitization/train/1006427285'):\n    if filename in files:\n        csv_file_path = os.path.join(root, filename)\n        break\n\nif csv_file_path:\n    try:\n        print(\"Processing data (Calculating missing leads if needed)...\")\n        clean_signals = load_and_clean_data(csv_file_path)\n        print(\"Generating image...\")\n        plot_ecg_super_tight(clean_signals, output_path='ecg_final_tight.png')\n    except Exception as e:\n        print(f\"An error occurred: {e}\")\nelse:\n    print(\"CSV file not found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:21.778847Z","iopub.execute_input":"2025-12-07T14:55:21.779097Z","iopub.status.idle":"2025-12-07T14:55:23.961530Z","shell.execute_reply.started":"2025-12-07T14:55:21.779078Z","shell.execute_reply":"2025-12-07T14:55:23.960685Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"*Hoccje na kneo\n*","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import AutoMinorLocator\nimport os\n\ndef load_and_fix_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    \n    # 1. Clean individual columns (remove empty rows)\n    for col in df_raw.columns:\n        clean_col = col.strip() \n        valid_data = df_raw[col].dropna().values\n        if len(valid_data) > 0:\n            signals[clean_col] = valid_data\n\n    # 2. FIX MISSING LEADS (The \"Middle Ones\" - aVR, aVL, aVF)\n    # If I and II are present, we can calculate everything else.\n    if 'I' in signals and 'II' in signals:\n        # Get the shortest length to match arrays\n        n = min(len(signals['I']), len(signals['II']))\n        sI = signals['I'][:n]\n        sII = signals['II'][:n]\n        \n        # Calculate Lead III if missing: III = II - I\n        if 'III' not in signals:\n            signals['III'] = sII - sI\n            \n        # Calculate aVR, aVL, aVF (Goldberger's equations)\n        # This fixes the \"flat line\" issue\n        if 'aVR' not in signals:\n            signals['aVR'] = -(sI + sII) / 2\n        if 'aVL' not in signals:\n            signals['aVL'] = sI - (sII / 2)\n        if 'aVF' not in signals:\n            signals['aVF'] = sII - (sI / 2)\n            \n    return signals\n\ndef normalize_and_center(signal):\n    \"\"\"Normalize to 0-1 range and then center around 0.5\"\"\"\n    if len(signal) == 0: return signal\n    sig_min = np.min(signal)\n    sig_max = np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    \n    # Normalize to 0-1\n    norm = (signal - sig_min) / (sig_max - sig_min)\n    # Center it (so 0.5 is the baseline)\n    return norm\n\ndef plot_ecg_reference_style(signals, output_path=\"ecg_final_reference.png\", sample_rate=500):\n    \n    # Standard Grid Layout (3 rows, 4 columns)\n    # Figure Size: Similar to a standard landscape paper print\n    fig = plt.figure(figsize=(16, 10))\n    ax = fig.add_subplot(111)\n    \n    # Axis Limits (Standard 10 seconds x 12 vertical units)\n    ax.set_xlim(0, 10)\n    ax.set_ylim(0, 12)\n    \n    # --- GRID STYLING (To match the reference) ---\n    # Major Grid (0.2s)\n    ax.xaxis.set_major_locator(plt.MultipleLocator(0.2))\n    ax.yaxis.set_major_locator(plt.MultipleLocator(1.0))\n    ax.grid(which='major', color='#ff5050', linestyle='-', linewidth=0.8, alpha=0.6)\n\n    # Minor Grid (0.04s)\n    ax.xaxis.set_minor_locator(AutoMinorLocator(5))\n    ax.yaxis.set_minor_locator(AutoMinorLocator(5))\n    ax.grid(which='minor', color='#ff5050', linestyle='-', linewidth=0.3, alpha=0.3)\n    \n    # 3x4 Layout\n    layout = [\n        ['I', 'aVR', 'V1', 'V4'],   # Row 1\n        ['II', 'aVL', 'V2', 'V5'],  # Row 2\n        ['III', 'aVF', 'V3', 'V6']  # Row 3\n    ]\n    \n    # Vertical Centers for the 3 rows\n    row_centers = [10.5, 7.5, 4.5]\n    segment_duration = 2.5\n    \n    for r, row_leads in enumerate(layout):\n        y_center = row_centers[r]\n        \n        for c, lead_name in enumerate(row_leads):\n            x_start = c * segment_duration\n            \n            if lead_name in signals:\n                data = signals[lead_name]\n                samples = int(segment_duration * sample_rate)\n                segment = data[:samples]\n                \n                # Normalize\n                norm_sig = normalize_and_center(segment)\n                \n                # Scale: 1.5 multiplier gives it the \"tall\" look of the reference\n                # Subtract 0.5 to center it exactly on the line\n                scaled_sig = (norm_sig - 0.5) * 1.5\n                \n                # Time vector\n                t = np.linspace(x_start, x_start + segment_duration, len(scaled_sig))\n                \n                # PLOT\n                ax.plot(t, scaled_sig + y_center, color='black', linewidth=0.9)\n                \n                # LABEL (Blue, Top Left of box)\n                ax.text(x_start + 0.05, y_center + 1.0, lead_name, fontsize=11, fontweight='bold', color='navy')\n\n    # --- RHYTHM STRIP (Lead II) ---\n    if 'II' in signals:\n        data = signals['II']\n        norm_sig = normalize_and_center(data)\n        scaled_sig = (norm_sig - 0.5) * 1.5\n        t_full = np.linspace(0, 10, len(scaled_sig))\n        \n        # Plot at bottom (centered at y=1.5)\n        ax.plot(t_full, scaled_sig + 1.5, color='black', linewidth=0.9)\n        ax.text(0.05, 2.5, \"II (Rhythm)\", fontsize=11, fontweight='bold', color='navy')\n\n    # Remove axes frame\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values(): spine.set_visible(False)\n    ax.set_axisbelow(True)\n\n    plt.tight_layout(pad=1.0)\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.show()\n    print(f\"Corrected ECG Image saved to: {output_path}\")\n\n# ==========================================\n# --- RUN THE CODE ---\n# ==========================================\nfilename = '1006427285.csv'\ncsv_file_path = None\n\n# Auto-find file\nfor root, dirs, files in os.walk('/kaggle/input/physionet-ecg-image-digitization/train/1006427285'):\n    if filename in files:\n        csv_file_path = os.path.join(root, filename)\n        break\n\nif csv_file_path:\n    print(\"Loading and fixing data...\")\n    # This function now CALCULATES the missing leads\n    clean_signals = load_and_fix_data(csv_file_path) \n    \n    print(\"Plotting reference style...\")\n    plot_ecg_reference_style(clean_signals)\nelse:\n    print(\"Error: File not found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:23.964452Z","iopub.execute_input":"2025-12-07T14:55:23.964751Z","iopub.status.idle":"2025-12-07T14:55:26.728557Z","shell.execute_reply.started":"2025-12-07T14:55:23.964729Z","shell.execute_reply":"2025-12-07T14:55:26.727537Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"offset lagabo hehe","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import AutoMinorLocator\nimport os\n\n# ==========================================\n# --- MANUAL CONTROL PANEL ---\n# ==========================================\n\n# 1. MOVE LEADS UP OR DOWN (Relative to their row center)\n# Positive = UP, Negative = DOWN\nMANUAL_Y_OFFSETS = {\n    'I': 1,   'aVR': 0,  'V1': 0.0,  'V4': 0.0,\n    'II': 0.0,  'aVL': 0.0,  'V2': 0.0,  'V5': 0.0,\n    'III': 0.0, 'aVF': 0.0,  'V3': 0.0,  'V6': 0.0\n}\n\n# 2. MAKE SIGNALS BIGGER OR SMALLER\n# 1.0 = Standard, 1.5 = Bigger, 0.5 = Smaller\n# If a specific lead is overlapping, reduce its gain here.\nMANUAL_GAINS = {\n    'I': 1.5,   'aVR': 1.5,  'V1': 1.5,  'V4': 1.5,\n    'II': 1.5,  'aVL': 1.5,  'V2': 1.5,  'V5': 1.5,\n    'III': 1.5, 'aVF': 1.5,  'V3': 1.5,  'V6': 1.5,\n    'Rhythm': 1.2 # Gain for the bottom strip\n}\n\n# ==========================================\n# --- CORE FUNCTIONS ---\n# ==========================================\n\ndef load_and_fix_data(csv_path):\n    df_raw = pd.read_csv(csv_path)\n    signals = {}\n    \n    # Clean data (Remove empty rows for each column)\n    for col in df_raw.columns:\n        clean_col = col.strip() \n        valid_data = df_raw[col].dropna().values\n        if len(valid_data) > 0:\n            signals[clean_col] = valid_data\n\n    # --- MATH FIX FOR MISSING LEADS ---\n    # 1. Fix Lead III\n    if 'II' in signals and 'I' in signals and 'III' not in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        signals['III'] = signals['II'][:n] - signals['I'][:n]\n        \n    # 2. Fix aVR, aVL, aVF (The \"Middle Ones\")\n    # If they are missing OR flat (std dev < 0.05), recalculate them\n    if 'I' in signals and 'II' in signals:\n        n = min(len(signals['I']), len(signals['II']))\n        sI = signals['I'][:n]\n        sII = signals['II'][:n]\n        \n        # Check if we need to overwrite flat/missing data\n        check_leads = ['aVR', 'aVL', 'aVF']\n        for lead in check_leads:\n            is_missing = lead not in signals\n            is_flat = False\n            if not is_missing:\n                if np.std(signals[lead]) < 0.05: # Threshold for \"flat line\"\n                    is_flat = True\n            \n            if is_missing or is_flat:\n                if lead == 'aVR': signals['aVR'] = -(sI + sII) / 2\n                if lead == 'aVL': signals['aVL'] = sI - (sII / 2)\n                if lead == 'aVF': signals['aVF'] = sII - (sI / 2)\n\n    return signals\n\ndef normalize_and_center(signal):\n    if len(signal) == 0: return signal\n    sig_min = np.min(signal)\n    sig_max = np.max(signal)\n    if sig_max == sig_min: return np.zeros_like(signal)\n    return (signal - sig_min) / (sig_max - sig_min)\n\ndef plot_ecg_manual(signals, output_path=\"ecg_manual_adjusted.png\", sample_rate=5000):\n    \n    fig = plt.figure(figsize=(20, 10))\n    ax = fig.add_subplot(111)\n    \n    # Grid Setup\n    ax.set_xlim(0, 10)\n    ax.set_ylim(0, 12)\n    \n    # Red Grid\n    ax.xaxis.set_major_locator(plt.MultipleLocator(0.2))\n    ax.yaxis.set_major_locator(plt.MultipleLocator(1.0))\n    ax.grid(which='major', color='#ff5050', linestyle='-', linewidth=0.8, alpha=0.6)\n    ax.xaxis.set_minor_locator(AutoMinorLocator(5))\n    ax.yaxis.set_minor_locator(AutoMinorLocator(5))\n    ax.grid(which='minor', color='#ff5050', linestyle='-', linewidth=0.3, alpha=0.3)\n    \n    # Layout Definition\n    layout = [\n        ['I', 'aVR', 'V1', 'V4'],\n        ['II', 'aVL', 'V2', 'V5'],\n        ['III', 'aVF', 'V3', 'V6']\n    ]\n    \n    # Row Base Lines (Can also be tweaked manually here)\n    row_centers = [10.5, 7.5, 4.5]\n    segment_duration = 2.5\n    \n    for r, row_leads in enumerate(layout):\n        base_y = row_centers[r]\n        \n        for c, lead_name in enumerate(row_leads):\n            x_start = c * segment_duration\n            \n            if lead_name in signals:\n                data = signals[lead_name]\n                samples = int(segment_duration * sample_rate)\n                segment = data[:samples]\n                \n                # Normalize\n                norm_sig = normalize_and_center(segment)\n                \n                # --- APPLY MANUAL ADJUSTMENTS ---\n                gain = MANUAL_GAINS.get(lead_name, 1.0)\n                y_adjust = MANUAL_Y_OFFSETS.get(lead_name, 0.0)\n                \n                # Apply Gain and Offset\n                scaled_sig = (norm_sig - 0.5) * gain\n                final_y = scaled_sig + base_y + y_adjust\n                \n                t = np.linspace(x_start, x_start + segment_duration, len(scaled_sig))\n                \n                ax.plot(t, final_y, color='black', linewidth=0.9)\n                ax.text(x_start + 0.05, base_y + y_adjust + 1.0, lead_name, fontsize=11, fontweight='bold', color='navy')\n\n    # Rhythm Strip\n    if 'II' in signals:\n        data = signals['II']\n        norm_sig = normalize_and_center(data)\n        \n        gain = MANUAL_GAINS.get('Rhythm', 1.2)\n        scaled_sig = (norm_sig - 0.5) * gain\n        \n        t_full = np.linspace(0, 10, len(scaled_sig))\n        ax.plot(t_full, scaled_sig + 1.5, color='black', linewidth=0.9)\n        ax.text(0.05, 2.5, \"II (Rhythm)\", fontsize=11, fontweight='bold', color='navy')\n\n    # Cleanup\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values(): spine.set_visible(False)\n    ax.set_axisbelow(True)\n\n    plt.tight_layout(pad=1.0)\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.show()\n    print(f\"Generated with manual adjustments: {output_path}\")\n\n# ==========================================\n# --- EXECUTION ---\n# ==========================================\nfilename = '1006427285.csv'\ncsv_file_path = None\n\nfor root, dirs, files in os.walk('/kaggle/input/physionet-ecg-image-digitization/train/1006427285'):\n    if filename in files:\n        csv_file_path = os.path.join(root, filename)\n        break\n\nif csv_file_path:\n    print(\"Loading Data...\")\n    clean_signals = load_and_fix_data(csv_file_path)\n    print(\"Plotting...\")\n    plot_ecg_manual(clean_signals)\nelse:\n    print(\"CSV File Not Found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:26.729627Z","iopub.execute_input":"2025-12-07T14:55:26.729899Z","iopub.status.idle":"2025-12-07T14:55:29.703015Z","shell.execute_reply.started":"2025-12-07T14:55:26.729879Z","shell.execute_reply":"2025-12-07T14:55:29.702044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import MultipleLocator\n\ndef generate_ecg_image_from_csv(\n    csv_path: str,\n    output_path: str,\n    sample_rate: float = 500.0\n):\n    \"\"\"\n    Load a multi-lead ECG from a CSV file, fill missing values, compute derived leads\n    and save a paper-style ECG image.\n    \n    Parameters\n    ----------\n    csv_path : str\n        Path to the input CSV (header row should contain lead names).\n    output_path : str\n        Path where the PNG image will be saved.\n    sample_rate : float, optional\n        Sampling rate of the ECG (Hz).  Adjust if your data uses a different rate.\n    \"\"\"\n    # Load the CSV.\n    df = pd.read_csv(csv_path)\n    # Reconstruct lead I if missing: I + III = II → I = II – III\n    if set([\"I\", \"II\", \"III\"]).issubset(df.columns):\n        missing_I = df[\"I\"].isna()\n        if missing_I.any():\n            df.loc[missing_I, \"I\"] = df.loc[missing_I, \"II\"] - df.loc[missing_I, \"III\"]\n    # Derive augmented leads when I and II exist.\n    if \"I\" in df.columns and \"II\" in df.columns:\n        df[\"aVR\"] = df[\"aVR\"].fillna(-(df[\"I\"] + df[\"II\"]) / 2)\n        df[\"aVL\"] = df[\"aVL\"].fillna(df[\"I\"] - df[\"II\"] / 2)\n        df[\"aVF\"] = df[\"aVF\"].fillna(df[\"II\"] - df[\"I\"] / 2)\n    # Drop any columns that are entirely NaN.\n    df = df.loc[:, df.notna().any()]\n    # Interpolate each lead to remove NaNs at internal points and extend at ends.\n    df = df.interpolate(method=\"linear\", limit_direction=\"both\")\n    # Build a time vector.\n    time = np.arange(len(df)) / sample_rate\n\n    # Prepare signals and names.\n    signals = [df[col].to_numpy() for col in df.columns]\n    lead_names = list(df.columns)\n    n_leads = len(signals)\n\n    # Determine vertical spacing: compute the maximum amplitude range and offset each lead.\n    ranges = [np.nanmax(sig) - np.nanmin(sig) for sig in signals]\n    max_range = max(ranges)\n    offsets = [i * (max_range + 1.0) for i in range(n_leads)]\n\n    # Create a single chart (no subplots) and plot each lead with its offset.\n    plt.figure(figsize=(12, max(6.0, 1.5 * n_leads)))\n    for i, sig in enumerate(signals):\n        offset_sig = sig + offsets[i]\n        plt.plot(time, offset_sig)\n        # Label each trace on the left.\n        plt.text(\n            time[0],\n            offsets[i] + 0.5 * ranges[i],\n            lead_names[i],\n            verticalalignment=\"bottom\"\n        )\n\n    # Configure major/minor grid lines similar to ECG paper.\n    ax = plt.gca()\n    ax.xaxis.set_major_locator(MultipleLocator(0.2))    # major grid every 0.2 s\n    ax.xaxis.set_minor_locator(MultipleLocator(0.04))   # minor grid every 0.04 s\n    ax.yaxis.set_major_locator(MultipleLocator(max_range))\n    ax.yaxis.set_minor_locator(MultipleLocator(max_range / 5))\n    ax.grid(which=\"major\", linestyle=\"-\")\n    ax.grid(which=\"minor\", linestyle=\":\", linewidth=0.5)\n\n    plt.xlabel(\"Time (seconds)\")\n    plt.yticks([])  # hide y‑axis tick labels; each lead is labelled manually\n    plt.title(\"ECG Leads\")\n    # Save the figure to disk.\n    plt.savefig(output_path, dpi=300, bbox_inches=\"tight\")\n    plt.close()\n\n# Example usage:\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:29.704177Z","iopub.execute_input":"2025-12-07T14:55:29.704947Z","iopub.status.idle":"2025-12-07T14:55:29.718401Z","shell.execute_reply.started":"2025-12-07T14:55:29.704923Z","shell.execute_reply":"2025-12-07T14:55:29.717453Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":" generate_ecg_image_from_csv(\"/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv\", \"/kaggle/working/my_ecg.png\", sample_rate=500)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:29.719440Z","iopub.execute_input":"2025-12-07T14:55:29.719879Z","iopub.status.idle":"2025-12-07T14:55:33.980427Z","shell.execute_reply.started":"2025-12-07T14:55:29.719849Z","shell.execute_reply":"2025-12-07T14:55:33.979513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float = 500.0):\n    \"\"\"\n    Generate a 3×4 ECG print with red grid lines from a CSV of multi‑lead ECG data.\n    - Assumes leads: I, II, III, aVR, aVL, aVF, V1–V6 (missing leads will be derived or skipped).\n    - Displays columns of 2.5 seconds each (total 10 seconds) and a rhythm strip across the bottom.\n    \"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Reconstruct lead I when missing: I + III = II → I = II – III\n    if {'I','II','III'}.issubset(df.columns):\n        missing_I = df['I'].isna()\n        df.loc[missing_I, 'I'] = df.loc[missing_I, 'II'] - df.loc[missing_I, 'III']\n\n    # Derive augmented leads when I and II exist\n    if 'I' in df.columns and 'II' in df.columns:\n        df['aVR'] = df['aVR'].fillna(-(df['I'] + df['II']) / 2)\n        df['aVL'] = df['aVL'].fillna(df['I'] - df['II'] / 2)\n        df['aVF'] = df['aVF'].fillna(df['II'] - df['I'] / 2)\n\n    # Drop columns that remain all NaN and interpolate remaining values\n    df = df.loc[:, df.notna().any()]\n    for col in df.columns:\n        df[col] = df[col].interpolate(method='linear', limit_direction='both')\n\n    segment_samples = int(2.5 * sample_rate)        # samples per 2.5 s segment\n    num_samples     = segment_samples * 4           # 10 seconds total\n\n    # Define rows of the 3×4 print; rhythm lead prints across entire width\n    rows = [\n        ['I',   'aVR', 'V1', 'V4'],\n        ['II',  'aVL', 'V2', 'V5'],\n        ['III', 'aVF', 'V3', 'V6'],\n    ]\n    rhythm_lead = 'II'\n\n    # Normalization function to scale each segment to the same vertical range\n    def normalize(signal):\n        sig = signal - np.mean(signal)\n        span = np.max(sig) - np.min(sig)\n        return sig / (span if span != 0 else 1.0)\n\n    # Vertical spacing between rows (units are arbitrary)\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    # Row offsets: top row highest, rhythm row lowest\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n\n    # Prepare figure\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid lines; major every 0.2 s (x) and 1.0 units (y), minor every 0.04 s and 0.2 units\n    total_time = 10.0\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each lead segment in its column and row\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx, lead in enumerate(lead_row):\n            if lead not in df.columns:\n                continue\n            signal = df[lead].iloc[:num_samples].to_numpy()\n            seg = signal[col_idx * segment_samples : (col_idx + 1) * segment_samples]\n            seg_norm = normalize(seg)\n            t = np.linspace(0, 2.5, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off)\n            # Lead label\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Plot the rhythm strip (full 10 s of a chosen lead) at the bottom\n    if rhythm_lead in df.columns:\n        y_off = row_offsets[-1]\n        signal = df[rhythm_lead].iloc[:num_samples].to_numpy()\n        seg_norm = normalize(signal)\n        t_full = np.linspace(0, total_time, len(seg_norm))\n        ax.plot(t_full, seg_norm + y_off)\n        ax.text(0.05, y_off + 0.5, rhythm_lead, fontsize=8)\n\n    # Adjust axes: hide labels and spines\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save the figure\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\n# Example usage:\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:33.981450Z","iopub.execute_input":"2025-12-07T14:55:33.981730Z","iopub.status.idle":"2025-12-07T14:55:34.001534Z","shell.execute_reply.started":"2025-12-07T14:55:33.981709Z","shell.execute_reply":"2025-12-07T14:55:34.000699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"generate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', '/kaggle/working/ecg_print.png', sample_rate=500)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:34.002980Z","iopub.execute_input":"2025-12-07T14:55:34.003402Z","iopub.status.idle":"2025-12-07T14:55:35.665162Z","shell.execute_reply.started":"2025-12-07T14:55:34.003373Z","shell.execute_reply":"2025-12-07T14:55:35.664236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile vrest.py\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float = 500.0):\n    \"\"\"\n    Generate a 3×4 ECG print with red grid lines and black signals from a CSV of multi‑lead ECG data.\n    - Assumes leads: I, II, III, aVR, aVL, aVF, V1–V6 (missing leads will be derived or skipped).\n    - Displays columns of 2.5 seconds each (total 10 seconds) and a rhythm strip across the bottom.\n    \"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Reconstruct lead I when missing: I + III = II → I = II – III\n    if {'I','II','III'}.issubset(df.columns):\n        missing_I = df['I'].isna()\n        df.loc[missing_I, 'I'] = df.loc[missing_I, 'II'] - df.loc[missing_I, 'III']\n\n    # Derive augmented leads when I and II exist\n    if 'I' in df.columns and 'II' in df.columns:\n        df['aVR'] = df['aVR'].fillna(-(df['I'] + df['II']) / 2)\n        df['aVL'] = df['aVL'].fillna(df['I'] - df['II'] / 2)\n        df['aVF'] = df['aVF'].fillna(df['II'] - df['I'] / 2)\n\n    # Drop columns that are entirely NaN and interpolate remaining values\n    df = df.loc[:, df.notna().any()]\n    for col in df.columns:\n        df[col] = df[col].interpolate(method='linear', limit_direction='both')\n\n    segment_samples = int(2.5 * sample_rate)        # samples per 2.5 s segment\n    num_samples     = segment_samples * 4           # 10 seconds total\n\n    # Define rows of the 3×4 print; rhythm lead prints across entire width\n    rows = [\n        ['I',   'aVR', 'V1', 'V4'],\n        ['II',  'aVL', 'V2', 'V5'],\n        ['III', 'aVF', 'V3', 'V6'],\n    ]\n    rhythm_lead = 'II'\n\n    # Normalization function to scale each segment to the same vertical range\n    def normalize(signal):\n        sig = signal - np.mean(signal)\n        span = np.max(sig) - np.min(sig)\n        return sig / (span if span != 0 else 1.0)\n\n    # Vertical spacing between rows (units are arbitrary)\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    # Row offsets: top row highest, rhythm row lowest\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n\n    # Prepare figure\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid lines; major every 0.2 s (x) and 1.0 units (y), minor every 0.04 s and 0.2 units\n    total_time = 10.0\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each lead segment in its column and row\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx, lead in enumerate(lead_row):\n            if lead not in df.columns:\n                continue\n            signal = df[lead].iloc[:num_samples].to_numpy()\n            seg = signal[col_idx * segment_samples : (col_idx + 1) * segment_samples]\n            seg_norm = normalize(seg)\n            t = np.linspace(0, 2.5, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off, color='black')\n            # Lead label\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Plot the rhythm strip (full 10 s of a chosen lead) at the bottom\n    if rhythm_lead in df.columns:\n        y_off = row_offsets[-1]\n        signal = df[rhythm_lead].iloc[:num_samples].to_numpy()\n        seg_norm = normalize(signal)\n        t_full = np.linspace(0, total_time, len(seg_norm))\n        ax.plot(t_full, seg_norm + y_off, color='black')\n        ax.text(0.05, y_off + 0.5, rhythm_lead, fontsize=8)\n\n    # Adjust axes: hide labels and spines\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save the figure\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\n# Example usage in Kaggle:\n# generate_ecg_print('/kaggle/input/ecg-data/1006427285.csv',\n#                    '/kaggle/working/ecg_print_black_signals.png',\n#                    sample_rate=500)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:35.666363Z","iopub.execute_input":"2025-12-07T14:55:35.667267Z","iopub.status.idle":"2025-12-07T14:55:35.675499Z","shell.execute_reply.started":"2025-12-07T14:55:35.667233Z","shell.execute_reply":"2025-12-07T14:55:35.674696Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"generate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv',\n                   '/kaggle/working/ecg_print_black_signals.png',\n                   sample_rate=500)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:35.676491Z","iopub.execute_input":"2025-12-07T14:55:35.676765Z","iopub.status.idle":"2025-12-07T14:55:37.503833Z","shell.execute_reply.started":"2025-12-07T14:55:35.676745Z","shell.execute_reply":"2025-12-07T14:55:37.502977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float | None = None):\n    \"\"\"\n    Generate a 3×4 ECG print with red grid lines and black signals from a CSV of multi‑lead ECG data.\n    Automatically infers sample rate if not provided and trims leading NaNs to prevent flat lines.\n    \"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Reconstruct I from II and III if needed\n    if {'I','II','III'}.issubset(df.columns):\n        missing_I = df['I'].isna()\n        df.loc[missing_I, 'I'] = df.loc[missing_I, 'II'] - df.loc[missing_I, 'III']\n\n    # Derive augmented leads\n    if 'I' in df.columns and 'II' in df.columns:\n        df['aVR'] = df['aVR'].fillna(-(df['I'] + df['II']) / 2)\n        df['aVL'] = df['aVL'].fillna(df['I'] - df['II'] / 2)\n        df['aVF'] = df['aVF'].fillna(df['II'] - df['I'] / 2)\n\n    # Drop columns that are entirely NaN\n    df = df.loc[:, df.notna().any()]\n\n    # Trim start of record to remove leading NaNs across all leads\n    start_idx = 0\n    for col in df.columns:\n        first_valid = df[col].first_valid_index()\n        if first_valid is not None:\n            start_idx = max(start_idx, int(first_valid))\n    df = df.iloc[start_idx:].reset_index(drop=True)\n\n    # Interpolate any remaining missing values\n    for col in df.columns:\n        df[col] = df[col].interpolate(method='linear', limit_direction='both')\n\n    # Infer sample rate from record length if not provided (assumes ~10‑s record)\n    duration_sec = 10.0\n    if sample_rate is None:\n        sample_rate = len(df) / duration_sec\n\n    segment_samples = int(round(2.5 * sample_rate))\n    num_segments = min(4, int(np.ceil(len(df) / segment_samples)))\n\n    # Layout: 3 rows of four leads + rhythm strip\n    rows = [\n        ['I','aVR','V1','V4'],\n        ['II','aVL','V2','V5'],\n        ['III','aVF','V3','V6'],\n    ]\n    rhythm_lead = 'II'\n\n    def normalize(signal: np.ndarray) -> np.ndarray:\n        sig = signal - np.mean(signal)\n        span = np.max(sig) - np.min(sig)\n        return sig / (span if span != 0 else 1.0)\n\n    # Vertical offsets for each row\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n\n    # Total time spanned by the columns\n    total_time = 2.5 * num_segments\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid (major: 0.2 s / 1 unit; minor: 0.04 s / 0.2 units)\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each 2.5‑s segment for each lead\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx in range(num_segments):\n            lead = lead_row[col_idx] if col_idx < len(lead_row) else None\n            if lead is None or lead not in df.columns:\n                continue\n            signal = df[lead].to_numpy()\n            start = col_idx * segment_samples\n            end = min(start + segment_samples, len(signal))\n            seg = signal[start:end]\n            if len(seg) == 0:\n                continue\n            seg_norm = normalize(seg)\n            seg_duration = (end - start) / sample_rate\n            t = np.linspace(0, seg_duration, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off, color='black')\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Rhythm strip across the bottom\n    if rhythm_lead in df.columns:\n        y_off = row_offsets[-1]\n        signal = df[rhythm_lead].to_numpy()\n        seg_norm = normalize(signal)\n        t_full = np.linspace(0, len(signal) / sample_rate, len(seg_norm))\n        ax.plot(t_full, seg_norm + y_off, color='black')\n        ax.text(0.05, y_off + 0.5, rhythm_lead, fontsize=8)\n\n    # Final axis adjustments\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save output\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\n# Example usage:\n# generate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', 'adjusted_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:37.504728Z","iopub.execute_input":"2025-12-07T14:55:37.504973Z","iopub.status.idle":"2025-12-07T14:55:37.525842Z","shell.execute_reply.started":"2025-12-07T14:55:37.504949Z","shell.execute_reply":"2025-12-07T14:55:37.524625Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"generate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', 'adjusted_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:37.527073Z","iopub.execute_input":"2025-12-07T14:55:37.527377Z","iopub.status.idle":"2025-12-07T14:55:39.388677Z","shell.execute_reply.started":"2025-12-07T14:55:37.527348Z","shell.execute_reply":"2025-12-07T14:55:39.387624Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# Load the file\ncsv_path = '/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv'\ndf = pd.read_csv(csv_path)\n\n# Inspect the dataframe\nprint('Columns:', df.columns)\nprint('Head of the dataset:', df.head())\n\n# Check for any NaN values\nprint('Missing values in each column:', df.isna().sum())\n\n# Check for first and last valid indices for the leads\nfor col in df.columns:\n    print(f'{col}: first_valid_index={df[col].first_valid_index()}, last_valid_index={df[col].last_valid_index()}')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:39.389730Z","iopub.execute_input":"2025-12-07T14:55:39.389995Z","iopub.status.idle":"2025-12-07T14:55:39.430526Z","shell.execute_reply.started":"2025-12-07T14:55:39.389976Z","shell.execute_reply":"2025-12-07T14:55:39.429484Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float = None):\n    \"\"\"\n    Generate a 3×4 ECG print with red grid lines and black signals from a CSV of multi‑lead ECG data.\n    Automatically infers sample rate if not provided and trims leading NaNs to prevent flat lines.\n    \"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Reconstruct I from II and III if needed\n    if {'I','II','III'}.issubset(df.columns):\n        missing_I = df['I'].isna()\n        df.loc[missing_I, 'I'] = df.loc[missing_I, 'II'] - df.loc[missing_I, 'III']\n\n    # Derive augmented leads\n    if 'I' in df.columns and 'II' in df.columns:\n        df['aVR'] = df['aVR'].fillna(-(df['I'] + df['II']) / 2)\n        df['aVL'] = df['aVL'].fillna(df['I'] - df['II'] / 2)\n        df['aVF'] = df['aVF'].fillna(df['II'] - df['I'] / 2)\n\n    # Drop columns that are entirely NaN\n    df = df.loc[:, df.notna().any()]\n\n    # Trim start of record to remove leading NaNs across all leads\n    start_idx = 0\n    for col in df.columns:\n        first_valid = df[col].first_valid_index()\n        if first_valid is not None:\n            start_idx = max(start_idx, int(first_valid))\n    df = df.iloc[start_idx:].reset_index(drop=True)\n\n    # Interpolate any remaining missing values (use forward fill for the leads with trailing NaNs)\n    for col in df.columns:\n        df[col] = df[col].interpolate(method='linear', limit_direction='both')\n\n    # Infer sample rate from record length if not provided (assumes ~10‑s record)\n    duration_sec = 10.0\n    if sample_rate is None:\n        sample_rate = len(df) / duration_sec\n\n    segment_samples = int(round(2.5 * sample_rate))\n    num_segments = min(4, int(np.ceil(len(df) / segment_samples)))\n\n    # Layout: 3 rows of four leads + rhythm strip\n    rows = [\n        ['I','aVR','V1','V4'],\n        ['II','aVL','V2','V5'],\n        ['III','aVF','V3','V6'],\n    ]\n    rhythm_lead = 'II'\n\n    def normalize(signal: np.ndarray) -> np.ndarray:\n        sig = signal - np.mean(signal)\n        span = np.max(sig) - np.min(sig)\n        return sig / (span if span != 0 else 1.0)\n\n    # Vertical offsets for each row\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n\n    # Total time spanned by the columns\n    total_time = 2.5 * num_segments\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid (major: 0.2 s / 1 unit; minor: 0.04 s / 0.2 units)\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each 2.5‑s segment for each lead\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx in range(num_segments):\n            lead = lead_row[col_idx] if col_idx < len(lead_row) else None\n            if lead is None or lead not in df.columns:\n                continue\n            signal = df[lead].to_numpy()\n            start = col_idx * segment_samples\n            end = min(start + segment_samples, len(signal))\n            seg = signal[start:end]\n            if len(seg) == 0:\n                continue\n            seg_norm = normalize(seg)\n            seg_duration = (end - start) / sample_rate\n            t = np.linspace(0, seg_duration, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off, color='black')\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Rhythm strip across the bottom\n    if rhythm_lead in df.columns:\n        y_off = row_offsets[-1]\n        signal = df[rhythm_lead].to_numpy()\n        seg_norm = normalize(signal)\n        t_full = np.linspace(0, len(signal) / sample_rate, len(seg_norm))\n        ax.plot(t_full, seg_norm + y_off, color='black')\n        ax.text(0.05, y_off + 0.5, rhythm_lead, fontsize=8)\n\n    # Final axis adjustments\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save output\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\n# Example usage:\n# generate_ecg_print('1006427285.csv', 'adjusted_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:39.431763Z","iopub.execute_input":"2025-12-07T14:55:39.432126Z","iopub.status.idle":"2025-12-07T14:55:39.454662Z","shell.execute_reply.started":"2025-12-07T14:55:39.432097Z","shell.execute_reply":"2025-12-07T14:55:39.453677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"generate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', 'see1_ecg.png')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:39.455842Z","iopub.execute_input":"2025-12-07T14:55:39.456192Z","iopub.status.idle":"2025-12-07T14:55:41.066408Z","shell.execute_reply.started":"2025-12-07T14:55:39.456163Z","shell.execute_reply":"2025-12-07T14:55:41.065227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float = None):\n    \"\"\"\n    Generate a 3×4 ECG print with red grid lines and black signals from a CSV of multi‑lead ECG data.\n    Automatically infers sample rate if not provided and trims leading NaNs to prevent flat lines.\n    \"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Reconstruct I from II and III if needed\n    if {'I','II','III'}.issubset(df.columns):\n        missing_I = df['I'].isna()\n        df.loc[missing_I, 'I'] = df.loc[missing_I, 'II'] - df.loc[missing_I, 'III']\n\n    # Derive augmented leads\n    if 'I' in df.columns and 'II' in df.columns:\n        df['aVR'] = df['aVR'].fillna(-(df['I'] + df['II']) / 2)\n        df['aVL'] = df['aVL'].fillna(df['I'] - df['II'] / 2)\n        df['aVF'] = df['aVF'].fillna(df['II'] - df['I'] / 2)\n\n    # Drop columns that are entirely NaN\n    df = df.loc[:, df.notna().any()]\n\n    # Trim start of record to remove leading NaNs across all leads\n    start_idx = 0\n    for col in df.columns:\n        first_valid = df[col].first_valid_index()\n        if first_valid is not None:\n            start_idx = max(start_idx, int(first_valid))\n    df = df.iloc[start_idx:].reset_index(drop=True)\n\n    # Interpolate any remaining missing values (forward/backward fill for better continuity)\n    for col in df.columns:\n        df[col] = df[col].fillna(method='ffill').fillna(method='bfill')  # Forward and Backward fill\n\n    # Infer sample rate from record length if not provided (assumes ~10‑s record)\n    duration_sec = 10.0\n    if sample_rate is None:\n        sample_rate = len(df) / duration_sec\n\n    segment_samples = int(round(2.5 * sample_rate))\n    num_segments = min(4, int(np.ceil(len(df) / segment_samples)))\n\n    # Layout: 3 rows of four leads + rhythm strip\n    rows = [\n        ['I','aVR','V1','V4'],\n        ['II','aVL','V2','V5'],\n        ['III','aVF','V3','V6'],\n    ]\n    rhythm_lead = 'II'\n\n    def normalize(signal: np.ndarray) -> np.ndarray:\n        sig = signal - np.mean(signal)\n        span = np.max(sig) - np.min(sig)\n        return sig / (span if span != 0 else 1.0)\n\n    # Vertical offsets for each row\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n\n    # Total time spanned by the columns\n    total_time = 2.5 * num_segments\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid (major: 0.2 s / 1 unit; minor: 0.04 s / 0.2 units)\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each 2.5‑s segment for each lead\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx in range(num_segments):\n            lead = lead_row[col_idx] if col_idx < len(lead_row) else None\n            if lead is None or lead not in df.columns:\n                continue\n            signal = df[lead].to_numpy()\n            start = col_idx * segment_samples\n            end = min(start + segment_samples, len(signal))\n            seg = signal[start:end]\n            if len(seg) == 0:\n                continue\n            seg_norm = normalize(seg)\n            seg_duration = (end - start) / sample_rate\n            t = np.linspace(0, seg_duration, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off, color='black')\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Rhythm strip across the bottom\n    if rhythm_lead in df.columns:\n        y_off = row_offsets[-1]\n        signal = df[rhythm_lead].to_numpy()\n        seg_norm = normalize(signal)\n        t_full = np.linspace(0, len(signal) / sample_rate, len(seg_norm))\n        ax.plot(t_full, seg_norm + y_off, color='black')\n        ax.text(0.05, y_off + 0.5, rhythm_lead, fontsize=8)\n\n    # Final axis adjustments\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save output\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\n# Example usage:\ngenerate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', 'see2_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:41.067601Z","iopub.execute_input":"2025-12-07T14:55:41.067972Z","iopub.status.idle":"2025-12-07T14:55:42.835806Z","shell.execute_reply.started":"2025-12-07T14:55:41.067943Z","shell.execute_reply":"2025-12-07T14:55:42.834860Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Image\nImage(filename='/kaggle/working/my_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:42.836700Z","iopub.execute_input":"2025-12-07T14:55:42.836933Z","iopub.status.idle":"2025-12-07T14:55:42.871352Z","shell.execute_reply.started":"2025-12-07T14:55:42.836914Z","shell.execute_reply":"2025-12-07T14:55:42.870175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile v46.py\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float = None):\n    \"\"\"\n    Generate a 3×4 ECG print with red grid lines and black signals from a CSV of multi‑lead ECG data.\n    Applies selective interpolation to avoid over-smoothing or trimming the leads that are valid.\n    \"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Reconstruct I from II and III if needed\n    if {'I','II','III'}.issubset(df.columns):\n        missing_I = df['I'].isna()\n        df.loc[missing_I, 'I'] = df.loc[missing_I, 'II'] - df.loc[missing_I, 'III']\n\n    # Derive augmented leads\n    if 'I' in df.columns and 'II' in df.columns:\n        df['aVR'] = df['aVR'].fillna(-(df['I'] + df['II']) / 2)\n        df['aVL'] = df['aVL'].fillna(df['I'] - df['II'] / 2)\n        df['aVF'] = df['aVF'].fillna(df['II'] - df['I'] / 2)\n\n    # Drop columns that are entirely NaN\n    df = df.loc[:, df.notna().any()]\n\n    # Trim start of record to remove leading NaNs across all leads\n    start_idx = 0\n    for col in df.columns:\n        first_valid = df[col].first_valid_index()\n        if first_valid is not None:\n            start_idx = max(start_idx, int(first_valid))\n    df = df.iloc[start_idx:].reset_index(drop=True)\n\n    # Selectively interpolate missing values based on the lead\n    for col in df.columns:\n        if col in ['V4', 'V5', 'V6', 'III']:  # Leads that need more aggressive interpolation\n            df[col] = df[col].fillna(method='ffill').fillna(method='bfill')\n        elif col in ['aVR', 'aVF']:  # Keep these leads more constant and minimize interpolation\n            df[col] = df[col].fillna(method='ffill').fillna(method='bfill')\n        else:  # For other leads, just forward fill\n            df[col] = df[col].fillna(method='ffill')\n\n    # Infer sample rate from record length if not provided (assumes ~10‑s record)\n    duration_sec = 10.0\n    if sample_rate is None:\n        sample_rate = len(df) / duration_sec\n\n    segment_samples = int(round(2.5 * sample_rate))\n    num_segments = min(4, int(np.ceil(len(df) / segment_samples)))\n\n    # Layout: 3 rows of four leads + rhythm strip\n    rows = [\n        ['I','aVR','V1','V4'],\n        ['II','aVL','V2','V5'],\n        ['III','aVF','V3','V6'],\n    ]\n    rhythm_lead = 'II'\n\n    def normalize(signal: np.ndarray) -> np.ndarray:\n        sig = signal - np.mean(signal)\n        span = np.max(sig) - np.min(sig)\n        return sig / (span if span != 0 else 1.0)\n\n    # Vertical offsets for each row\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n\n    # Total time spanned by the columns\n    total_time = 2.5 * num_segments\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid (major: 0.2 s / 1 unit; minor: 0.04 s / 0.2 units)\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each 2.5‑s segment for each lead\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx in range(num_segments):\n            lead = lead_row[col_idx] if col_idx < len(lead_row) else None\n            if lead is None or lead not in df.columns:\n                continue\n            signal = df[lead].to_numpy()\n            start = col_idx * segment_samples\n            end = min(start + segment_samples, len(signal))\n            seg = signal[start:end]\n            if len(seg) == 0:\n                continue\n            seg_norm = normalize(seg)\n            seg_duration = (end - start) / sample_rate\n            t = np.linspace(0, seg_duration, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off, color='black')\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Rhythm strip across the bottom\n    if rhythm_lead in df.columns:\n        y_off = row_offsets[-1]\n        signal = df[rhythm_lead].to_numpy()\n        seg_norm = normalize(signal)\n        t_full = np.linspace(0, len(signal) / sample_rate, len(seg_norm))\n        ax.plot(t_full, seg_norm + y_off, color='black')\n        ax.text(0.05, y_off + 0.5, rhythm_lead, fontsize=8)\n\n    # Final axis adjustments\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save output\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\n# Example usage:\n# generate_ecg_print('1006427285.csv', 'adjusted_ecg.png')\n\n# Example usage:\ngenerate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', 'hopeful_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:42.872389Z","iopub.execute_input":"2025-12-07T14:55:42.872674Z","iopub.status.idle":"2025-12-07T14:55:42.881041Z","shell.execute_reply.started":"2025-12-07T14:55:42.872624Z","shell.execute_reply":"2025-12-07T14:55:42.880196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Image\nImage(filename='/kaggle/working/ecg_print_black_signals.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:42.882158Z","iopub.execute_input":"2025-12-07T14:55:42.882469Z","iopub.status.idle":"2025-12-07T14:55:42.914522Z","shell.execute_reply.started":"2025-12-07T14:55:42.882447Z","shell.execute_reply":"2025-12-07T14:55:42.913300Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> merging twi shits","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float = None):\n    \"\"\"\n    Generate a 3×4 ECG print with red grid lines and black signals from a CSV of multi‑lead ECG data.\n    Applies selective interpolation to avoid over-smoothing or trimming the leads that are valid.\n    \"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Reconstruct I from II and III if needed\n    if {'I','II','III'}.issubset(df.columns):\n        missing_I = df['I'].isna()\n        df.loc[missing_I, 'I'] = df.loc[missing_I, 'II'] - df.loc[missing_I, 'III']\n\n    # Derive augmented leads\n    if 'I' in df.columns and 'II' in df.columns:\n        df['aVR'] = df['aVR'].fillna(-(df['I'] + df['II']) / 2)\n        df['aVL'] = df['aVL'].fillna(df['I'] - df['II'] / 2)\n        df['aVF'] = df['aVF'].fillna(df['II'] - df['I'] / 2)\n\n    # Drop columns that are entirely NaN\n    df = df.loc[:, df.notna().any()]\n\n    # Trim start of record to remove leading NaNs across all leads\n    start_idx = 0\n    for col in df.columns:\n        first_valid = df[col].first_valid_index()\n        if first_valid is not None:\n            start_idx = max(start_idx, int(first_valid))\n    df = df.iloc[start_idx:].reset_index(drop=True)\n\n    # Interpolate selectively for each lead\n    for col in df.columns:\n        if col in ['V4', 'V5', 'V6', 'V1', 'V2', 'V3']:  # Leads that need more aggressive interpolation\n            df[col] = df[col].fillna(method='ffill').fillna(method='bfill')\n        elif col in ['aVR', 'aVF']:  # Keep these leads more constant and minimize interpolation\n            df[col] = df[col].fillna(method='ffill').fillna(method='bfill')\n        else:  # For other leads, just forward fill\n            df[col] = df[col].fillna(method='ffill')\n\n    # Infer sample rate from record length if not provided (assumes ~10‑s record)\n    duration_sec = 10.0\n    if sample_rate is None:\n        sample_rate = len(df) / duration_sec\n\n    segment_samples = int(round(2.5 * sample_rate))\n    num_segments = min(4, int(np.ceil(len(df) / segment_samples)))\n\n    # Layout: 3 rows of four leads + rhythm strip\n    rows = [\n        ['I','aVR','V1','V4'],\n        ['II','aVL','V2','V5'],\n        ['III','aVF','V3','V6'],\n    ]\n    rhythm_lead = 'II'\n\n    def normalize(signal: np.ndarray) -> np.ndarray:\n        sig = signal - np.mean(signal)\n        span = np.max(sig) - np.min(sig)\n        return sig / (span if span != 0 else 1.0)\n\n    # Vertical offsets for each row\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n\n    # Total time spanned by the columns\n    total_time = 2.5 * num_segments\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid (major: 0.2 s / 1 unit; minor: 0.04 s / 0.2 units)\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each 2.5‑s segment for each lead\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx in range(num_segments):\n            lead = lead_row[col_idx] if col_idx < len(lead_row) else None\n            if lead is None or lead not in df.columns:\n                continue\n            signal = df[lead].to_numpy()\n            start = col_idx * segment_samples\n            end = min(start + segment_samples, len(signal))\n            seg = signal[start:end]\n            if len(seg) == 0:\n                continue\n            seg_norm = normalize(seg)\n            seg_duration = (end - start) / sample_rate\n            t = np.linspace(0, seg_duration, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off, color='black')\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Rhythm strip across the bottom\n    if rhythm_lead in df.columns:\n        y_off = row_offsets[-1]\n        signal = df[rhythm_lead].to_numpy()\n        seg_norm = normalize(signal)\n        t_full = np.linspace(0, len(signal) / sample_rate, len(seg_norm))\n        ax.plot(t_full, seg_norm + y_off, color='black')\n        ax.text(0.05, y_off + 0.5, rhythm_lead, fontsize=8)\n\n    # Final axis adjustments\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save output\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\n# Example usage:\n# generate_ecg_print('1006427285.csv', 'adjusted_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:42.915653Z","iopub.execute_input":"2025-12-07T14:55:42.916298Z","iopub.status.idle":"2025-12-07T14:55:42.939557Z","shell.execute_reply.started":"2025-12-07T14:55:42.916265Z","shell.execute_reply":"2025-12-07T14:55:42.938543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"generate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', 'hu_ecg.png')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:42.944043Z","iopub.execute_input":"2025-12-07T14:55:42.944407Z","iopub.status.idle":"2025-12-07T14:55:44.678295Z","shell.execute_reply.started":"2025-12-07T14:55:42.944387Z","shell.execute_reply":"2025-12-07T14:55:44.677402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Image\nImage(filename='hu_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:44.679445Z","iopub.execute_input":"2025-12-07T14:55:44.680135Z","iopub.status.idle":"2025-12-07T14:55:44.690584Z","shell.execute_reply.started":"2025-12-07T14:55:44.680092Z","shell.execute_reply":"2025-12-07T14:55:44.689706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom IPython.display import Image\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float = None):\n    \"\"\"\n    Generate a 3×4 ECG print with red grid lines and black signals from a CSV of multi‑lead ECG data.\n    Applies controlled interpolation to avoid over-smoothing or trimming the leads that are valid.\n    \"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Reconstruct I from II and III if needed\n    if {'I','II','III'}.issubset(df.columns):\n        missing_I = df['I'].isna()\n        df.loc[missing_I, 'I'] = df.loc[missing_I, 'II'] - df.loc[missing_I, 'III']\n\n    # Derive augmented leads\n    if 'I' in df.columns and 'II' in df.columns:\n        df['aVR'] = df['aVR'].fillna(-(df['I'] + df['II']) / 2)\n        df['aVL'] = df['aVL'].fillna(df['I'] - df['II'] / 2)\n        df['aVF'] = df['aVF'].fillna(df['II'] - df['I'] / 2)\n\n    # Drop columns that are entirely NaN\n    df = df.loc[:, df.notna().any()]\n\n    # Trim start of record to remove leading NaNs across all leads\n    start_idx = 0\n    for col in df.columns:\n        first_valid = df[col].first_valid_index()\n        if first_valid is not None:\n            start_idx = max(start_idx, int(first_valid))\n    df = df.iloc[start_idx:].reset_index(drop=True)\n\n    # Interpolate missing values, but limit to smaller gaps\n    for col in df.columns:\n        nan_proportion = df[col].isna().sum() / len(df)\n        if nan_proportion < 0.05:  # Keep the original data for leads with small NaN proportion\n            df[col] = df[col].fillna(method='ffill').fillna(method='bfill')\n        elif nan_proportion >= 0.05:  # For larger gaps, only fill small missing parts\n            df[col] = df[col].fillna(method='bfill').fillna(method='ffill')  # Backward & forward\n\n    # Infer sample rate from record length if not provided (assumes ~10‑s record)\n    duration_sec = 10.0\n    if sample_rate is None:\n        sample_rate = len(df) / duration_sec\n\n    segment_samples = int(round(2.5 * sample_rate))\n    num_segments = min(4, int(np.ceil(len(df) / segment_samples)))\n\n    # Layout: 3 rows of four leads + rhythm strip\n    rows = [\n        ['I','aVR','V1','V4'],\n        ['II','aVL','V2','V5'],\n        ['III','aVF','V3','V6'],\n    ]\n    rhythm_lead = 'II'\n\n    def normalize(signal: np.ndarray) -> np.ndarray:\n        sig = signal - np.mean(signal)\n        span = np.max(sig) - np.min(sig)\n        return sig / (span if span != 0 else 1.0)\n\n    # Vertical offsets for each row\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n\n    # Total time spanned by the columns\n    total_time = 2.5 * num_segments\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid (major: 0.2 s / 1 unit; minor: 0.04 s / 0.2 units)\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each 2.5‑s segment for each lead\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx in range(num_segments):\n            lead = lead_row[col_idx] if col_idx < len(lead_row) else None\n            if lead is None or lead not in df.columns:\n                continue\n            signal = df[lead].to_numpy()\n            start = col_idx * segment_samples\n            end = min(start + segment_samples, len(signal))\n            seg = signal[start:end]\n            if len(seg) == 0:\n                continue\n            seg_norm = normalize(seg)\n            seg_duration = (end - start) / sample_rate\n            t = np.linspace(0, seg_duration, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off, color='black')\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Rhythm strip across the bottom\n    if rhythm_lead in df.columns:\n        y_off = row_offsets[-1]\n        signal = df[rhythm_lead].to_numpy()\n        seg_norm = normalize(signal)\n        t_full = np.linspace(0, len(signal) / sample_rate, len(seg_norm))\n        ax.plot(t_full, seg_norm + y_off, color='black')\n        ax.text(0.05, y_off + 0.5, rhythm_lead, fontsize=8)\n\n    # Final axis adjustments\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save output\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\n    # Display the saved image in the notebook\n    from IPython.display import Image\n    Image(filename=output_path)\n\n# Example usage:\ngenerate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', 'hudd_ecg.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:44.691866Z","iopub.execute_input":"2025-12-07T14:55:44.692191Z","iopub.status.idle":"2025-12-07T14:55:46.309507Z","shell.execute_reply.started":"2025-12-07T14:55:44.692162Z","shell.execute_reply":"2025-12-07T14:55:46.308408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Image\nImage(filename='hudd_ecg.png')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:46.310664Z","iopub.execute_input":"2025-12-07T14:55:46.310933Z","iopub.status.idle":"2025-12-07T14:55:46.322365Z","shell.execute_reply.started":"2025-12-07T14:55:46.310912Z","shell.execute_reply":"2025-12-07T14:55:46.321227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom IPython.display import Image\n\ndef handle_missing_data(df, columns_to_interpolate):\n    \"\"\"\n    Handle missing data in the dataframe.\n    Interpolate leads with NaN values, only for smaller gaps (less than 1 second).\n    \"\"\"\n    for col in columns_to_interpolate:\n        # Compute the proportion of NaN values in the column\n        nan_proportion = df[col].isna().sum() / len(df)\n        if nan_proportion < 0.05:  # If less than 5% NaNs, forward fill\n            df[col] = df[col].fillna(method='ffill').fillna(method='bfill')\n        else:  # If too many NaNs, fill only small gaps\n            df[col] = df[col].fillna(method='ffill')\n    return df\n\ndef normalize(signal: np.ndarray) -> np.ndarray:\n    \"\"\"\n    Normalize a signal to have a mean of 0 and a range between -1 and 1.\n    \"\"\"\n    sig = signal - np.mean(signal)\n    span = np.max(sig) - np.min(sig)\n    return sig / (span if span != 0 else 1.0)\n\ndef plot_ecg_segments(df, rows, output_path, segment_samples, sample_rate):\n    \"\"\"\n    Plot ECG segments for each lead in the dataset.\n    \"\"\"\n    row_spacing = 3.0\n    num_grid_rows = len(rows)\n    row_offsets = [row_spacing * (num_grid_rows - idx) for idx in range(num_grid_rows)] + [0.0]\n    total_time = 2.5 * len(rows[0])  # Assuming 4 segments\n\n    fig, ax = plt.subplots(figsize=(12, 12))\n    ax.set_facecolor('white')\n\n    # Draw red grid (major: 0.2s, minor: 0.04s)\n    ax.set_xticks(np.arange(0, total_time + 0.2, 0.2))\n    ax.set_xticks(np.arange(0, total_time + 0.04, 0.04), minor=True)\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 1.0))\n    ax.set_yticks(np.arange(-2, row_offsets[0] + 2, 0.2), minor=True)\n    ax.grid(which='major', axis='both', color='red', linewidth=0.6)\n    ax.grid(which='minor', axis='both', color='red', linewidth=0.3)\n\n    # Plot each 2.5s segment for each lead\n    for row_idx, lead_row in enumerate(rows):\n        y_off = row_offsets[row_idx]\n        for col_idx, lead in enumerate(lead_row):\n            if lead not in df.columns:\n                continue\n            signal = df[lead].to_numpy()\n            start = col_idx * segment_samples\n            end = min(start + segment_samples, len(signal))\n            seg = signal[start:end]\n            seg_norm = normalize(seg)\n            seg_duration = (end - start) / sample_rate\n            t = np.linspace(0, seg_duration, len(seg_norm))\n            ax.plot(t + col_idx * 2.5, seg_norm + y_off, color='black')\n            ax.text(col_idx * 2.5 + 0.05, y_off + 0.5, lead, fontsize=8)\n\n    # Final axis adjustments\n    ax.set_xlim(0, total_time)\n    ax.set_ylim(-2, row_offsets[0] + 2)\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    for spine in ax.spines.values():\n        spine.set_visible(False)\n\n    # Save the plot\n    plt.savefig(output_path, dpi=300, bbox_inches='tight')\n    plt.close()\n\ndef generate_ecg_print(csv_path: str, output_path: str, sample_rate: float = None):\n    \"\"\"\n    Main function to generate ECG plot from the CSV data.\n    Handles missing data, normalizes signals, and plots in a 3x4 grid.\n    \"\"\"\n    # Load the dataset\n    df = pd.read_csv(csv_path)\n\n    # List of leads that may have sparse data\n    columns_to_interpolate = ['V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'aVR', 'aVF']\n\n    # Handle missing data: interpolate only for small gaps\n    df = handle_missing_data(df, columns_to_interpolate)\n\n    # Set default sample rate if not provided\n    duration_sec = 10.0\n    if sample_rate is None:\n        sample_rate = len(df) / duration_sec\n\n    segment_samples = int(round(2.5 * sample_rate))\n    num_segments = 4  # 4 segments for 10 seconds (2.5 seconds per segment)\n\n    # ECG Layout: 3 rows of leads and one rhythm strip at the bottom\n    rows = [\n        ['I', 'aVR', 'V1', 'V4'],\n        ['II', 'aVL', 'V2', 'V5'],\n        ['III', 'aVF', 'V3', 'V6'],\n    ]\n\n    # Plot ECG segments\n    plot_ecg_segments(df, rows, output_path, segment_samples, sample_rate)\n\n    # Display the saved image in the notebook\n    from IPython.display import Image\n    Image(filename=output_path)\n\n# Example usage:\ngenerate_ecg_print('/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv', 'hu_ecgsdad.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:46.323393Z","iopub.execute_input":"2025-12-07T14:55:46.323692Z","iopub.status.idle":"2025-12-07T14:55:48.118278Z","shell.execute_reply.started":"2025-12-07T14:55:46.323666Z","shell.execute_reply":"2025-12-07T14:55:48.117192Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Image\nImage(filename='hu_ecgsdad.png')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T14:55:48.119161Z","iopub.execute_input":"2025-12-07T14:55:48.119416Z","iopub.status.idle":"2025-12-07T14:55:48.131494Z","shell.execute_reply.started":"2025-12-07T14:55:48.119374Z","shell.execute_reply":"2025-12-07T14:55:48.130159Z"}},"outputs":[],"execution_count":null}]}