{"cells":[{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# ==============================================================================\n# RSNA Knee Abnormality Detection - Multilingual Clinical NLP Pseudo-Labeler (v2.0)\n# Supports: English, German, and Spanish Radiology Reports\n# Targets (12 RSNA Classes):\n#   1. ACL              7. PF OA\n#   2. MCL              8. Effusion\n#   3. Medial Meniscus  9. Synovitis\n#   4. Lateral Meniscus 10. Baker's Cyst\n#   5. Medial OA        11. Bone Contusion\n#   6. Lateral OA       12. Fracture\n# ==============================================================================\nimport os\nimport sys\nimport re\nimport numpy as np\nimport pandas as pd\nfrom tqdm.auto import tqdm\n\nKAGGLE_INPUT_DIR = \"/kaggle/input\"\nTRAIN_CSV = os.path.join(KAGGLE_INPUT_DIR, 'rsna-knee-abnormality-detection', 'train.csv')\nfor path in [\n    os.path.join(KAGGLE_INPUT_DIR, 'competitions', 'rsna-knee-abnormality-detection', 'train.csv'),\n    os.path.join(KAGGLE_INPUT_DIR, 'rsna-knee-abnormality-detection', 'train.csv'),\n    \"train.csv\"\n]:\n    if os.path.exists(path):\n        TRAIN_CSV = path\n        break\n\nOUTPUT_CSV = \"train_pseudo_labeled.csv\"\n\nTARGET_COLS = [\n    \"ACL\", \"MCL\", \"Medial Meniscus\", \"Lateral Meniscus\",\n    \"Medial OA\", \"Lateral OA\", \"PF OA\", \"Effusion\",\n    \"Synovitis\", \"Baker's\", \"Contusion\", \"Fracture\"\n]\n\n# Multilingual Positive Finding Patterns (English, German, Spanish)\nFINDING_TERMS = {\n    \"ACL\": [\n        r'\\b(acl|anterior cruciate)\\b.*\\b(tear|rupture|sprain|injury|defect|torn|laxity|avulsion)\\b',\n        r'\\b(vkb|vorderes kreuzband|kreuzband)\\b.*\\b(riss|ruptur|teilruptur|laesion|insuffizienz)\\b',\n        r'\\b(lca|ligamento cruzado anterior)\\b.*\\b(rotura|desgarro|lesion|esguince|rotura completa)\\b'\n    ],\n    \"MCL\": [\n        r'\\b(mcl|medial collateral)\\b.*\\b(tear|sprain|injury|defect|torn|thickening)\\b',\n        r'\\b(innenband|mediales kollateralband)\\b.*\\b(riss|ruptur|zerrung|laesion|dehnung)\\b',\n        r'\\b(lcm|ligamento colateral medial|ligamento colateral interno)\\b.*\\b(rotura|desgarro|lesion|esguince)\\b'\n    ],\n    \"Medial Meniscus\": [\n        r'\\b(medial meniscus|medial meniscal)\\b.*\\b(tear|defect|maceration|extrusion|complex tear|horizontal tear|radial tear|flap)\\b',\n        r'\\b(innenmeniskus|medialer meniskus)\\b.*\\b(riss|ruptur|laesion|komplexriss|horizontalriss|radialriss|degeneration)\\b',\n        r'\\b(menisco medial|menisco interno)\\b.*\\b(rotura|desgarro|lesion|fisura|rotura compleja|degeneracion)\\b'\n    ],\n    \"Lateral Meniscus\": [\n        r'\\b(lateral meniscus|lateral meniscal)\\b.*\\b(tear|defect|maceration|complex tear|horizontal tear|radial tear|flap)\\b',\n        r'\\b(aussenmeniskus|lateraler meniskus)\\b.*\\b(riss|ruptur|laesion|komplexriss|horizontalriss|radialriss|degeneration)\\b',\n        r'\\b(menisco lateral|menisco externo)\\b.*\\b(rotura|desgarro|lesion|fisura|rotura compleja|degeneracion)\\b'\n    ],\n    \"Medial OA\": [\n        r'\\b(medial)\\b.*\\b(osteoarthritis|cartilage loss|joint space narrowing|chondromalacia|cartilage defect|subchondral sclerosis)\\b',\n        r'\\b(mediale|mediales)\\b.*\\b(gonarthrose|knorpelschaden|knorpeldefekt|gelenkspaltverschmaelerung|arthrose)\\b',\n        r'\\b(compartimento medial|compartimento interno)\\b.*\\b(artrosis|osteoartritis|gonartrosis|desgaste|condromalacia|perdida de cartilago)\\b'\n    ],\n    \"Lateral OA\": [\n        r'\\b(lateral)\\b.*\\b(osteoarthritis|cartilage loss|joint space narrowing|chondromalacia|cartilage defect|subchondral sclerosis)\\b',\n        r'\\b(laterale|laterales)\\b.*\\b(gonarthrose|knorpelschaden|knorpeldefekt|gelenkspaltverschmaelerung|arthrose)\\b',\n        r'\\b(compartimento lateral|compartimento externo)\\b.*\\b(artrosis|osteoartritis|gonartrosis|desgaste|condromalacia|perdida de cartilago)\\b'\n    ],\n    \"PF OA\": [\n        r'\\b(patellofemoral|trochlear|patella)\\b.*\\b(osteoarthritis|chondromalacia|cartilage loss|cartilage defect|arthrosis)\\b',\n        r'\\b(retropatellar|retropatellare|femoropatellar)\\b.*\\b(arthrose|knorpelschaden|chondropathie|gonarthrose)\\b',\n        r'\\b(femororrotuliana|patelofemoral|rotula)\\b.*\\b(artrosis|condromalacia|desgaste|condropatia|osteoartritis)\\b'\n    ],\n    \"Effusion\": [\n        r'\\b(effusion|joint fluid|fluid collection|hydrarthrosis)\\b',\n        r'\\b(erguss|gelenkerguss|gelenkfluessigkeit|intraartikulaerer erguss)\\b',\n        r'\\b(derrame|derrame articular|liquido articular|hidrartrosis)\\b'\n    ],\n    \"Synovitis\": [\n        r'\\b(synovitis|synovial thickening|synovial proliferation|synovial enhancement)\\b',\n        r'\\b(synovialitis|synovitis|synoviale verdickung|synovialreizung)\\b',\n        r'\\b(sinovitis|engrosamiento sinovial|proliferacion sinovial)\\b'\n    ],\n    \"Baker's\": [\n        r'\\b(baker|popliteal cyst|gastrocnemius-semimembranosus bursa)\\b',\n        r'\\b(baker-zyste|bakerzyste|poplitealzyste)\\b',\n        r'\\b(quiste de baker|quiste popliteo|bursa de baker)\\b'\n    ],\n    \"Contusion\": [\n        r'\\b(bone contusion|bone marrow edema|marrow edema|bone bruise|trabecular injury)\\b',\n        r'\\b(knochenmarkoedem|knochenkontusion|bone bruise|knochenodem)\\b',\n        r'\\b(contusion osea|edema oseo|edema de medula osea|bruise oseo)\\b'\n    ],\n    \"Fracture\": [\n        r'\\b(fracture|avulsion fracture|cortical step-off|trabecular microfracture|tibial plateau fracture)\\b',\n        r'\\b(fraktur|knochenbruch|ausrissfraktur|tibiaplateaufraktur|mikrofraktur)\\b',\n        r'\\b(fractura|fisura osea|fractura por avulsion|fractura de meseta tibial)\\b'\n    ]\n}\n\n# Multilingual Negations\nNEGATIONS = [\n    # English\n    r'\\b(no|not|without|free of|negative for|intact|unremarkable|normal|preserved|absence of|denies|ruled out)\\b',\n    # German\n    r'\\b(kein|keine|keinen|ohne|frei von|regelrecht|intakt|unauffaellig|ohne befund|ausgeschlossen|abwesend)\\b',\n    # Spanish\n    r'\\b(no|sin|ausencia de|intacto|normal|descartado|conservado|sin signos de|libre de|negativo para)\\b'\n]\nCOMBINED_NEGATION_REGEX = re.compile(\"|\".join(NEGATIONS), re.IGNORECASE)\n\ndef parse_report_multilingual(text, finding_regex_list, context_window=40):\n    if not isinstance(text, str) or not text.strip():\n        return np.nan\n        \n    text_clean = text.lower().replace('\\n', ' ').replace('\\r', ' ')\n    \n    for pattern in finding_regex_list:\n        for match in re.finditer(pattern, text_clean):\n            start, end = match.span()\n            window = text_clean[max(0, start - context_window):min(len(text_clean), end + context_window)]\n            if COMBINED_NEGATION_REGEX.search(window):\n                return 0.05  # Strong True Negative\n            return 0.95      # Strong True Positive\n            \n    return np.nan\n\ndef generate_multilingual_pseudo_dataset():\n    print(\"=\" * 70)\n    print(\"🚀 RSNA Multilingual Clinical NLP Pseudo-Labeling Engine\")\n    print(\"=\" * 70)\n    \n    if not os.path.exists(TRAIN_CSV):\n        print(f\"Error: {TRAIN_CSV} not found.\")\n        return None\n        \n    df = pd.read_csv(TRAIN_CSV)\n    print(f\"Total Studies Loaded: {len(df)}\")\n    \n    if 'loss_weight' not in df.columns:\n        df['loss_weight'] = 1.0\n        \n    human_labeled_count = 0\n    pseudo_labeled_count = 0\n    \n    # Process all 12 targets\n    for col in TARGET_COLS:\n        if col not in df.columns:\n            df[col] = np.nan\n\n    for idx in tqdm(range(len(df)), desc=\"Parsing Multilingual Reports\"):\n        report = df.at[idx, 'Report'] if 'Report' in df.columns else \"\"\n        has_any_missing = any(pd.isna(df.at[idx, col]) for col in TARGET_COLS)\n        \n        if not has_any_missing:\n            human_labeled_count += 1\n            df.at[idx, 'loss_weight'] = 1.0\n            continue\n            \n        is_pseudo = False\n        for col in TARGET_COLS:\n            current_val = df.at[idx, col]\n            if pd.isna(current_val):\n                regex_patterns = FINDING_TERMS[col]\n                extracted_score = parse_report_multilingual(report, regex_patterns)\n                if not pd.isna(extracted_score):\n                    df.at[idx, col] = extracted_score\n                    is_pseudo = True\n                else:\n                    df.at[idx, col] = 0.10  # Low baseline prior for unmentioned findings\n                    \n        if is_pseudo:\n            pseudo_labeled_count += 1\n            df.at[idx, 'loss_weight'] = 0.85  # High-confidence pseudo weight\n            \n    # Fill remaining NaNs with prior\n    for col in TARGET_COLS:\n        df[col] = df[col].fillna(0.10)\n        \n    df.to_csv(OUTPUT_CSV, index=False)\n    print(\"\\n\" + \"=\" * 70)\n    print(f\"✅ Successfully Created '{OUTPUT_CSV}'!\")\n    print(f\"   - Fully Human-Annotated Studies: {human_labeled_count}\")\n    print(f\"   - Multilingual NLP Pseudo-Labeled Studies: {pseudo_labeled_count}\")\n    print(f\"   - Total Training Studies Ready: {len(df)}\")\n    print(\"=\" * 70)\n    return df\n\nif __name__ == \"__main__\":\n    generate_multilingual_pseudo_dataset()\n\n"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":4}