{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":39272,"databundleVersionId":4629629,"isSourceIdPinned":false,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from pathlib import Path\nimport pandas as pd\nimport numpy as np\n\nfrom sklearn.model_selection import train_test_split\n\nOUT_ROOT = Path(\"/kaggle/working/mammo_patient_split_v1\")\nOUT_ROOT.mkdir(parents=True, exist_ok=True)\n\ndef locate_rsna_root():\n    # Je cherche un train.csv qui contient patient_id / image_id / cancer\n    for csv_path in Path(\"/kaggle/input\").rglob(\"train.csv\"):\n        try:\n            df = pd.read_csv(csv_path, nrows=5)\n        except Exception:\n            continue\n        cols = set(df.columns)\n        if {\"patient_id\", \"image_id\", \"cancer\"}.issubset(cols):\n            # Je cherche un dossier d’images à côté\n            candidate_root = csv_path.parent\n            # RSNA: souvent train_images/ dans le même dossier\n            img_dir = candidate_root / \"train_images\"\n            if img_dir.exists():\n                return candidate_root\n            # parfois l’arborescence diffère, je tente de trouver train_images plus loin\n            for p in candidate_root.rglob(\"train_images\"):\n                if p.is_dir():\n                    return candidate_root\n            return candidate_root\n    raise FileNotFoundError(\"Impossible de trouver le dataset RSNA (train.csv avec patient_id/image_id/cancer).\")\n\nRSNA_ROOT = locate_rsna_root()\nprint(\"RSNA_ROOT =\", RSNA_ROOT)\n\nTRAIN_CSV = RSNA_ROOT / \"train.csv\"\nassert TRAIN_CSV.exists(), f\"train.csv introuvable: {TRAIN_CSV}\"\n\ndf = pd.read_csv(TRAIN_CSV)\nprint(\"train.csv loaded:\", df.shape)\nprint(df.columns.tolist()[:30])\ndf.head()\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Je garde uniquement les colonnes utiles\nkeep_cols = [\"patient_id\", \"image_id\", \"laterality\", \"view\", \"cancer\"]\nfor c in keep_cols:\n    if c not in df.columns:\n        raise ValueError(f\"Colonne manquante dans RSNA train.csv: {c}\")\n\ndf = df[keep_cols].copy()\n\n# Chemin DICOM: RSNA = train_images/{patient_id}/{image_id}.dcm\n# (si ton arborescence est différente, on ajustera ici)\ntrain_images_dir = None\nfor p in RSNA_ROOT.rglob(\"train_images\"):\n    if p.is_dir():\n        train_images_dir = p\n        break\nif train_images_dir is None:\n    raise FileNotFoundError(\"Dossier train_images introuvable.\")\n\ndf[\"dcm_path\"] = df.apply(lambda r: str(train_images_dir / str(r[\"patient_id\"]) / f\"{r['image_id']}.dcm\"), axis=1)\n\n# Vérif rapide de l’existence des fichiers (sur un échantillon pour éviter de perdre du temps)\nsample_paths = df[\"dcm_path\"].sample(min(200, len(df)), random_state=0).tolist()\nexists_rate = np.mean([Path(p).exists() for p in sample_paths])\nprint(f\"Existence DICOM (sample): {exists_rate*100:.1f}%\")\n\n# Label binaire\ndf[\"label\"] = df[\"cancer\"].astype(int)\n\n# Petite vue sur le déséquilibre\npos_rate = df[\"label\"].mean()\nprint(f\"Pos rate (image-level) = {pos_rate*100:.2f}%\")\ndf.head()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_patient_splits(df_manifest: pd.DataFrame, seed: int,\n                        train_ratio=0.70, val_ratio=0.15, test_ratio=0.15):\n    assert abs(train_ratio + val_ratio + test_ratio - 1.0) < 1e-6\n\n    # Je passe au niveau patient pour faire un split propre (pas de fuite)\n    # patient_label = 1 si le patient a au moins une image cancer=1\n    patient = (\n        df_manifest.groupby(\"patient_id\")[\"label\"]\n        .max()\n        .reset_index()\n        .rename(columns={\"label\": \"patient_label\"})\n    )\n\n    # split train vs temp (val+test)\n    p_train, p_temp = train_test_split(\n        patient,\n        test_size=(1.0 - train_ratio),\n        random_state=seed,\n        stratify=patient[\"patient_label\"]\n    )\n\n    # split temp en val/test\n    val_share_of_temp = val_ratio / (val_ratio + test_ratio)\n    p_val, p_test = train_test_split(\n        p_temp,\n        test_size=(1.0 - val_share_of_temp),\n        random_state=seed,\n        stratify=p_temp[\"patient_label\"]\n    )\n\n    train_ids = set(p_train[\"patient_id\"].tolist())\n    val_ids   = set(p_val[\"patient_id\"].tolist())\n    test_ids  = set(p_test[\"patient_id\"].tolist())\n\n    df_train = df_manifest[df_manifest[\"patient_id\"].isin(train_ids)].copy()\n    df_val   = df_manifest[df_manifest[\"patient_id\"].isin(val_ids)].copy()\n    df_test  = df_manifest[df_manifest[\"patient_id\"].isin(test_ids)].copy()\n\n    # Vérif aucune fuite\n    assert len(train_ids & val_ids) == 0 and len(train_ids & test_ids) == 0 and len(val_ids & test_ids) == 0\n\n    def stats(name, dfi):\n        pids = dfi[\"patient_id\"].nunique()\n        imgs = len(dfi)\n        pos_img = int(dfi[\"label\"].sum())\n        pos_pat = int(dfi.groupby(\"patient_id\")[\"label\"].max().sum())\n        print(f\"{name:>5} | patients={pids:5d} | images={imgs:7d} | pos_img={pos_img:6d} | pos_pat={pos_pat:4d} | pos_img_rate={pos_img/imgs*100:5.2f}%\")\n    stats(\"train\", df_train); stats(\"val\", df_val); stats(\"test\", df_test)\n\n    return df_train, df_val, df_test\n\nSEEDS = [42, 43, 44]\nfor s in SEEDS:\n    print(\"\\n=== SEED\", s, \"===\")\n    _ = make_patient_splits(df, seed=s)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def save_seed_splits(df_manifest, seed: int):\n    df_train, df_val, df_test = make_patient_splits(df_manifest, seed=seed)\n\n    out_dir = OUT_ROOT / f\"seed_{seed}\"\n    out_dir.mkdir(parents=True, exist_ok=True)\n\n    df_train.to_csv(out_dir / \"train.csv\", index=False)\n    df_val.to_csv(out_dir / \"val.csv\", index=False)\n    df_test.to_csv(out_dir / \"test.csv\", index=False)\n\n    # Je garde aussi un petit résumé pour le rapport\n    summary = {\n        \"seed\": seed,\n        \"train_patients\": df_train[\"patient_id\"].nunique(),\n        \"val_patients\": df_val[\"patient_id\"].nunique(),\n        \"test_patients\": df_test[\"patient_id\"].nunique(),\n        \"train_images\": len(df_train),\n        \"val_images\": len(df_val),\n        \"test_images\": len(df_test),\n        \"train_pos_img\": int(df_train[\"label\"].sum()),\n        \"val_pos_img\": int(df_val[\"label\"].sum()),\n        \"test_pos_img\": int(df_test[\"label\"].sum()),\n        \"train_pos_pat\": int(df_train.groupby(\"patient_id\")[\"label\"].max().sum()),\n        \"val_pos_pat\": int(df_val.groupby(\"patient_id\")[\"label\"].max().sum()),\n        \"test_pos_pat\": int(df_test.groupby(\"patient_id\")[\"label\"].max().sum()),\n    }\n    pd.DataFrame([summary]).to_csv(out_dir / \"split_summary.csv\", index=False)\n    print(\"Saved:\", out_dir)\n\nfor s in SEEDS:\n    save_seed_splits(df, seed=s)\n\nprint(\"\\nOK. Output root =\", OUT_ROOT)\nprint(\"Contenu:\", [p.name for p in OUT_ROOT.iterdir()])\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}