{"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"},{"sourceId":617956,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":464634,"modelId":480456}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport cv2\nfrom scipy import ndimage\nfrom typing import Tuple, Optional\nimport numpy as np\nimport math\nimport matplotlib.pyplot as plt\n#import tensorflow as tf\n#from get_models import rotnet\n\nclass ecg_rotation():\n    \"\"\"This class will take a scanned ECG and rotate it to get the correct orientation\"\"\"\n\n    def __init__(self, model):\n        \"\"\"\n        \n        :Parameters:\n        ------------\n            model:\n                rotation detection model\n        \"\"\"\n        self.model = model\n        \n    @staticmethod\n    def edge_detection(img: np.ndarray):\n        \"\"\"Detect edges in the scanned ECG image. \n        This will be used to orient the image when we rotate it\n        \n        :Parameters:\n        ------------\n            img:\n                Scanned ECG image\n        \"\"\"\n        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n        img_edges = cv2.Canny(img_gray, 500, 500, apertureSize=3)\n        lines = cv2.HoughLinesP(img_edges, 1, math.pi / 180.0, 100, minLineLength=100, maxLineGap=20)\n        return lines\n    \n    @staticmethod\n    def get_rotation_angle(img: np.ndarray, lines):\n        \"\"\"Get possible rotation angles\n        \n        :Parameters:\n        -------------\n            img:\n                Scanned ECG image\n            lines:\n                horisontal lines used to orient image\n        \"\"\"\n        angles = []\n        for [[x1, y1, x2, y2]] in lines:\n            cv2.line(img, (x1, y1), (x2, y2), (255, 0, 0), 3)\n            angle = math.degrees(math.atan2(y2 - y1, x2 - x1))\n            angles.append(angle)\n        return angles\n\n    @staticmethod        \n    def _crop_image(img: np.ndarray, height: int, width: int) -> np.ndarray:\n        #left limit\n        for i in range(width):\n            if np.sum(img[:,i,:]) > 0:\n                break\n        #right limit\n        for j in range(width-1,0,-1):\n            if np.sum(img[:,j,:]) > 0:\n                break\n        #top lim\n        for k in range(height):\n            if np.sum(img[k,:,:]) > 0:\n                break\n        #bottom limit\n        for l in range(height-1,0,-1):\n            if np.sum(img[l,:,:]) > 0:\n                break\n        return img[k:l+1,i:j+1,:]\n\n    def rotate_and_crop_image(self, img:  np.ndarray, angles) -> np.ndarray:\n        median_angle = np.median(angles)\n        img_rotated = ndimage.rotate(img, median_angle)\n        height, width, _ = img_rotated.shape\n        cropped_img = self._crop_image(img_rotated, height, width)\n        return cropped_img\n\n    @staticmethod\n    def resize_image(img:  np.ndarray, width: int = 2339, height: int = 1654) -> np.ndarray:\n        ECG_image_resized = cv2.resize(img,(width,height))\n        return ECG_image_resized\n\n\n    def up_down_detection(self, img: np.ndarray) -> np.ndarray:\n        pred = self.model.predict(np.expand_dims(img,0))\n        if int(pred) == 1:\n            return ndimage.rotate(img, 180)\n\n        elif int(pred) == 0:\n            return ndimage.rotate(img, 0)\n    \n    def rotate(self, img: np.ndarray) -> np.ndarray:\n        \"\"\"Rotate the image\"\"\"\n        lines = self.edge_detection(img)\n        angles = self.get_rotation_angle(img.copy(), lines)\n        img = self.rotate_and_crop_image(img, angles)\n        img = self.resize_image(img)\n\n        #img = self.up_down_detection(img)\n        return img\n\n\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:34:48.410469Z","iopub.execute_input":"2025-10-24T07:34:48.411326Z","iopub.status.idle":"2025-10-24T07:34:48.532394Z","shell.execute_reply.started":"2025-10-24T07:34:48.411231Z","shell.execute_reply":"2025-10-24T07:34:48.531145Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Taing a look at one ECG","metadata":{}},{"cell_type":"code","source":"ecg = pd.read_csv(\"/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:34:48.534220Z","iopub.execute_input":"2025-10-24T07:34:48.534497Z","iopub.status.idle":"2025-10-24T07:34:48.566231Z","shell.execute_reply.started":"2025-10-24T07:34:48.534476Z","shell.execute_reply":"2025-10-24T07:34:48.565249Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_ecg_waveform(waveform_arr, length):\n\n    lead_labels = [\n        'I',  'II', 'III',\n        'aVR','aVL','aVF', \n        'V1', 'V2', 'V3', \n        'V4', 'V5', 'V6'\n    ]\n\n    fig, axes = plt.subplots(12, 1, sharex=True)\n    fig.set_figwidth(10)\n    fig.set_figheight(10)\n\n    for ax, lead_waveform, lead_name in zip(axes, waveform_arr, lead_labels):\n        ax.plot(lead_waveform, label=lead_name)\n        ax.set_xlim((0,length))\n        ax.legend(loc='center left')\n        xaxis = ax.get_shared_x_axes()\n\n    fig.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:34:48.567474Z","iopub.execute_input":"2025-10-24T07:34:48.567833Z","iopub.status.idle":"2025-10-24T07:34:48.574276Z","shell.execute_reply.started":"2025-10-24T07:34:48.567808Z","shell.execute_reply":"2025-10-24T07:34:48.573234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_ecg_waveform(np.moveaxis(np.asarray(ecg),0,1), len(ecg))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:34:48.575203Z","iopub.execute_input":"2025-10-24T07:34:48.575518Z","iopub.status.idle":"2025-10-24T07:34:50.435974Z","shell.execute_reply.started":"2025-10-24T07:34:48.575497Z","shell.execute_reply":"2025-10-24T07:34:50.434807Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"path = \"/kaggle/input/physionet-ecg-image-digitization/train/1006427285/\"\nimages = [f for f in os.listdir(path) if f.endswith(\".png\")]\nimages = images[:8]\n\n\n\nfig, axes = plt.subplots(2, 4, figsize=(20, 12))  \naxes = axes.flatten()\nfor ax, img_name in zip(axes, images):\n    img = mpimg.imread(os.path.join(path, img_name))\n    ax.imshow(img)\n    ax.set_title(img_name, fontsize=8)\n    ax.axis(\"off\")\n\nfor ax in axes[len(images):]:\n    ax.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:34:50.438467Z","iopub.execute_input":"2025-10-24T07:34:50.438812Z","iopub.status.idle":"2025-10-24T07:35:07.387934Z","shell.execute_reply.started":"2025-10-24T07:34:50.438790Z","shell.execute_reply":"2025-10-24T07:35:07.386432Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"path = \"/kaggle/input/physionet-ecg-image-digitization/train/1006867983/\"\nimages = [f for f in os.listdir(path) if f.endswith(\".png\")]\nimages = images[:8]\n\n\n\nfig, axes = plt.subplots(2, 4, figsize=(20, 12))  \naxes = axes.flatten()\nfor ax, img_name in zip(axes, images):\n    img = mpimg.imread(os.path.join(path, img_name))\n    ax.imshow(img)\n    ax.set_title(img_name, fontsize=8)\n    ax.axis(\"off\")\n\nfor ax in axes[len(images):]:\n    ax.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:35:56.236058Z","iopub.execute_input":"2025-10-24T07:35:56.237135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#ecg_rotation()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}