{"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":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<div style=\"display: flex; align-items: center;\">\n    <!-- Lewa kolumna (obrazek) -->\n    <div style=\"flex: 0 0 200px; margin-right: 20px;\">\n        <img src=\"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS_HBAU78p5qUkLpHWuoj5AsrQeWDsUb8aOhw&s#\" alt=\"Icon\" width=\"400\">\n    </div>\n    <!-- Prawa kolumna (tekst) -->\n    <div style=\"flex: 1;\"><h2 style=\"margin-top: 0;\">\n        Restoring ECG Signals from Images\n    </h2><p>\n        Restoring an ECG signal from images of varying quality is a challenging task. Some existing methodologies are described in\n         <a href=\"https://www.nature.com/articles/s41598-022-25284-1#Sec2\" target=\"_blank\">\n        this article.</a> In this project, I take a closer look at the provided ECG images. With the help of the OpenCV (cv2) library, I preprocess the images as much as possible. The model is implemented using PyTorch.\n    </p><p><strong>Project goals:</strong></p>\n        <ul>\n            <li>Become familiar with CSV files and data visualization</li>\n            <li>Inspect random images to understand the difficulty of the task</li>\n            <li>Preprocess the provided images as thoroughly as possible</li>\n            <li>Train a model to restore ECG signals</li>\n            <li>Evaluate the model performance</li>\n        </ul></div>\n</div>","metadata":{}},{"cell_type":"code","source":"import os\nimport math\nimport random\nimport cv2\nimport glob\nimport time\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\n\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\nimport scipy.signal as signal\nfrom scipy.interpolate import interp1d\n\n\nimport warnings\nwarnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n\n\nimport os\ni=0\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    i = i+1\n    if i> 5:\n        break\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:23.550672Z","iopub.execute_input":"2025-12-13T20:05:23.551052Z","iopub.status.idle":"2025-12-13T20:05:23.572002Z","shell.execute_reply.started":"2025-12-13T20:05:23.551030Z","shell.execute_reply":"2025-12-13T20:05:23.570495Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Become familiar with CSV files and data visualization","metadata":{}},{"cell_type":"code","source":"# Paths to data\nTRAIN_DIR = \"/kaggle/input/physionet-ecg-image-digitization/train/\"\nTRAIN_META = \"/kaggle/input/physionet-ecg-image-digitization/train.csv\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:23.573979Z","iopub.execute_input":"2025-12-13T20:05:23.574360Z","iopub.status.idle":"2025-12-13T20:05:23.579629Z","shell.execute_reply.started":"2025-12-13T20:05:23.574333Z","shell.execute_reply":"2025-12-13T20:05:23.578552Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Read and check train.csv","metadata":{}},{"cell_type":"code","source":"#Read train.csv\ntrain_meta = pd.read_csv(TRAIN_META)\nprint(\"Train metadata shape:\", train_meta.shape)\ntrain_meta.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:23.605599Z","iopub.execute_input":"2025-12-13T20:05:23.605979Z","iopub.status.idle":"2025-12-13T20:05:23.617933Z","shell.execute_reply.started":"2025-12-13T20:05:23.605955Z","shell.execute_reply":"2025-12-13T20:05:23.617004Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.info() #non-nulls, integer columns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:23.619128Z","iopub.execute_input":"2025-12-13T20:05:23.619531Z","iopub.status.idle":"2025-12-13T20:05:23.638789Z","shell.execute_reply.started":"2025-12-13T20:05:23.619493Z","shell.execute_reply":"2025-12-13T20:05:23.637606Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Read and display example signal","metadata":{}},{"cell_type":"code","source":"sample_id = train_meta['id'].iloc[0]\nsignal_path = os.path.join(TRAIN_DIR, str(sample_id), f\"{sample_id}.csv\")\nsignal = pd.read_csv(signal_path)\nprint(\"Signal shape:\", signal.shape)\nsignal.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:23.666248Z","iopub.execute_input":"2025-12-13T20:05:23.667522Z","iopub.status.idle":"2025-12-13T20:05:23.689207Z","shell.execute_reply.started":"2025-12-13T20:05:23.667484Z","shell.execute_reply":"2025-12-13T20:05:23.687358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12,6))\nfor lead in ['I','II','III']:\n    plt.plot(signal[lead].values, label=lead)\nplt.title(f\"ECG Leads for {sample_id}\")\nplt.xlabel(\"Sample index\")\nplt.ylabel(\"Amplitude (mV)\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:23.690837Z","iopub.execute_input":"2025-12-13T20:05:23.691059Z","iopub.status.idle":"2025-12-13T20:05:23.872209Z","shell.execute_reply.started":"2025-12-13T20:05:23.691044Z","shell.execute_reply":"2025-12-13T20:05:23.871169Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inspect random images to understand the difficulty of the task","metadata":{}},{"cell_type":"markdown","source":"folder_files_dict - it is a dictionary object\nfields:\nfile name\nfile path\nimage","metadata":{}},{"cell_type":"code","source":"# function for displayng images from patient's folder\n\ndef display_images(folder_files_list):\n    image_files = [f for f in folder_files_list if f.endswith(('.png', '.jpg', '.jpeg'))]\n    image_files.sort()\n\n\n    cols = 3\n    rows = math.ceil(len(image_files) / cols)\n    plt.figure(figsize=(15, 5 * rows))\n\n    for i, filename in enumerate(image_files): \n        #img_path = os.path.join(folder_path, filename)\n        f_name = os.path.basename(filename)\n        img = cv2.imread(filename)\n        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        plt.subplot(rows, cols, i + 1)\n        plt.imshow(img_rgb)\n        plt.title(f_name)\n        plt.axis('off')\n\n    plt.tight_layout()\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:23.872986Z","iopub.execute_input":"2025-12-13T20:05:23.873233Z","iopub.status.idle":"2025-12-13T20:05:23.880523Z","shell.execute_reply.started":"2025-12-13T20:05:23.873215Z","shell.execute_reply":"2025-12-13T20:05:23.879243Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Random folder display -> helps detect image problems.\nfolder_list = os.listdir(TRAIN_DIR)\nrand_folder = random.choice(folder_list)\nfolder_path = os.path.join(TRAIN_DIR, rand_folder)\nlist_of_files = [] #<------ List of files currently being processed to test image processing function performance.\nfor element in os.listdir(folder_path):\n    if element.endswith(('.png', '.jpg', '.jpeg')):\n        list_of_files.append(os.path.join(folder_path, element))\ndisplay_images(list_of_files)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:23.881465Z","iopub.execute_input":"2025-12-13T20:05:23.881771Z","iopub.status.idle":"2025-12-13T20:05:32.905581Z","shell.execute_reply.started":"2025-12-13T20:05:23.881722Z","shell.execute_reply":"2025-12-13T20:05:32.904515Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Define challenges","metadata":{}},{"cell_type":"markdown","source":"**problems**: \n* extra bordes/backgroung\n* shadows, shine backrounds, dark/ whiteand wooden bacgrounds\n* rotatet images\n* stains\n* noise\n* images taken from an angle","metadata":{}},{"cell_type":"markdown","source":"# Preprocess images","metadata":{}},{"cell_type":"code","source":"#help function for 2 images comparision\ndef compare(org_img, changed_img, change_name):\n    \n    plt.figure(figsize=(10, 5))\n\n\n    plt.subplot(1, 2, 1)\n    if len(org_img.shape) == 2 or org_img.shape[2] == 1: #Check if gray\n        plt.imshow(org_img,cmap='gray')\n    else:\n        img_rgb = cv2.cvtColor(org_img, cv2.COLOR_BGR2RGB)\n        plt.imshow(img_rgb)\n    plt.title(\"Original\")\n    plt.axis(\"off\")\n\n\n    plt.subplot(1, 2, 2)\n    if len(changed_img.shape) == 2 or changed_img.shape[2] == 1:\n            plt.imshow(changed_img,cmap='gray')\n    else:\n        img_rgb = cv2.cvtColor(changed_img, cv2.COLOR_BGR2RGB)\n        plt.imshow(img_rgb)\n    plt.title(change_name)\n    plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:32.907555Z","iopub.execute_input":"2025-12-13T20:05:32.907852Z","iopub.status.idle":"2025-12-13T20:05:32.916713Z","shell.execute_reply.started":"2025-12-13T20:05:32.907830Z","shell.execute_reply":"2025-12-13T20:05:32.915405Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Remove background - cropping function","metadata":{}},{"cell_type":"code","source":"def cropping(image_path):\n\n    img = cv2.imread(image_path)\n    original = img.copy()\n\n    \n    alpha = 1.5\n    beta = 20\n    img_con = cv2.convertScaleAbs(img, alpha=alpha, beta=beta)\n    # sometimes image need to be: brighten or darken to remove background\n    possible_images = [img,\n                       img_con,\n                       cv2.convertScaleAbs(img, alpha=1.0, beta=-70),\n                       cv2.convertScaleAbs(img, alpha=1.5, beta=50),\n                       cv2.convertScaleAbs(img_con, alpha=1.0, beta=-70),\n                       cv2.convertScaleAbs(img_con, alpha=1.5, beta=50)\n                       ]\n    for image in possible_images:\n        \n\n        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n\n        blur = cv2.GaussianBlur(gray, (5,5), 0)\n        edges = cv2.Canny(blur, 50, 150)\n\n\n        kernel = np.ones((5,5), np.uint8)\n        edges = cv2.dilate(edges, kernel, iterations=2)\n        edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)\n\n\n        contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n\n        if not contours:\n            print(\" X Nie znaleziono konturów. X\")\n            return image\n\n\n        largest = max(contours, key=cv2.contourArea)\n        x, y, w, h = cv2.boundingRect(largest)\n\n\n        cv2.rectangle(original, (x, y), (x+w, y+h), (0, 255, 0), 3)\n\n\n        ekg_cropped = img[y:y+h, x:x+w]\n\n        if image.shape[:2] == ekg_cropped.shape[:2]:\n            pass\n        else:\n            return ekg_cropped\n\n    print(\"Cropping image - failed!\")\n    return original","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:32.917802Z","iopub.execute_input":"2025-12-13T20:05:32.918081Z","iopub.status.idle":"2025-12-13T20:05:32.941408Z","shell.execute_reply.started":"2025-12-13T20:05:32.918061Z","shell.execute_reply":"2025-12-13T20:05:32.939668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#get random image from folder\nrand_image_path = random.choice(list_of_files)\noriginal = cv2.imread(rand_image_path)\nprint(rand_image_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:32.942809Z","iopub.execute_input":"2025-12-13T20:05:32.943098Z","iopub.status.idle":"2025-12-13T20:05:33.289023Z","shell.execute_reply.started":"2025-12-13T20:05:32.943076Z","shell.execute_reply":"2025-12-13T20:05:33.287828Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Checking how preprocessing function works on pictures with background","metadata":{}},{"cell_type":"code","source":"cropperd_image = cropping(rand_image_path)\ncompare(original,cropperd_image, 'Cropped')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:33.289849Z","iopub.execute_input":"2025-12-13T20:05:33.290506Z","iopub.status.idle":"2025-12-13T20:05:36.127271Z","shell.execute_reply.started":"2025-12-13T20:05:33.290488Z","shell.execute_reply":"2025-12-13T20:05:36.126409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def crop_by_center_mask(\n    img,\n    mask_ratio=0.3,\n    border=10,\n    threshold=30,\n    max_iters=50\n):\n    \"\"\"\n    img        : grayscale lub BGR\n    mask_ratio : procent rozmiaru obrazu użyty jako maska centralna\n    border     : grubość sprawdzanego brzegu (px)\n    threshold  : próg różnicy jasności\n    max_iters  : zabezpieczenie przed nieskończoną pętlą\n    \"\"\"\n\n    cropped = img.copy()\n\n    for _ in range(max_iters):\n        if cropped.ndim == 2:  # GRAYSCALE\n            value_img = cropped\n        else:  # COLOR → HSV → V\n            hsv = cv2.cvtColor(cropped, cv2.COLOR_BGR2HSV)\n            value_img = hsv[:, :, 2]\n\n        h, w = value_img.shape\n\n        # Za mały obraz – kończymy\n        if h < 2 * border or w < 2 * border:\n            break\n\n        # Maska centralna\n        mh = int(h * mask_ratio)\n        mw = int(w * mask_ratio)\n        y1 = h//2 - mh//2\n        y2 = h//2 + mh//2\n        x1 = w//2 - mw//2\n        x2 = w//2 + mw//2\n\n        center_mean = np.mean(value_img[y1:y2, x1:x2])\n\n        cut = False\n\n        # GÓRA\n        if abs(np.mean(value_img[:border, :]) - center_mean) > threshold:\n            cropped = cropped[border:, :]\n            cut = True\n\n        # DÓŁ\n        elif abs(np.mean(value_img[-border:, :]) - center_mean) > threshold:\n            cropped = cropped[:-border, :]\n            cut = True\n\n        # LEWO\n        elif abs(np.mean(value_img[:, :border]) - center_mean) > threshold:\n            cropped = cropped[:, border:]\n            cut = True\n\n        # PRAWO\n        elif abs(np.mean(value_img[:, -border:]) - center_mean) > threshold:\n            cropped = cropped[:, :-border]\n            cut = True\n\n        if not cut:\n            break\n\n    return cropped","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:36.127915Z","iopub.execute_input":"2025-12-13T20:05:36.128076Z","iopub.status.idle":"2025-12-13T20:05:36.137720Z","shell.execute_reply.started":"2025-12-13T20:05:36.128063Z","shell.execute_reply":"2025-12-13T20:05:36.136202Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cropperd_image1 = cropping(rand_image_path)\ncompare(original,cropperd_image1, 'Cropped')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:36.138427Z","iopub.execute_input":"2025-12-13T20:05:36.138617Z","iopub.status.idle":"2025-12-13T20:05:38.682828Z","shell.execute_reply.started":"2025-12-13T20:05:36.138605Z","shell.execute_reply":"2025-12-13T20:05:38.681030Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Fix skewed images ","metadata":{}},{"cell_type":"code","source":"def deskew_ekg(img):\n    # 1. Grayscale\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n\n    # 2. Delikatne wygładzenie\n    blur = cv2.GaussianBlur(gray, (5, 5), 0)\n\n    # 3. Binaryzacja adaptacyjna (dobrze łapie siatkę)\n    binary = cv2.adaptiveThreshold(\n        blur, 255,\n        cv2.ADAPTIVE_THRESH_MEAN_C,\n        cv2.THRESH_BINARY_INV,\n        31, 10\n    )\n\n    # 4. Morfologia – łączymy linie siatki\n    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 3))\n    morphed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)\n\n    # 5. Wykrycie kąta\n    coords = np.column_stack(np.where(morphed > 0))\n    angle = cv2.minAreaRect(coords)[-1]\n\n    if angle < -45:\n        angle = -(90 + angle)\n    else:\n        angle = -angle\n\n    # 6. Obrót\n    (h, w) = img.shape[:2]\n    center = (w // 2, h // 2)\n    M = cv2.getRotationMatrix2D(center, angle, 1.0)\n    rotated = cv2.warpAffine(\n        img, M, (w, h),\n        flags=cv2.INTER_CUBIC,\n        borderMode=cv2.BORDER_REPLICATE\n    )\n\n    return rotated, angle","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:38.684054Z","iopub.execute_input":"2025-12-13T20:05:38.684382Z","iopub.status.idle":"2025-12-13T20:05:38.692091Z","shell.execute_reply.started":"2025-12-13T20:05:38.684356Z","shell.execute_reply":"2025-12-13T20:05:38.690680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"deskew_cropped_mage, angle = deskew_ekg(cropperd_image)\nprint(angle)\ncompare(original,deskew_cropped_mage, 'deskew_cropped_mage')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:38.693332Z","iopub.execute_input":"2025-12-13T20:05:38.693658Z","iopub.status.idle":"2025-12-13T20:05:41.117994Z","shell.execute_reply.started":"2025-12-13T20:05:38.693628Z","shell.execute_reply":"2025-12-13T20:05:41.116813Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Make scan","metadata":{}},{"cell_type":"code","source":"def scan_like_document(img):\n\n    orig = img.copy()\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    blur = cv2.GaussianBlur(gray, (5,5), 0)\n    \n    thresh = cv2.adaptiveThreshold(\n    blur, 255,\n    cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\n    cv2.THRESH_BINARY,\n    11, 2\n        )\n    \n    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))\n    closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)\n\n    contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n    contours = sorted(contours, key=cv2.contourArea, reverse=True)\n    \n    \n    \n    doc_cnt = None\n    for c in contours:\n        peri = cv2.arcLength(c, True)\n        approx = cv2.approxPolyDP(c, 0.02 * peri, True)\n        if len(approx) == 4:  # szukamy prostokąta\n            doc_cnt = approx\n            break\n    \n    if doc_cnt is None:\n        raise ValueError(\"No documnt!\")\n    \n    \n    # Funkcja do sortowania punktów w kolejności: tl, tr, br, bl\n    def order_points(pts):\n        pts = pts.reshape(4,2)\n        rect = np.zeros((4,2), dtype=\"float32\")\n        s = pts.sum(axis=1)\n        rect[0] = pts[np.argmin(s)]\n        rect[2] = pts[np.argmax(s)]\n        diff = np.diff(pts, axis=1)\n        rect[1] = pts[np.argmin(diff)]\n        rect[3] = pts[np.argmax(diff)]\n        return rect\n\n    rect = order_points(doc_cnt)\n    (tl, tr, br, bl) = rect\n\n    # Oblicz szerokość i wysokość nowego obrazu\n    widthA = np.linalg.norm(br - bl)\n    widthB = np.linalg.norm(tr - tl)\n    maxWidth = max(int(widthA), int(widthB))\n\n    heightA = np.linalg.norm(tr - br)\n    heightB = np.linalg.norm(tl - bl)\n    maxHeight = max(int(heightA), int(heightB))\n\n    # Punkt docelowy dla transformacji perspektywy\n    dst = np.array([\n        [0,0],\n        [maxWidth-1,0],\n        [maxWidth-1,maxHeight-1],\n        [0,maxHeight-1]\n    ], dtype=\"float32\")\n\n    # Macierz transformacji i warp\n    M = cv2.getPerspectiveTransform(rect, dst)\n    warped = cv2.warpPerspective(orig, M, (maxWidth, maxHeight))\n\n    return warped","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:41.119196Z","iopub.execute_input":"2025-12-13T20:05:41.119461Z","iopub.status.idle":"2025-12-13T20:05:41.132487Z","shell.execute_reply.started":"2025-12-13T20:05:41.119441Z","shell.execute_reply":"2025-12-13T20:05:41.131221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#check function scan\nscan_o = scan_like_document(original)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:41.135388Z","iopub.execute_input":"2025-12-13T20:05:41.135619Z","iopub.status.idle":"2025-12-13T20:05:41.310449Z","shell.execute_reply.started":"2025-12-13T20:05:41.135599Z","shell.execute_reply":"2025-12-13T20:05:41.309683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"compare(original,scan_o, 'make scan on original')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:41.311597Z","iopub.execute_input":"2025-12-13T20:05:41.311976Z","iopub.status.idle":"2025-12-13T20:05:43.576345Z","shell.execute_reply.started":"2025-12-13T20:05:41.311947Z","shell.execute_reply":"2025-12-13T20:05:43.575359Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Detect QR-code, and crop-image\n\n","metadata":{}},{"cell_type":"code","source":"def crop_below_qrcode(img, bottom_margin=0.0):\n    \"\"\"\n    img          : obraz BGR\n    bottom_margin: ile % wysokości QR-kodu dodać nad fragmentem, żeby nie ciąć dokładnie od krawędzi\n    Zwraca fragment obrazu poniżej QR-kodu.\n    \"\"\"\n    detector = cv2.QRCodeDetector()\n    data, bbox, rectified = detector.detectAndDecode(img)\n\n    if bbox is None:\n        print(\"Nie wykryto QR-kodu\")\n        return img  # zwracamy oryginał\n\n    # bbox zwraca 4 punkty narożne (x, y)\n    bbox = bbox[0]  # shape (4,2)\n    x_min = int(np.min(bbox[:,0]))\n    x_max = int(np.max(bbox[:,0]))\n    y_min = int(np.min(bbox[:,1]))\n    y_max = int(np.max(bbox[:,1]))\n\n    # dodanie marginesu poniżej QR (opcjonalnie)\n    y_start = y_max + int((y_max - y_min) * bottom_margin)\n    y_start = min(y_start, img.shape[0]-1)\n\n    # przycinanie poniżej QR\n    cropped = img[y_start:, :]\n\n    return cropped","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:43.577638Z","iopub.execute_input":"2025-12-13T20:05:43.577875Z","iopub.status.idle":"2025-12-13T20:05:43.584509Z","shell.execute_reply.started":"2025-12-13T20:05:43.577858Z","shell.execute_reply":"2025-12-13T20:05:43.583448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"no_qr_code = crop_below_qrcode(cropperd_image, 2.5)\ncompare(original,no_qr_code, 'Cropped scan_1')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:43.585318Z","iopub.execute_input":"2025-12-13T20:05:43.585588Z","iopub.status.idle":"2025-12-13T20:05:46.019844Z","shell.execute_reply.started":"2025-12-13T20:05:43.585566Z","shell.execute_reply":"2025-12-13T20:05:46.018782Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Remove grid","metadata":{}},{"cell_type":"code","source":"def remove_grid(image):\n    image_gray = cv2.cvtColor(no_qr_code, cv2.COLOR_BGR2GRAY)\n\n    ret, thresh = cv2.threshold(image_gray, 95, 255, cv2.THRESH_BINARY)\n\n    return cv2.equalizeHist(thresh)\n\ncompare(no_qr_code,remove_grid(no_qr_code), 'No grid')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T20:05:46.020984Z","iopub.execute_input":"2025-12-13T20:05:46.021329Z","iopub.status.idle":"2025-12-13T20:05:47.055128Z","shell.execute_reply.started":"2025-12-13T20:05:46.021304Z","shell.execute_reply":"2025-12-13T20:05:47.053567Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"markdown","source":"## 1. Config","metadata":{}}]}