{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.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":4104,"databundleVersionId":46661,"sourceType":"competition"},{"sourceId":7866129,"sourceType":"datasetVersion","datasetId":4614938},{"sourceId":7869237,"sourceType":"datasetVersion","datasetId":4617269}],"dockerImageVersionId":30673,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# 🛠️ MODULE UNIQUE — prétraitement + scoring (Kaggle Utility Script)\n# Utilisé À L'IDENTIQUE par preprocess_train.py, preprocess_test.py,\n# et tout script d'entraînement/inférence.\n# Aucune opération aléatoire ici — l'augmentation reste dans train_tf.\n# ============================================================\nimport cv2\nimport numpy as np\nimport torch\nfrom sklearn.metrics import cohen_kappa_score\nfrom scipy.optimize import minimize\n\nIMG_SIZE = 512\nSIGMA_X = 10\nNUM_CLASSES = 5\n\n# --- 1. Prétraitement déterministe (train & test, sans distinction) -------\ndef crop_image_from_gray(img, tol=7):\n    gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    mask = gray_img > tol\n    if mask.sum() == 0:\n        return img\n    coords = np.ix_(mask.any(1), mask.any(0))\n    cropped = np.stack([img[:, :, c][coords] for c in range(3)], axis=-1)\n    return cropped if cropped.size > 0 else img\n\ndef circle_crop(img):\n    h, w = img.shape[:2]\n    x, y = w // 2, h // 2\n    r = min(x, y)\n    mask = np.zeros((h, w), np.uint8)\n    cv2.circle(mask, (x, y), r, 1, thickness=-1)\n    return cv2.bitwise_and(img, img, mask=mask)\n\ndef preprocess_image(img, img_size=IMG_SIZE, sigma_x=SIGMA_X):\n    \"\"\"Pipeline déterministe unique — train ET test, sans distinction is_train.\"\"\"\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    img = crop_image_from_gray(img)\n    img = circle_crop(img)\n    img = crop_image_from_gray(img)\n    img = cv2.resize(img, (img_size, img_size))\n    img = cv2.addWeighted(img, 4, cv2.GaussianBlur(img, (0, 0), sigma_x), -4, 128)\n    return img\n\n# --- 2. Scoring CORN (identique entraînement / inférence) -----------------\ndef corn_label_matrix(labels, num_classes=NUM_CLASSES):\n    \"\"\"Cible binaire ordinale pour la loss CORN, à partir de labels entiers.\"\"\"\n    m = torch.zeros((labels.size(0), num_classes - 1), device=labels.device)\n    for i in range(num_classes - 1):\n        m[:, i] = (labels > i).float()\n    return m\n\ndef corn_to_score(corn_logits):\n    \"\"\"Score ordinal continu (0 à num_classes-1) à partir des logits CORN.\"\"\"\n    return torch.sigmoid(corn_logits).sum(dim=1)\n\ndef optimize_thresholds(scores, labels, init=(0.5, 1.5, 2.5, 3.5)):\n    \"\"\"Optimise les 4 seuils de découpage pour maximiser le QWK sur (scores, labels).\"\"\"\n    def neg_kappa(th):\n        return -cohen_kappa_score(labels, np.digitize(scores, np.sort(th)), weights='quadratic')\n    result = minimize(neg_kappa, np.array(init), method='Nelder-Mead')\n    return np.sort(result.x), -result.fun\n\ndef apply_thresholds(scores, thresholds):\n    \"\"\"À utiliser en inférence avec les seuils sauvegardés dans le checkpoint.\"\"\"\n    return np.digitize(scores, thresholds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-08T14:32:14.771055Z","iopub.execute_input":"2026-08-08T14:32:14.771415Z","iopub.status.idle":"2026-08-08T14:32:14.786047Z","shell.execute_reply.started":"2026-08-08T14:32:14.771389Z","shell.execute_reply":"2026-08-08T14:32:14.784700Z"}},"outputs":[],"execution_count":null}]}