{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":5048,"databundleVersionId":868335,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\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-08-20T18:51:08.816826Z","iopub.execute_input":"2025-08-20T18:51:08.817220Z","iopub.status.idle":"2025-08-20T18:53:22.757414Z","shell.execute_reply.started":"2025-08-20T18:51:08.817193Z","shell.execute_reply":"2025-08-20T18:53:22.756427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================================================\n# Driver Distraction (State Farm) - Pipeline Bench\n# One-technique-per-section + 70/15/15 split\n# ================================================\n\nimport os, glob, random, gc, math, json\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nimport pandas as pd\n\nfrom sklearn.decomposition import PCA\nfrom sklearn.model_selection import StratifiedShuffleSplit\nfrom sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay\n\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, callbacks\n\n# -------------------- Config --------------------\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); tf.random.set_seed(SEED)\n\nDATA_DIR = \"/kaggle/input/state-farm-distracted-driver-detection/imgs/train\"\nCLASS_NAMES = [f\"c{i}\" for i in range(10)]\n\nIMG_SIZE = 64              # keep small for speed\nSCALE_RANGE = \"[0,1]\"      # \"[0,1]\" or \"[-1,1]\"\nPER_CLASS_LIMIT = 400      # <= for quick runs; raise if you have time\nBATCH = 256\nEPOCHS = 25                # early stopping will usually stop earlier\nPCA_KEEP = 0.95            # retain 95% variance\nSHOW_CM = False            # set True to render confusion matrices\n\n# Pipelines to train (one per rubric section)\nPIPELINE_ORDER = [\n    \"Standard\",\n    \"Lighting\",\n    \"Noise_Gaussian\",\n    \"Texture_LBP\",        # <-- enabled\n    \"Feature_Edges\",\n    \"BackgroundRemoval\",\n    \"MultiScale\",\n    \"Aug_Rotate\"\n]\n\n# ------------------ Helpers: scaling ------------------\ndef _scale01(imgf32):\n    return np.clip(imgf32, 0.0, 1.0).astype(np.float32)\n\ndef _scale_out(x01):\n    if SCALE_RANGE == \"[-1,1]\":\n        return (x01 * 2.0 - 1.0).astype(np.float32)\n    return x01.astype(np.float32)  # [0,1]\n\ndef _resize_rgb(img_bgr, size=IMG_SIZE):\n    return cv2.cvtColor(cv2.resize(img_bgr, (size, size), interpolation=cv2.INTER_AREA),\n                        cv2.COLOR_BGR2RGB)\n\n# ------------------ 1) Standard ------------------\ndef pp_standard(img_bgr):\n    rgb = _resize_rgb(img_bgr)\n    x = rgb.astype(np.float32) / 255.0\n    return _scale_out(x)\n\n# -------- 2) Lighting (CLAHE + gamma + B/C) -------\ndef pp_lighting(img_bgr, gamma=1.15, alpha=1.05, beta=0.02, clip=2.0, tile=8):\n    rgb = _resize_rgb(img_bgr)\n    lab = cv2.cvtColor(rgb, cv2.COLOR_RGB2LAB)\n    L, A, B = cv2.split(lab)\n    clahe = cv2.createCLAHE(clipLimit=clip, tileGridSize=(tile, tile))\n    L2 = clahe.apply(L)\n    rgb2 = cv2.cvtColor(cv2.merge([L2, A, B]), cv2.COLOR_LAB2RGB).astype(np.float32) / 255.0\n    rgb2 = np.power(np.clip(rgb2, 0, 1), gamma).astype(np.float32)   # gamma\n    rgb2 = np.clip(alpha * rgb2 + beta, 0.0, 1.0)                    # B/C\n    return _scale_out(rgb2)\n\n# -------- 3) Noise reduction (Gaussian) -----------\ndef pp_noise_gaussian(img_bgr, k=3):\n    rgb = _resize_rgb(img_bgr)\n    x8 = cv2.GaussianBlur(rgb, (k, k), 0)\n    x = x8.astype(np.float32) / 255.0\n    # tiny morph open for dot noise\n    k3 = np.ones((2,2), np.uint8)\n    x8 = (x * 255).astype(np.uint8)\n    x8 = cv2.morphologyEx(x8, cv2.MORPH_OPEN, k3)\n    return _scale_out(x8.astype(np.float32) / 255.0)\n\n# -------- 4a) Feature: Edge detection (Canny) ----\ndef pp_feature_edges(img_bgr):\n    rgb = _resize_rgb(img_bgr)\n    g8 = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)\n    e = cv2.Canny(g8, 100, 200).astype(np.float32) / 255.0\n    e3 = np.repeat(e[..., None], 3, axis=-1)  # keep 3 channels for fairness\n    return _scale_out(e3)\n\n# -------- 4b) Background removal (GrabCut) -------\ndef pp_bg_grabcut(img_bgr):\n    rgb = _resize_rgb(img_bgr)\n    h, w = rgb.shape[:2]\n    mask = np.full((h, w), cv2.GC_PR_BGD, np.uint8)\n    rect = (w//8, h//8, 3*w//4, 3*h//4)\n    bgd, fgd = np.zeros((1,65), np.float64), np.zeros((1,65), np.float64)\n    try:\n        cv2.grabCut(rgb, mask, rect, bgd, fgd, 3, cv2.GC_INIT_WITH_RECT)\n        fgmask = (mask == cv2.GC_FGD) | (mask == cv2.GC_PR_FGD)\n        out = np.zeros_like(rgb, dtype=np.float32)\n        out[fgmask] = rgb[fgmask].astype(np.float32) / 255.0\n        return _scale_out(out)\n    except:\n        return pp_standard(img_bgr)\n\n# -------- 4c) Multi-scale (pyramid mean) ---------\ndef pp_multiscale(img_bgr):\n    base = _resize_rgb(img_bgr).astype(np.float32) / 255.0\n    s2  = cv2.resize(base, (IMG_SIZE*3//4, IMG_SIZE*3//4), interpolation=cv2.INTER_AREA)\n    s2  = cv2.resize(s2, (IMG_SIZE, IMG_SIZE), interpolation=cv2.INTER_AREA)\n    s3  = cv2.resize(base, (IMG_SIZE//2, IMG_SIZE//2), interpolation=cv2.INTER_AREA)\n    s3  = cv2.resize(s3, (IMG_SIZE, IMG_SIZE), interpolation=cv2.INTER_AREA)\n    out = np.clip((base + s2 + s3) / 3.0, 0, 1).astype(np.float32)\n    return _scale_out(out)\n\n# -------- 4d) Texture (LBP) — vectorized ----------\ndef _lbp_simple(gray_u8):\n    g = gray_u8.astype(np.float32)\n    H, W = g.shape\n    c = g[1:-1, 1:-1]\n    code = np.zeros((H-2, W-2), dtype=np.uint8)\n    # (dy, dx, bit)\n    SHIFTS = [(-1,-1,7), (-1,0,6), (-1,1,5), (0,1,4),\n              (1,1,3),  (1,0,2),  (1,-1,1), (0,-1,0)]\n    for dy, dx, bit in SHIFTS:\n        nbr = g[1+dy:H-1+dy, 1+dx:W-1+dx]\n        code |= ((nbr >= c).astype(np.uint8) << bit)\n    # pad back to HxW (zeros border)\n    out = np.zeros((H, W), dtype=np.float32)\n    out[1:-1, 1:-1] = code.astype(np.float32)\n    # normalize 0..1\n    out = (out - out.min()) / (out.max() - out.min() + 1e-6)\n    return out\n\ndef pp_texture_lbp(img_bgr):\n    rgb = _resize_rgb(img_bgr)\n    g = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)\n    lbp01 = _lbp_simple(g)\n    e3 = np.repeat(lbp01[..., None], 3, axis=-1)\n    return _scale_out(e3)\n\n# -------- 5) Augmentation (Rotation only) --------\ndef _rotate_only(rgb, max_deg=15):\n    h, w = rgb.shape[:2]\n    M = cv2.getRotationMatrix2D((w/2, h/2), np.random.uniform(-max_deg, max_deg), 1.0)\n    return cv2.warpAffine(rgb, M, (w, h), borderMode=cv2.BORDER_REFLECT101)\n\ndef pp_aug_rotate(img_bgr):\n    rgb = _resize_rgb(img_bgr).astype(np.float32) / 255.0\n    rgb = _rotate_only(rgb)\n    return _scale_out(np.clip(rgb, 0, 1))\n\n# ------------ Register pipelines -------------\nPIPELINES = {\n    \"Standard\":           pp_standard,\n    \"Lighting\":           pp_lighting,\n    \"Noise_Gaussian\":     pp_noise_gaussian,\n    \"Texture_LBP\":        pp_texture_lbp,   # <-- now registered\n    \"Feature_Edges\":      pp_feature_edges,\n    \"BackgroundRemoval\":  pp_bg_grabcut,\n    \"MultiScale\":         pp_multiscale,\n    \"Aug_Rotate\":         pp_aug_rotate,\n}\n\n# -------------- Data loading ----------------\ndef list_files(root, per_class=None):\n    X, y = [], []\n    for ci, cname in enumerate(CLASS_NAMES):\n        p = os.path.join(root, cname, \"*.jpg\")\n        files = sorted(glob.glob(p))\n        if per_class:\n            files = files[:per_class]\n        X += files\n        y += [ci] * len(files)\n    return np.array(X), np.array(y, dtype=np.int32)\n\nALL_FILES, ALL_LABELS = list_files(DATA_DIR, PER_CLASS_LIMIT)\nprint(f\"Loaded paths: {len(ALL_FILES)}\")\n\ndef load_images(paths, preprocess_fn):\n    xs = []\n    for p in paths:\n        bgr = cv2.imread(p, cv2.IMREAD_COLOR)\n        if bgr is None:\n            continue\n        xs.append(preprocess_fn(bgr))\n    return np.array(xs, dtype=np.float32)\n\n# -------------- Split 70/15/15 --------------\ndef split_70_15_15(X, y):\n    idx = np.arange(len(y))\n    sss1 = StratifiedShuffleSplit(n_splits=1, test_size=0.30, random_state=SEED)\n    tr_idx, tmp_idx = next(sss1.split(idx, y))\n    y_tr, y_tmp = y[tr_idx], y[tmp_idx]\n\n    sss2 = StratifiedShuffleSplit(n_splits=1, test_size=0.50, random_state=SEED)\n    va_rel, te_rel = next(sss2.split(tmp_idx, y_tmp))\n    val_idx = tmp_idx[va_rel]\n    tst_idx = tmp_idx[te_rel]\n    return tr_idx, val_idx, tst_idx\n\n# -------------- Model -----------------------\ndef build_dense(input_dim, num_classes=10):\n    m = models.Sequential([\n        layers.Input(shape=(input_dim,)),\n        layers.Dense(256, activation=\"relu\"),\n        layers.Dropout(0.3),\n        layers.Dense(128, activation=\"relu\"),\n        layers.Dropout(0.3),\n        layers.Dense(num_classes, activation=\"softmax\"),\n    ])\n    m.compile(optimizer=\"adam\", loss=\"sparse_categorical_crossentropy\", metrics=[\"accuracy\"])\n    return m\n\n# -------------- Train one pipeline ----------\ndef train_one_pipeline(pname, pfunc):\n    print(f\"\\n===== Pipeline: {pname} =====\")\n    tr_idx, va_idx, te_idx = split_70_15_15(ALL_FILES, ALL_LABELS)\n\n    Xtr = load_images(ALL_FILES[tr_idx], pfunc)\n    Xva = load_images(ALL_FILES[va_idx], pfunc)\n    Xte = load_images(ALL_FILES[te_idx], pfunc)\n    ytr = ALL_LABELS[tr_idx]; yva = ALL_LABELS[va_idx]; yte = ALL_LABELS[te_idx]\n\n    # flatten -> PCA (fit on train only)\n    Xtr_flat = Xtr.reshape(len(Xtr), -1).astype(np.float32)\n    Xva_flat = Xva.reshape(len(Xva), -1).astype(np.float32)\n    Xte_flat = Xte.reshape(len(Xte), -1).astype(np.float32)\n\n    pca = PCA(n_components=PCA_KEEP, svd_solver=\"full\", random_state=SEED)\n    Xtr_p = pca.fit_transform(Xtr_flat)\n    Xva_p = pca.transform(Xva_flat)\n    Xte_p = pca.transform(Xte_flat)\n\n    input_dim = Xtr_p.shape[1]\n    model = build_dense(input_dim)\n\n    es = callbacks.EarlyStopping(monitor=\"val_accuracy\", patience=5, mode=\"max\",\n                                 restore_best_weights=True, verbose=1)\n    rlr = callbacks.ReduceLROnPlateau(monitor=\"val_loss\", factor=0.5, patience=2,\n                                      min_lr=1e-5, verbose=1)\n\n    hist = model.fit(\n        Xtr_p.astype(np.float32), ytr,\n        validation_data=(Xva_p.astype(np.float32), yva),\n        epochs=EPOCHS, batch_size=BATCH, verbose=1,\n        callbacks=[es, rlr]\n    )\n\n    va_acc = float(model.evaluate(Xva_p.astype(np.float32), yva, verbose=0)[1])\n    te_acc = float(model.evaluate(Xte_p.astype(np.float32), yte, verbose=0)[1])\n    print(f\"[{pname}] Val Acc: {va_acc:.4f} | Test Acc: {te_acc:.4f} | PCA dims: {input_dim}\")\n\n    if SHOW_CM:\n        y_pred = np.argmax(model.predict(Xte_p.astype(np.float32), verbose=0), axis=1)\n        cm = confusion_matrix(yte, y_pred, labels=list(range(10)))\n        disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=CLASS_NAMES)\n        disp.plot(cmap=plt.cm.Blues, xticks_rotation=45)\n        plt.title(f\"Confusion Matrix — {pname}\")\n        plt.show()\n\n    out = {\n        \"val_acc\": va_acc,\n        \"test_acc\": te_acc,\n        \"pca_dims\": int(input_dim),\n        \"history\": {k: [float(vv) for vv in hist.history[k]] for k in hist.history},\n    }\n    del Xtr, Xva, Xte, Xtr_flat, Xva_flat, Xte_flat, Xtr_p, Xva_p, Xte_p; gc.collect()\n    return out\n\n# -------------- Run all selected pipelines --------------\nRESULTS = {}\nfor pname in PIPELINE_ORDER:\n    if pname not in PIPELINES:\n        continue\n    RESULTS[pname] = train_one_pipeline(pname, PIPELINES[pname])\n\n# -------------- Accuracy histogram --------------\ndf_rows = [{\"pipeline\": k, \"test_acc\": RESULTS[k][\"test_acc\"]} for k in RESULTS]\ndf = pd.DataFrame(df_rows).sort_values(\"test_acc\", ascending=False)\nprint(\"\\n=== Test Accuracies ===\")\nprint(df)\n\nplt.figure(figsize=(8, 4.5))\nplt.bar(df[\"pipeline\"], df[\"test_acc\"])\nplt.ylim(0, 1.0)\nplt.ylabel(\"Test accuracy\")\nplt.title(\"Per-pipeline accuracy (70/15/15 split)\")\nplt.xticks(rotation=25)\nplt.grid(axis=\"y\", alpha=0.3)\nplt.show()\n\n# -------------- Enhancement samples --------------\nrng = np.random.default_rng(SEED)\nsample_idx = int(rng.integers(0, len(ALL_FILES)))\nsample_path = ALL_FILES[sample_idx]\norig_bgr = cv2.imread(sample_path, cv2.IMREAD_COLOR)\norig_rgb = cv2.cvtColor(cv2.resize(orig_bgr, (IMG_SIZE, IMG_SIZE)), cv2.COLOR_BGR2RGB)\n\nn_show = min(len(PIPELINE_ORDER), 6)  # keep the panel compact\nto_show = PIPELINE_ORDER[:n_show]\n\nplt.figure(figsize=(3*(n_show+1), 3))\nplt.subplot(1, n_show+1, 1)\nplt.imshow(orig_rgb); plt.axis(\"off\"); plt.title(\"Original\")\n\nfor i, name in enumerate(to_show, start=2):\n    proc = (PIPELINES[name](orig_bgr) if name in PIPELINES else orig_rgb)\n    if proc.dtype != np.uint8:\n        if SCALE_RANGE == \"[-1,1]\":\n            vis = np.clip((proc + 1.0)/2.0, 0, 1)\n        else:\n            vis = np.clip(proc, 0, 1)\n        vis = (vis * 255).astype(np.uint8)\n    else:\n        vis = proc\n    plt.subplot(1, n_show+1, i)\n    plt.imshow(vis); plt.axis(\"off\"); plt.title(name)\n\nplt.suptitle(f\"Enhancement samples — {os.path.basename(sample_path)}\", y=1.03)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-21T14:09:42.466295Z","iopub.execute_input":"2025-08-21T14:09:42.466588Z","iopub.status.idle":"2025-08-21T14:16:27.612080Z","shell.execute_reply.started":"2025-08-21T14:09:42.466566Z","shell.execute_reply":"2025-08-21T14:16:27.611088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============ Multi-sample enhancement panel ============\ndef show_enhancement_samples(sample_count=4, max_pipelines=6, seed=SEED):\n    \"\"\"\n    Displays a grid:\n      rows   -> different random images\n      cols   -> Original + selected pipelines\n    \"\"\"\n    rng = np.random.default_rng(seed)\n    n_samples = min(sample_count, len(ALL_FILES))\n    idxs = rng.choice(len(ALL_FILES), size=n_samples, replace=False)\n\n    # choose which pipelines to visualize (in the same order you train)\n    to_show = [p for p in PIPELINE_ORDER if p in PIPELINES][:max_pipelines]\n    ncols = 1 + len(to_show)\n    nrows = n_samples\n\n    plt.figure(figsize=(3*ncols, 3*nrows))\n    for r, idx in enumerate(idxs):\n        path = ALL_FILES[idx]\n        bgr = cv2.imread(path, cv2.IMREAD_COLOR)\n        if bgr is None:\n            continue\n\n        # Original\n        orig_rgb = cv2.cvtColor(cv2.resize(bgr, (IMG_SIZE, IMG_SIZE)), cv2.COLOR_BGR2RGB)\n        ax = plt.subplot(nrows, ncols, r*ncols + 1)\n        ax.imshow(orig_rgb); ax.axis(\"off\")\n        if r == 0:\n            ax.set_title(\"Original\", fontsize=10)\n\n        # Each pipeline\n        for c, name in enumerate(to_show, start=1):\n            proc = PIPELINES[name](bgr)\n            # Convert to displayable RGB uint8\n            if proc.dtype != np.uint8:\n                vis = (np.clip((proc + 1.0)/2.0, 0, 1)\n                       if SCALE_RANGE == \"[-1,1]\" else np.clip(proc, 0, 1))\n                vis = (vis * 255).astype(np.uint8)\n            else:\n                vis = proc\n\n            ax = plt.subplot(nrows, ncols, r*ncols + c + 1)\n            ax.imshow(vis); ax.axis(\"off\")\n            if r == 0:\n                ax.set_title(name, fontsize=10)\n\n    plt.suptitle(\"Enhancement samples (multiple)\", y=1.02)\n    plt.tight_layout()\n    plt.show()\n\n# Example: show 5 samples across the first 6 pipelines\nshow_enhancement_samples(sample_count=5, max_pipelines=6)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-21T14:20:25.576929Z","iopub.execute_input":"2025-08-21T14:20:25.577636Z","iopub.status.idle":"2025-08-21T14:20:26.990593Z","shell.execute_reply.started":"2025-08-21T14:20:25.577614Z","shell.execute_reply":"2025-08-21T14:20:26.989746Z"}},"outputs":[],"execution_count":null}]}