{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":154281},{"sourceType":"datasetVersion","sourceId":251736},{"sourceType":"datasetVersion","sourceId":13312655},{"sourceType":"datasetVersion","sourceId":18229736},{"sourceType":"datasetVersion","sourceId":18673450},{"sourceType":"datasetVersion","sourceId":18673646},{"sourceType":"datasetVersion","sourceId":18706996},{"sourceType":"datasetVersion","sourceId":18715672},{"sourceType":"datasetVersion","sourceId":18716507},{"sourceType":"datasetVersion","sourceId":18757740},{"sourceType":"datasetVersion","sourceId":18839182},{"sourceType":"datasetVersion","sourceId":18875869},{"sourceType":"datasetVersion","sourceId":18956429},{"sourceType":"datasetVersion","sourceId":19092884},{"sourceType":"datasetVersion","sourceId":19120128},{"sourceType":"kernelVersion","sourceId":340767035},{"sourceType":"kernelVersion","sourceId":340999829},{"sourceType":"modelInstanceVersion","sourceId":3732},{"sourceType":"modelInstanceVersion","sourceId":4533},{"sourceType":"modelInstanceVersion","sourceId":4534},{"sourceType":"modelInstanceVersion","sourceId":658774}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false},"rsna_optimization":{"official_source_score":0.891,"revision":"v66-v65-parent-legacy-dino-002","source":"pilkwang/rsna-knee-baseline-v1"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Bend the Knee Ensemble\n\nA community pipeline, with one more model added.\n\n## Thank you\n\nThis is built on work other people did first and shared:\n\n- **pilkwang** — twenty trained models that are part of this ensemble, and a\n  set of labels read from the reports.\n- **stevenleehans** and **lixin73** — two more sets of labels read from the\n  reports, so one reading could be checked against another.\n- **tonylica** — four more trained models that join the ensemble.\n- **marwanmath** — the official RadImageNet ResNet-50 weights.\n- **prvsiyan** — the notebook this was forked from (Apache 2.0). Most of its\n  later stages have been removed here.\n- **cf696666** (*fishface*) — for the earliest verified E10/E11 RadImageNet\n  machinery, including the four-slot E11 design, and for leaving two findings\n  out of the E10 blend.\n- **romantamrazov** — for the fold-rank aggregation idea, and for the\n  adaptive second-pass weighting this version uses: the RadImageNet second\n  pass is no longer blended in at one fixed weight for all twelve findings,\n  but at a weight set per finding by how much the five folds agree and how\n  far the second pass departs from the first. His design, from his public\n  DINOsaur V3 notebook; the implementation here is our own.\n- **ieshanmeghani** — for isolating the public v15/E10 RadImageNet delta.\n- **sofiaanjenje** — for running and publishing the five-fold E11 and E13\n  RadImageNet heads, including the E13 fat-sensitive crop arm used here.\n- **saidmohamedomary** — for publishing V48, which surfaced the value of\n  adding the E13 arm to the deployed RadImageNet block.\n- **sakhawathossen** and **ranjithragavan07** — for publishing the\n  no-extra-pass fold-balanced/smooth DINO aggregation tested here.\n- **tonylica** — for publishing a run of this pipeline with the two per-finding\n  specialists left out. That version scored higher than the one with them, which\n  is what led to their removal here; the deployed recipe is otherwise unchanged.\n","metadata":{}},{"cell_type":"markdown","source":"### The added member\n\nThe submission this forks from is a rank mean of twenty **DINOv2** models. Added\nhere is one **DINOv3 ViT-S/16** — self-supervised and pretrained without labels,\nthen fine-tuned on this competition's knee MRI — rank-blended into that\nfamily.\n\nAbove the encoder, each series carries a learned type embedding — plane crossed\nwith fat suppression — added to all of its tokens. The tokens of every series in\na study are then concatenated into one key/value sequence, and twelve learned\nqueries, one per finding, cross-attend over it with multi-head attention. A\nfinding therefore draws evidence from any series in the study at once, rather\nthan from per-series summaries combined afterwards. Each query's output is\nconcatenated with the mean and the max of the per-series CLS embeddings before\nthe classifier. Series a study does not contain are removed by the key-padding\nmask, so nothing is imputed for them.\n\n**RadImageNet** joins the vote in two pinned stages. The E13 heads are fused\ninto the pinned v15 reference family at 0.50 on ranks; that family then gets a\n0.50 rank vote on ten findings, with Baker's and Fracture preserved. The same\nE13 heads are then run a second time on the E11 slot layout — three\nnon-fat-suppressed planes at a 130 mm crop — and blended in at 0.15.\n\nAll twelve findings come from the image ensemble. Two earlier stages that\nreplaced individual findings with report and metadata models — Synovitis, and\nLateral Meniscus with Lateral OA — have been removed: they were unreachable in the deployed run, and\nleaving those findings to the image models scored better.","metadata":{}},{"cell_type":"code","source":"from __future__ import annotations\nimport re\nimport unicodedata\nTARGETS = ['ACL', 'MCL', 'Medial Meniscus', 'Lateral Meniscus', 'Medial OA', 'Lateral OA', 'PF OA', 'Effusion', 'Synovitis', \"Baker's\", 'Contusion', 'Fracture']\n_PRE = str.maketrans({'ı': 'i', 'İ': 'i', 'I': 'i', 'ß': 'ss', 'đ': 'd', 'Đ': 'd', 'ø': 'o', 'Ø': 'o', 'æ': 'ae', 'Æ': 'ae'})\n\ndef normalize(text: str) -> str:\n    if not isinstance(text, str):\n        return ''\n    text = text.translate(_PRE).lower()\n    text = unicodedata.normalize('NFKD', text)\n    text = ''.join((ch for ch in text if not unicodedata.combining(ch)))\n    text = text.replace('\\xad', '')\n    text = re.sub('[_\\\\-/\\\\\\\\]+', ' ', text)\n    text = re.sub('[ \\\\t]+', ' ', text)\n    return text\n_SENT_SPLIT = re.compile('(?<=[.;!?])\\\\s+|\\\\n+')\n\ndef unwrap(text: str) -> str:\n    if not isinstance(text, str):\n        return ''\n    out = []\n    for line in text.split('\\n'):\n        s = line.strip()\n        if out and out[-1] and (not re.search('[.;:!?>*•]$', out[-1])) and (len(out[-1].split()) >= 4) and s and (not s[:1].isupper()):\n            out[-1] = out[-1] + ' ' + s\n        else:\n            out.append(s)\n    return '\\n'.join(out)\n\ndef clauses(text: str):\n    norm = normalize(unwrap(text) if FEATURES['unwrap'] else text)\n    raw = [c.strip() for c in _SENT_SPLIT.split(norm) if c and c.strip()]\n    merged = []\n    for i, c in enumerate(raw):\n        if c.endswith(':') and len(c.split()) <= 14 and (i + 1 < len(raw)):\n            merged.append(c + ' ' + raw[i + 1])\n        merged.append(c)\n    out = []\n    for c in merged:\n        out.append(c)\n        if len(c.split()) > 25:\n            out.extend((p.strip() for p in c.split(',') if len(p.split()) > 2))\n    return out\nFEATURES = {'unwrap': True, 'directional_negation': True, 'oa_inherit': True, 'graded_pathology': True, 'synovitis_backoff': True}\n\ndef _rx(*alts: str) -> re.Pattern:\n    return re.compile('|'.join(alts))\nPRE_NEG = _rx('\\\\bno\\\\b', '\\\\bnot\\\\b', '\\\\bwithout\\\\b', '\\\\bnegative for\\\\b', '\\\\babsence\\\\b', '\\\\bno evidence\\\\b', '\\\\bfree of\\\\b', '\\\\bnone\\\\b', '\\\\bneither\\\\b', '\\\\bnor\\\\b', '\\\\bsin\\\\b', '\\\\bno hay\\\\b', '\\\\bausencia\\\\b', '\\\\bausentes?\\\\b', '\\\\bno se\\\\b', '\\\\bpas de\\\\b', '\\\\bsans\\\\b', '\\\\baucune?\\\\b', '\\\\bgeen\\\\b', '\\\\bzonder\\\\b', '\\\\bniet\\\\b', '\\\\bkeine?[nmrs]?\\\\b', '\\\\bohne\\\\b', '\\\\bnicht\\\\b', '\\\\bkein\\\\b', '\\\\bnema\\\\b', '\\\\bbez\\\\b', '\\\\bnisu\\\\b', '\\\\bnije\\\\b', '\\\\bδεν\\\\b', '\\\\bχωρις\\\\b', 'ουδεν', '\\\\bουτε\\\\b', '\\\\bбез\\\\b', '\\\\bне\\\\b', 'липсва', '\\\\bняма\\\\b')\nPOST_NEG = _rx('\\\\byok\\\\b', '\\\\byoktur\\\\b', 'izlenmemekte', 'saptanmadi', '\\\\bdegil\\\\b', 'gozlenmemekte', 'mevcut degil', 'eslik etmiyor', '\\\\bizlenmedi\\\\b', 'izlenmemistir', 'saptanmamistir', 'gorulmemistir', '\\\\bnema znakova\\\\b', 'bez znakova')\nNEGATION = _rx(PRE_NEG.pattern, POST_NEG.pattern, '\\\\bunremarkable\\\\b')\nNEG_WINDOW = 90\n\ndef _negated(clause: str, start: int, end: int) -> bool:\n    for m in PRE_NEG.finditer(clause):\n        if m.end() <= start and start - m.end() <= NEG_WINDOW:\n            if not re.search('\\\\b(but|however|ancak|fakat|pero|maar|aber|no i|ali|ωστοσο|αλλα|но)\\\\b', clause[m.end():start]):\n                return True\n    for m in POST_NEG.finditer(clause):\n        if m.start() >= end and m.start() - end <= NEG_WINDOW:\n            return True\n    return False\nNORMALITY = _rx('\\\\bnormal', '\\\\bintact\\\\b', '\\\\bpreserved\\\\b', '\\\\bwithin normal limits\\\\b', 'limites normales', '\\\\bconservad', '\\\\bintegr', '\\\\bnormales\\\\b', '\\\\bdoga(l|ll)\\\\b', 'korunmus', '\\\\bnormaldir\\\\b', 'olagan', '\\\\buredn', '\\\\bocuvan', '\\\\bodrzan', '\\\\bintakt', '\\\\bprimjeren', '\\\\bodrzanog kontinuiteta', '\\\\bodržan', 'φυσιολογικ', 'ακεραι', 'δεν παρατηρουνται', 'δεν σημειωνονται', 'unauffallig', 'regelrecht', '\\\\bo\\\\.?b\\\\.?\\\\b', 'нормал', 'запазен', 'съхранен', '\\\\bбез особености\\\\b', 'интактн', '\\\\bgaaf\\\\b', '\\\\bnormaal\\\\b')\nNORMAL_PHRASE = _rx('\\\\bsin alteracion', '\\\\bsin cambios\\\\b', '\\\\bsin particularidad', '\\\\bsin hallazgos\\\\b', '\\\\bsin lesion', '\\\\bsin signos de (rotura|lesion)', '\\\\bcontinu[oa]s?\\\\b', '\\\\bcontinuidad conservada\\\\b', '\\\\bno abnormalit', '\\\\bno significant abnormalit', '\\\\bunremarkable\\\\b', '\\\\bno evidence of (tear|injury|abnormalit)', '\\\\bohne auffalligkeit', '\\\\bkein nachweis\\\\b', '\\\\bohne befund\\\\b', '\\\\bgeen afwijking', '\\\\bzonder afwijking', '\\\\bsans anomalie', \"\\\\bpas d[e']anomalie\", '\\\\bbez osobitosti\\\\b', '\\\\bbez znakova (rupture|lezije)\\\\b', '\\\\bbez patoloskih\\\\b', 'χωρις αλλοιωσ', 'χωρις παθολογ', 'δεν παρατηρουνται (αξιολογα|παθολογ)', '\\\\bбез особености\\\\b', '\\\\bбез патологич', '\\\\bбез данни за\\\\b', '\\\\bozel bir ozellik yok', '\\\\bpatolojik bulgu (yok|izlenmemis)')\nUNCERTAIN = _rx('\\\\bpossible\\\\b', '\\\\bprobable\\\\b', '\\\\bsuspicious\\\\b', '\\\\bsuspected?\\\\b', 'cannot (be )?exclude', '\\\\bmay\\\\b', '\\\\bquestionable\\\\b', '\\\\bequivocal\\\\b', '\\\\br/o\\\\b', '\\\\bdd\\\\b', '\\\\blikely\\\\b', '\\\\bsuggest', '\\\\bcompatible with\\\\b', '\\\\bposible\\\\b', 'sin criterios categoricos', '\\\\bdudos', '\\\\bsugier', '\\\\bmuhtemel\\\\b', '\\\\bolasi\\\\b', '\\\\bsupheli\\\\b', '\\\\bizlenim', '\\\\bdusundur', '\\\\bmoguce\\\\b', '\\\\bvjerojatno\\\\b', '\\\\bsumnja\\\\b', '\\\\bmoze odgovarati\\\\b', 'πιθαν', 'υποπτ', '\\\\bmoglich', '\\\\bverdachtig', '\\\\bfraglich', '\\\\bv\\\\.?a\\\\.?\\\\b', '\\\\bwohl\\\\b', '\\\\bвъзможно\\\\b', '\\\\bвероятно\\\\b', 'суспект', '\\\\bmogelijk\\\\b', '\\\\bverdacht\\\\b')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:54:28.937889Z","iopub.execute_input":"2026-09-02T10:54:28.938182Z","iopub.status.idle":"2026-09-02T10:54:28.963965Z","shell.execute_reply.started":"2026-09-02T10:54:28.938159Z","shell.execute_reply":"2026-09-02T10:54:28.963315Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TEAR = _rx('\\\\btear', '\\\\btorn\\\\b', '\\\\brupture', '\\\\bdisruption\\\\b', 'discontinuit', '\\\\bavuls', '\\\\bmacerat', '\\\\bbuckethandle\\\\b', 'bucket handle', '\\\\brotura\\\\b', '\\\\broturas\\\\b', '\\\\bruptura', '\\\\bdesgarro', '\\\\broto\\\\b', '\\\\bdechirure', '\\\\bdechire', '\\\\bscheur', '\\\\bruptuur', 'gescheurd', '\\\\briss\\\\b', 'einriss', '\\\\bruptur', 'zerreiss', '\\\\blasion', '\\\\bausriss', '\\\\byirtik', '\\\\byirtig', '\\\\bkopma\\\\b', 'butunluk kaybi', '\\\\brupturu\\\\b', 'devamsizlik', '\\\\brupture\\\\b', '\\\\bdevamliligi secilememis', '\\\\bpuknuce', '\\\\bprekid\\\\b', '\\\\bpukotin', '\\\\bruptur', 'ρηξη', 'ρηξις', 'ρηγμα', 'ασυνεχεια', 'руптура', 'разкъсв', 'разрив', 'скъсв', '\\\\bлезия\\\\b')\nDEGEN = _rx('degenerat', '\\\\bmucoid\\\\b', '\\\\bmyxoid\\\\b', '\\\\bfray', '\\\\bfissur', 'dejeneratif', '\\\\bmukoid\\\\b', 'degenerativn', 'εκφυλ', 'дегенерат', '\\\\bμυξοειδ', '\\\\bμυξωδ', '\\\\bmeniskopat', '\\\\bmeniscopath', '\\\\bmuco ?ide\\\\b', 'aufgefasert', '\\\\bdejenerasyon\\\\b')\nINJURY = _rx('\\\\binjur', '\\\\bsprain', '\\\\blesion', '\\\\blasion', '\\\\bedema\\\\b', '\\\\boedema\\\\b', '\\\\bodem\\\\b', '\\\\bedem\\\\b', '\\\\bοιδημα', '\\\\bодем', '\\\\bедем', '\\\\bstrain\\\\b', '\\\\bhigh signal\\\\b', '\\\\bsignal alteration\\\\b', '\\\\bhiperintens', '\\\\bhyperintens', 'aumento de senal', 'alteracion de senal', 'cambio de senal', '\\\\bsignalanhebung', '\\\\bsignalalteration', 'verhoogd signaal', 'sinyal artis', 'αυξημενο σημα', 'повишен сигнал', '\\\\besguince\\\\b', '\\\\bthicken', '\\\\bzadebljanje\\\\b', '\\\\bverdikking\\\\b', '\\\\bdistenzij', '\\\\blaksite\\\\b', '\\\\blaxity\\\\b', '\\\\bpartial\\\\b', '\\\\bparcijaln', '\\\\bparcial', '\\\\bpartiel', '\\\\bpartiell')\n_GRADE_RX = re.compile('(?:grade|grad|grado|grau|derece|stupnja|stupanj|βαθμ|степен|icrs|outerbridge)[\\\\s:]*(?:grade\\\\s*)?([1-4]|iv|iii|ii|i)\\\\b')\n_ROMAN = {'i': 1, 'ii': 2, 'iii': 3, 'iv': 4}\n\ndef _grade_of(clause: str):\n    best = None\n    for m in _GRADE_RX.finditer(clause):\n        v = m.group(1)\n        n = _ROMAN.get(v, None) if not v.isdigit() else int(v)\n        if n is not None and (best is None or n > best):\n            best = n\n    return best\nANAT = {'ACL': _rx('anterior cruciate', '\\\\bacl\\\\b', 'cruzado anterior', '\\\\blca\\\\b', 'croise anterieur', 'voorste kruisband', '\\\\bvkb\\\\b', 'vorderes kreuzband', 'vorderen kreuzband', 'vordere kreuzband', 'on capraz', '\\\\bocb\\\\b', 'anterior capraz', 'prednji krizni', 'prednjeg krizn', 'προσθι[οα][^ ]* χιαστ', 'προσθιου χιαστου', 'χιαστο[^ ]* συνδεσμ', '\\\\bχιαστ\\\\w*', 'предна кръстна', 'предната кръстна', 'предна кръста', 'cruciate ligaments', 'ligamentos cruzados', 'ligaments croises', 'kruisbanden', 'kreuzbander', 'capraz baglar', 'krizn[a-z]* ligament[a-z]*', 'χιαστοι συνδεσμ', 'χιαστων συνδεσμ', 'кръстните връзки', 'кръстни връзки'), 'MCL': _rx('medial collateral', '\\\\bmcl\\\\b', 'tibial collateral', 'colateral medial', 'colateral interno', '\\\\blcm\\\\b', 'collateral medial', 'collateral interne', 'mediale collaterale', 'binnenband', '\\\\b(mediale|laterale) banden\\\\b', '\\\\bcollaterale banden\\\\b', 'innenband', 'mediales? kollateral', '\\\\bic yan bag', 'medial kollateral', '\\\\biyb\\\\b', 'medyal kollateral', 'medijalni kolateraln', 'medijalnog kolateraln', 'εσω πλαγι', 'εσωτερικο πλαγι', '\\\\bπλαγι\\\\w* συνδεσμ', '\\\\bπλαγιοι\\\\b', 'медиален колатерал', 'вътрешна странична', '\\\\bколатерал\\\\w*', '\\\\bcolaterales\\\\b', '\\\\bcollateraux\\\\b', '\\\\bcollateralen\\\\b', '\\\\bkolateralni\\\\b', 'collateral ligaments', 'ligamentos colaterales', 'ligaments collateraux', 'collaterale banden', 'kollateralbander', 'seitenbander', 'yan baglar', 'kolateraln[a-z]* ligament[a-z]*', 'πλαγιοι συνδεσμ', 'πλαγιων συνδεσμ', 'колатерални връзки', 'страничните връзки'), 'Medial Meniscus': _rx('medial meniscus', '\\\\bmm\\\\b(?= tear)', 'medial menisc', 'menisco medial', 'menisco interno', 'menisque medial', 'menisque interne', 'mediale meniscus', 'binnenmeniscus', 'innenmeniskus', 'medialen? meniskus', 'innenmeniskushinterhorn', 'medyal menisk', '\\\\bic menisk', 'medijalni meniskus', 'medijalnog meniskusa', 'medijalnom meniskusu', 'medijaln\\\\w* menisk\\\\w*', '\\\\bmedijalnog meniska\\\\b', 'medijalni menisk', 'εσω μηνισκ', 'μηνισκ[^ ]* του εσω', 'εσω διαμερισμα[^.]{0,40}μηνισκ', 'медиалния менискус', 'медиален менискус', 'вътрешния менискус', 'oba meniska', 'both menisci', 'ambos meniscos', 'beide menisci', 'her iki menisku', 'amfoteroi\\\\w* mhnisk', 'αμφοτερ\\\\w* μηνισκ', 'двата менискуса', 'medial (and|&) lateral menisc'), 'Lateral Meniscus': _rx('lateral meniscus', 'lateral menisc', 'menisco lateral', 'menisco externo', 'menisque lateral', 'menisque externe', 'laterale meniscus', 'buitenmeniscus', 'aussenmeniskus', 'lateralen? meniskus', 'aussenmeniskushinterhorn', 'lateral menisk', '\\\\bdis menisk', 'lateralni meniskus', 'lateralnog meniskusa', 'lateralnom meniskusu', 'lateraln\\\\w* menisk\\\\w*', '\\\\blateralnog meniska\\\\b', 'εξω μηνισκ', 'μηνισκ[^ ]* του εξω', 'εξω διαμερισμα[^.]{0,40}μηνισκ', 'латералния менискус', 'латерален менискус', 'външния менискус', 'oba meniska', 'both menisci', 'ambos meniscos', 'beide menisci', 'her iki menisku', 'αμφοτερ\\\\w* μηνισκ', 'двата менискуса', 'medial (and|&) lateral menisc')}\nOA_EVIDENCE = _rx('osteoarthrit', '\\\\barthros', '\\\\bgonarthros', '\\\\bosteoarthros', 'chondropath', 'chondromalac', 'condropat', 'condromalac', '\\\\bchondros', '\\\\bchondrosis\\\\b', 'chondral (loss|defect|ulcer|thinning|injury|fissur|wear)', 'cartilage (loss|thinning|defect|fissur|wear|damage|heterogeneity|irregularit)', '(loss|thinning|fissur|defect|ulcer|erosion|denudation) of[^.]{0,20}cartilage', 'articular cartilage[^.]{0,30}(loss|thin|fissur|defect|erosion|wear|irregular)', 'osteophyt', 'osteofit', 'osteofyt', 'osteofito', 'osteophyten', 'spurring', 'joint space narrowing', 'pinzamiento articular', 'reduced joint space', 'kikirdak kayb', 'kikirdak incelme', 'kondropati', 'kondral', 'kikirdak dejener', 'eklem aralig\\\\w* daral', 'eklem mesafesi daral', 'kikirdak kalinlig\\\\w* azal', 'kraakbeen', 'gonartrose', 'artrose', '\\\\bknorpel', 'arthrose', 'gonarthrose', 'hrskavic', 'hondromalac', 'artroz', 'osteoartrit', 'artrotsk', 'artrotick', '\\\\boa promjen', '\\\\boa\\\\b', 'degenerativne promjene hrskav', 'χονδρ[^ ]*παθ', 'αρθριτ', 'αρθρωσ', 'οστεοφυτ', 'χονδρομαλακ', 'αρθρικου χονδρου', 'εξαλειψη του αρθρικου χονδρου', 'διαβρωση του αρθρικου χονδρ', 'λεπτυνση[^.]{0,30}χονδρ', 'φθορα[^.]{0,20}χονδρ', 'артроз', 'хондропат', 'остеофит', 'хрущял[^.]{0,40}(изтън|увред|дефект|липс)', 'изтъняване[^.]{0,30}хрущял', 'хондромалац', 'ulcera[s]? condral', 'cartilago[^.]{0,25}(perdida|adelgaz)', 'icrs grade', 'icrs\\\\b', 'outerbridge', '\\\\bdenudation\\\\b', 'denudacij', 'erozivne promjene', '\\\\berosion of[^.]{0,20}cartilage', 'kraakbeenlijden', 'kraakbeenverlies')\nTF_SITE = _rx('compartment', 'compartimento', 'compartiment', 'kompartman', 'kompartiment', 'kompartment', 'odjelj', 'διαμερισμα', 'компартм', '\\\\bотдел', 'femorotibial', 'tibiofemoral', 'femoro tibial', 'femorotibiaal', 'femorotibijaln', 'феморотибиал', '\\\\bft zglob', 'tibiofemoraln', 'condyle', 'condilo', 'kondyl', 'kondil', 'condyl', 'κονδυλ', 'кондил', '\\\\bplateau', '\\\\bplato\\\\b', 'platillo', 'meseta', 'плато', 'tibiaplateau', 'tibijaln\\\\w* plato', 'tibyal plato', 'tibia plato', 'κνημιαι', 'μηριαι', 'weightbearing', 'weightbaring', 'zona de carga', 'dragende deel', 'agirlik tasiyan', '\\\\bfemur\\\\b', '\\\\btibia\\\\b', '\\\\bfemoral\\\\b', '\\\\btibial\\\\b', '\\\\bfemura\\\\b', '\\\\btibije\\\\b', '\\\\bmesarthrio\\\\b', 'μεσαρθριο')\nPF_SITE = _rx('patellofemoral', 'femoropatellar', 'femoropatelar', 'patelofemoral', 'retropatellar', 'retrorotulian', 'trochlea', 'troclea', 'troklea', 'trochlear', 'trohlej', 'τροχιλ', '\\\\bpatella', '\\\\bpatellar', 'rotulian', '\\\\brotula\\\\b', '\\\\bpatele\\\\b', 'patellofemoraal', 'femoropatellair', 'επιγονατιδ', 'μηροεπιγονατιδ', 'пател', 'феморопател', 'anterior compartment', 'compartimento anterior', 'prednj\\\\w* odjeljk', '\\\\bfp zglob', '\\\\bpf zglob', '\\\\bfaset', '\\\\bfacet', 'patellofemoraln')\nSIDE_MEDIAL = _rx('\\\\bmedial\\\\w*', '\\\\bmedyal\\\\w*', '\\\\bmedijaln\\\\w*', '\\\\bmediaal\\\\w*', '\\\\bmediale\\\\w*', '\\\\binterno\\\\b', '\\\\binterna\\\\b', '\\\\binternos\\\\b', '\\\\binterne\\\\b', '\\\\binnen\\\\w*', '\\\\bic\\\\b', '\\\\bunutarnj\\\\w*', '\\\\bεσω\\\\w*', '\\\\bεσωτερικ\\\\w*', '\\\\bмедиал\\\\w*', '\\\\bвътреш\\\\w*', '\\\\bbinnen\\\\w*', '\\\\bmediaal\\\\b', '\\\\bmediales?\\\\b')\nSIDE_LATERAL = _rx('\\\\blateral\\\\w*', '\\\\bexterno\\\\b', '\\\\bexterna\\\\b', '\\\\bexternos\\\\b', '\\\\bexterne\\\\b', '\\\\bdis\\\\b', '\\\\blateraln\\\\w*', '\\\\baussen\\\\w*', '\\\\bbuiten\\\\w*', '\\\\bεξω\\\\w*', '\\\\bεξωτερικ\\\\w*', '\\\\bлатерал\\\\w*', '\\\\bвъншн\\\\w*', '\\\\bvanjsk\\\\w*')\nSIDE_ANTERIOR = _rx('\\\\banterior\\\\w*', '\\\\bant\\\\b', '\\\\bon\\\\b', '\\\\bprednj\\\\w*', '\\\\bvorder\\\\w*', '\\\\bvoorste\\\\b', '\\\\bπροσθι\\\\w*', '\\\\bпредн\\\\w*', '\\\\banteriyor\\\\w*', '\\\\bavant\\\\b', '\\\\banterieur\\\\w*')\nGLOBAL_OA = _rx('tri ?compartment', 'all three compartment', 'global(ised)? (oa|osteoarthrit)', '\\\\bgonarthros', '\\\\bgonartros', '\\\\bgonarthrose', '\\\\bgonartrose', 'gonartro', 'goanrtrot', 'gonartrot', 'osteoarthritis of the knee', 'artrosis (de |)(la )?rodilla', 'knee osteoarthrit', '\\\\bdiz osteoartrit', '\\\\bgonartroz', 'artroza koljena', 'οστεοαρθριτιδα', 'αρθριτιδα του γονατος', 'εκφυλιστικη οστεοαρθριτ', 'артроза на колянната', 'гонартроз', 'degenerative joint disease', '\\\\bdjd\\\\b', 'three compartments', 'compartmens', 'compartments')\nDIRECT = {'Effusion': _rx('\\\\beffusion', 'joint fluid', 'intra ?articular fluid', '\\\\bhydrops\\\\b', '\\\\bhemarthros', '\\\\bhaemarthros', 'derrame articular', '\\\\bderrame\\\\b', 'liquido articular', 'hemartrosis', 'epanchement', 'gewrichtsvocht', '\\\\bvocht\\\\b', 'gewrichtseffusie', 'opzetting van suprapatell', 'gelenkerguss', '\\\\berguss\\\\b', 'gelenksergu', 'gelenksflussigkeit', 'eklem\\\\w* ic\\\\w* sivi', 'efuzyon', 'eklem sivisi', 'eklem mesafesinde sivi', 'sivi (miktari|artisi|birikimi)', 'sivi artis', '\\\\bsivi\\\\b[^.]{0,25}artmis', '\\\\bizljev', '\\\\bizliv', 'zglobn[^ ]* tekucin', '\\\\bhidrops\\\\b', 'αρθρικ[^ ]* υγρ', 'υγρου ενδαρθρικα', 'ενδαρθρικ[^ ]* υγρ', 'ποσοτητα υγρου', 'ενδαρθρικ', 'αρθρικη συλλογη', 'υγρο στην αρθρωση', 'υγρου στην αρθρωση', 'συλλογη υγρου', 'ενθαρθρικ', 'ставен излив', 'излив', 'ставна течност', 'синовиална течност'), 'Synovitis': _rx('synovit', 'sinovit', 'synovial (thickening|proliferation|hypertroph)', 'thicken\\\\w* synovial', 'hypertroph\\\\w* of the synovium', 'synoviale? (verdikking|proliferatie)', 'verdikkingen van (het )?synovium', 'synovialitis', 'synovialis(verdickung|proliferation)', 'reizsynovial', 'sinovijalitis', 'sinovitis', 'zadebljanje sinovij', 'proliferacij\\\\w* sinovij', 'sinovijaln\\\\w* proliferacij', 'υμενιτιδα', 'συνοβιτιδα', 'υμενικ[^ ]* υπερτροφ', 'αρθρικου υμεν', 'παχυνση[^.]{0,20}υμεν', 'υμενα', 'синовит', 'синовиал[^ ]* (задебел|пролифер)', '\\\\bpannus\\\\b', '\\\\bhoffit', 'sinovyal\\\\w* (kalinlas|proliferas)', 'sinovyal hipertrof', '\\\\bartrit\\\\b', '\\\\barthritis\\\\b'), \"Baker's\": _rx('baker', 'popliteal cyst', 'quiste popliteo', 'quistes popliteos', 'kyste poplite', 'popliteale? cyst', 'poplitealzyste', 'bakerzyste', 'popliteal kist', '\\\\bbakerova\\\\b', 'poplitealn[^ ]* cist', 'popliteal\\\\w* cist', 'κυστη baker', 'πολυχωρη συνοβιακη κυστη', 'κυστη του baker', 'συνοβιακη κυστη', 'κυστη τυπου baker', 'киста на бейкър', 'бейкърова киста', 'поплитеална киста', 'бекеров', 'gastrocnemio ?semimembranos', 'gastrocnemius semimembranosus burs'), 'Contusion': _rx('\\\\bcontusion', 'bone bruise', 'bone marrow (o?edema|contusion)', 'marrow o?edema', '\\\\bkontuz', 'medular bone o?edema', 'osseous contusion', 'contusion osea', 'edema oseo', 'edema de medula osea', 'contusiones oseas', 'oedeme osseux', 'contusion osseuse', 'botcontusie', 'botoedeem', 'beenmergoedeem', 'botmergoedeem', 'knochenmarkodem', 'knochenodem', 'knochenmarksodem', 'kontusion', 'kemik kontuzyonu', 'kemik iligi odemi', 'kemik odemi', 'kemik iliginde odem', 'kontuzyonel kemik', 'kemik iligi odemleri', 'kostani edem', 'edem kosti', 'kontuzij', 'kostane srzi[^.]{0,20}edem', 'οστεομυελικ[^ ]* οιδημα', 'οστικο οιδημα', 'μυελικο οιδημα', 'οστικο μωλωπ', 'костномозъчен едем', 'костен едем', 'контузионен', 'костно мозъчен едем'), 'Fracture': _rx('\\\\bfractur', '\\\\bfract\\\\b', '\\\\bfractura', '\\\\bfracturas\\\\b', '\\\\bfractuur', '\\\\bbreuk\\\\b', '\\\\bfraktur', '\\\\bbruch\\\\b', '\\\\bkirik\\\\b', '\\\\bkirigi\\\\b', '\\\\bkiri[kg]\\\\w*', '\\\\bprijelom', 'impresijsk[^ ]* fraktur', 'impaktcij', 'καταγμα', 'καταγματ', 'фрактур', 'счупван', 'фисур', 'insufficiency fracture', 'stress fracture', 'avulsion fracture', 'subchondral fracture', 'subkondral kiri', 'impaction (fracture|injury)', 'osteochondral (fracture|impaction)', '\\\\bsegond\\\\b', 'impactiefractuur', 'subchondrale impression', 'subchondraler? impress')}\nDECOY = {'Fracture': _rx('microfractur', '\\\\bfracture (risk|prophyla)'), \"Baker's\": _rx('meniscal cyst', 'quiste meniscal', 'parameniscal')}\nPAIRED = {'ACL', 'MCL', 'Medial Meniscus', 'Lateral Meniscus'}\nOA_TARGETS = ['Medial OA', 'Lateral OA', 'PF OA']\nPLURAL_MENISCI = _rx('\\\\bmenisci\\\\b', '\\\\bmeniscos\\\\b', '\\\\bmenisques\\\\b', '\\\\bmenisken\\\\b', '\\\\bmeniskusi\\\\b', '\\\\bmenisk\\\\w*ler\\\\b', '\\\\bμηνισκοι\\\\b', '\\\\bμηνισκων\\\\b', '\\\\bменискуси\\\\b', '\\\\bменискусите\\\\b', '\\\\bmenisci\\\\w*\\\\b')\nANY_SIDE = _rx(SIDE_MEDIAL.pattern, SIDE_LATERAL.pattern)\nSTEM_MENISCUS = _rx('menisc\\\\w*', 'menisk\\\\w*', 'μηνισκ\\\\w*', 'мениск\\\\w*')\nSTEM_CRUCIATE = _rx('cruciate', 'cruzado', 'croise', 'kruisband', 'kreuzband', 'capraz bag\\\\w*', 'krizn\\\\w*', 'χιαστ\\\\w*', 'кръстн\\\\w*', '\\\\bacl\\\\b', '\\\\blca\\\\b', '\\\\bvkb\\\\b', '\\\\bocb\\\\b', '\\\\bacb\\\\b')\nSTEM_COLLATERAL = _rx('collateral\\\\w*', 'colateral\\\\w*', 'kollateral\\\\w*', 'collaterale\\\\w*', 'kolateraln\\\\w*', 'yan bag\\\\w*', 'πλαγι\\\\w*', 'колатерал\\\\w*', 'странич\\\\w*', 'innenband\\\\w*', 'binnenband\\\\w*', '\\\\bmcl\\\\b', '\\\\blcm\\\\b', '\\\\biyb\\\\b')\nSTEM_FRACTURE = _rx('fractur\\\\w*', 'fraktur\\\\w*', 'fractuur\\\\w*', '\\\\bfract\\\\b', 'kiri[kgğ]\\\\w*', 'prijelom\\\\w*', 'lom kosti', '\\\\bbreuk\\\\w*', '\\\\bbruch\\\\w*', 'καταγμα\\\\w*', 'καταγματ\\\\w*', 'фрактур\\\\w*', 'счупван\\\\w*', 'fisur\\\\w* (osea|oseas|kost)', 'fissur\\\\w* kost')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:54:47.657402Z","iopub.execute_input":"2026-09-02T10:54:47.65822Z","iopub.status.idle":"2026-09-02T10:54:47.697224Z","shell.execute_reply.started":"2026-09-02T10:54:47.658182Z","shell.execute_reply":"2026-09-02T10:54:47.696414Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def _near(clause: str, stem_rx: re.Pattern, qual_rx: re.Pattern, window: int=55):\n    for m in stem_rx.finditer(clause):\n        lo = max(0, m.start() - window)\n        hi = min(len(clause), m.end() + window)\n        if qual_rx.search(clause[lo:hi]):\n            return True\n    return False\nSTEM_RULES = {'ACL': (STEM_CRUCIATE, SIDE_ANTERIOR), 'MCL': (STEM_COLLATERAL, SIDE_MEDIAL), 'Medial Meniscus': (STEM_MENISCUS, SIDE_MEDIAL), 'Lateral Meniscus': (STEM_MENISCUS, SIDE_LATERAL)}\n\nclass _Matcher:\n\n    def __init__(self, phrase_rx, stem=None, side=None, window=55):\n        self.phrase_rx = phrase_rx\n        self.stem = stem\n        self.side = side\n        self.window = window\n\n    def search(self, clause):\n        m = self.phrase_rx.search(clause)\n        if m is not None:\n            return m\n        if self.stem is not None and _near(clause, self.stem, self.side, self.window):\n            return self.stem.search(clause)\n        return None\nANAT_MATCH = {t: _Matcher(ANAT[t], *STEM_RULES[t]) for t in PAIRED}\nDIRECT_MATCH = {t: _Matcher(_rx(rx.pattern, STEM_FRACTURE.pattern) if t == 'Fracture' else rx) for t, rx in DIRECT.items()}\nSEV_LOW = _rx('\\\\bsmall\\\\b', '\\\\bminimal\\\\b', '\\\\btrace\\\\b', '\\\\bmild\\\\b', '\\\\bslight\\\\b', '\\\\btiny\\\\b', '\\\\bscant\\\\b', '\\\\bdiscrete\\\\b', '\\\\blow ?grade\\\\b', '\\\\bincipient\\\\b', '\\\\bleve\\\\b', '\\\\bminim', '\\\\bpeque', '\\\\bfina\\\\b', '\\\\bfino\\\\b', '\\\\bligero\\\\b', '\\\\bescaso\\\\b', '\\\\bdiscreto\\\\b', '\\\\bhafif\\\\b', '\\\\baz miktarda\\\\b', '\\\\bsilik\\\\b', '\\\\bmanj\\\\w*', '\\\\bblago\\\\b', '\\\\bdiskretn', '\\\\bmalo\\\\b', '\\\\bpocetn', '\\\\bgering', '\\\\bdiskret', '\\\\bkleine?r?\\\\b', '\\\\bwenig\\\\b', '\\\\bzarte?\\\\b', '\\\\bbeperkte?\\\\b', '\\\\bgeringe\\\\b', '\\\\bweinig\\\\b', '\\\\blichte?\\\\b', '\\\\blicht\\\\b', '\\\\bηπι', '\\\\bμικρ', '\\\\bελαχιστ', '\\\\bαρχομεν', '\\\\bминимал', '\\\\bлек', '\\\\bмалк', '\\\\bнеголям')\nSEV_HIGH = _rx('\\\\blarge\\\\b', '\\\\bmarked\\\\b', '\\\\bmassive\\\\b', '\\\\bsevere\\\\b', '\\\\bextensive\\\\b', '\\\\bmoderate\\\\b', '\\\\bgross\\\\b', '\\\\bsignificant\\\\b', '\\\\babundant\\\\b', '\\\\btense\\\\b', '\\\\bcomplete\\\\b', '\\\\bfull ?thickness\\\\b', '\\\\bhigh ?grade\\\\b', '\\\\badvanced\\\\b', '\\\\bmoderad', '\\\\bimportante\\\\b', '\\\\bsevera?\\\\b', '\\\\bmarcad', '\\\\bcuantios', '\\\\bespesor total\\\\b', '\\\\bcompleta?\\\\b', '\\\\bbelirgin\\\\b', '\\\\byaygin\\\\b', '\\\\bileri\\\\b', '\\\\bciddi\\\\b', '\\\\bbol\\\\b', '\\\\bkomplet', '\\\\bopsezan\\\\b', '\\\\bveliki\\\\b', '\\\\bizrazit', '\\\\bznacajn', '\\\\bumjeren', '\\\\buznapredoval', '\\\\bpotpun', '\\\\bkompleksn', '\\\\bausgepragt', '\\\\bdeutlich', '\\\\bmassiv', '\\\\bmassig', '\\\\bgross', '\\\\buitgebreid', '\\\\bgevorderd', '\\\\bveel\\\\b', '\\\\bmatige?\\\\b', '\\\\bvolledig', '\\\\bμετρι', '\\\\bμεγαλ', '\\\\bεκτεταμεν', '\\\\bευμεγεθ', '\\\\bσοβαρ', '\\\\bπληρη', '\\\\bголям', '\\\\bизразен', '\\\\bзначим', '\\\\bумерен', '\\\\bобилен', '\\\\bпълн')\nGRADE_HIGH = re.compile('grade?[ao]?\\\\s*(3|4|iii|iv)\\\\b|icrs grade (iii|iv|3|4)|stupnja iv|stupnja iii|\\\\bgrado (3|4)\\\\b|\\\\bgrad (3|4)\\\\b|\\\\bgrade (3|4)\\\\b')\nDEGENERATIVE_MARROW = _rx('subchondral', 'subcondral', 'subkondral', 'supkondraln', 'subchondraln', 'υποχονδρι', 'υπαρθρικ', 'субхондрал', 'subchondrale?', 'subartikuler', '\\\\bcyst', '\\\\bquist', '\\\\bzyste\\\\b', '\\\\bcistic', 'reactive', 'reactivo', 'degenerative', 'degenerativ', 'reaktiv', '\\\\bcisti\\\\b')\nTRAUMA = _rx('\\\\bbruise\\\\b', '\\\\bcontusion', '\\\\bkontuz', '\\\\btrauma', '\\\\bimpaction\\\\b', '\\\\bpivot shift\\\\b', '\\\\bkissing\\\\b', '\\\\bacute\\\\b', '\\\\bagudo\\\\b', '\\\\bakut', '\\\\bpivot kaymasi\\\\b', '\\\\bcontusion osseuse\\\\b', '\\\\bbone bruise\\\\b', '\\\\bbotcontusie\\\\b', '\\\\bконтузион', '\\\\bμωλωπ', '\\\\bkontuzij', '\\\\bimpaktcij', '\\\\bimpakcij', '\\\\bfall\\\\b', '\\\\binjury\\\\b', '\\\\bimpression\\\\b')\nSYNOVIAL_PROXY = _rx('bursit', 'burzit', '\\\\bbursa\\\\b[^.]{0,30}(fluid|distend|sivi|tekucin|opzetting)', 'suprapatellar (bursitis|effusion|recess)', 'suprapatellar bursa', 'suprapatellar bursada', 'suprapatelarno', 'suprapatellaire recessus', 'hoffa', 'hoffit', 'plica', 'plika', 'πλικα', 'fat pad[^.]{0,20}(edema|oedema)', 'kapsul', 'capsul', 'καψ', 'капсул', '\\\\bpannus\\\\b', '\\\\bsinov', '\\\\bsynov')\n\ndef _polarity(clause: str, span=None) -> str:\n    if UNCERTAIN.search(clause):\n        return 'uncertain'\n    if span is None or not FEATURES['directional_negation']:\n        if NEGATION.search(clause):\n            return 'negative'\n    elif _negated(clause, span[0], span[1]):\n        return 'negative'\n    if NORMALITY.search(clause):\n        if TEAR.search(clause) or GRADE_HIGH.search(clause):\n            return 'positive'\n        return 'negative'\n    return 'positive'\n\ndef _severity(clause: str) -> float:\n    high = SEV_HIGH.search(clause) is not None\n    low = SEV_LOW.search(clause) is not None\n    if high and (not low):\n        return 1.0\n    if low and (not high):\n        return 0.45\n    if high and low:\n        return 0.8\n    return 0.75\n\ndef _grade(n_pos, n_neg, n_unc, best):\n    if n_pos or n_unc:\n        score = min(0.97, 0.5 + 0.45 * best + 0.015 * min(n_pos, 3))\n        conf = min(1.0, 0.55 + 0.15 * n_pos)\n    elif n_neg:\n        score = max(0.04, 0.2 - 0.04 * n_neg)\n        conf = min(0.9, 0.45 + 0.12 * n_neg)\n    else:\n        score, conf = (0.28, 0.05)\n    return (score, conf)\n\ndef _paired_weight(clause: str, meniscus: bool) -> float:\n    g = _grade_of(clause) if FEATURES['graded_pathology'] else None\n    tear = TEAR.search(clause) is not None\n    if meniscus:\n        if tear:\n            base = 1.0\n        elif g is not None:\n            base = 0.95 if g >= 3 else 0.3\n        elif DEGEN.search(clause):\n            base = 0.35\n        else:\n            base = 0.45\n    elif tear:\n        base = 1.0\n    elif g is not None:\n        base = 0.85 if g >= 2 else 0.3\n    elif DEGEN.search(clause):\n        base = 0.4\n    else:\n        base = 0.55\n    if SEV_HIGH.search(clause) and (not SEV_LOW.search(clause)):\n        base = min(1.0, base * 1.2)\n    elif SEV_LOW.search(clause) and (not SEV_HIGH.search(clause)):\n        base *= 0.7\n    return base\n\ndef _score_paired(cls, tgt):\n    anat_rx = ANAT_MATCH[tgt]\n    path_rx = _rx(TEAR.pattern, DEGEN.pattern, INJURY.pattern)\n    meniscus = 'Meniscus' in tgt\n    n_pos = n_neg = n_unc = 0\n    best = 0.0\n    for c in cls:\n        hit = anat_rx.search(c)\n        if hit is None and meniscus and PLURAL_MENISCI.search(c) and (not ANY_SIDE.search(c)):\n            hit = PLURAL_MENISCI.search(c)\n        if hit is None:\n            continue\n        pm = path_rx.search(c)\n        if pm is None and _grade_of(c) is None:\n            if NORMAL_PHRASE.search(c) or (NORMALITY.search(c) and (not NEGATION.search(c))):\n                n_neg += 1\n            continue\n        span = (pm.start(), pm.end()) if pm is not None else None\n        pol = _polarity(c, span)\n        if pol == 'positive':\n            n_pos += 1\n            best = max(best, _paired_weight(c, meniscus))\n        elif pol == 'negative':\n            n_neg += 1\n        else:\n            n_unc += 1\n            best = max(best, 0.45 * _paired_weight(c, meniscus))\n    s, cf = _grade(n_pos, n_neg, n_unc, best)\n    return (s, cf, n_pos, n_neg)\n\ndef _score_clauses(cls, anat_rx, path_rx=None, decoy_rx=None, context_penalty=None, context_bonus=None):\n    n_pos = n_neg = n_unc = 0\n    best = 0.0\n    for c in cls:\n        m = anat_rx.search(c)\n        if not m:\n            continue\n        if decoy_rx is not None and decoy_rx.search(c):\n            continue\n        if path_rx is not None and (not path_rx.search(c)):\n            if NORMAL_PHRASE.search(c) or (NORMALITY.search(c) and (not NEGATION.search(c))):\n                n_neg += 1\n            continue\n        pol = _polarity(c, (m.start(), m.end()))\n        if pol == 'positive':\n            n_pos += 1\n            w = _severity(c)\n            if context_penalty is not None and context_penalty.search(c):\n                w *= 0.45\n            if context_bonus is not None and context_bonus.search(c):\n                w = min(1.0, w * 1.35)\n            best = max(best, w)\n        elif pol == 'negative':\n            n_neg += 1\n        else:\n            n_unc += 1\n            best = max(best, 0.3)\n    s, c = _grade(n_pos, n_neg, n_unc, best)\n    return (s, c, n_pos, n_neg)\n\ndef _score_oa(cls):\n    acc = {t: {'pos': 0, 'neg': 0, 'unc': 0, 'best': 0.0} for t in OA_TARGETS}\n    g_pos, g_neg, g_best = (0, 0, 0.0)\n    for c in cls:\n        m = OA_EVIDENCE.search(c)\n        if not m:\n            continue\n        pol = _polarity(c, (m.start(), m.end()))\n        sev = _severity(c)\n        tf_med = _near(c, TF_SITE, SIDE_MEDIAL, 45)\n        tf_lat = _near(c, TF_SITE, SIDE_LATERAL, 45)\n        pf = PF_SITE.search(c) is not None\n        hits = []\n        if tf_med:\n            hits.append('Medial OA')\n        if tf_lat:\n            hits.append('Lateral OA')\n        if pf:\n            hits.append('PF OA')\n        if not hits:\n            if pol == 'positive':\n                g_pos += 1\n                g_best = max(g_best, sev if GLOBAL_OA.search(c) else sev * 0.7)\n            elif pol == 'negative':\n                g_neg += 1\n            continue\n        for t in hits:\n            if pol == 'positive':\n                acc[t]['pos'] += 1\n                acc[t]['best'] = max(acc[t]['best'], sev)\n            elif pol == 'negative':\n                acc[t]['neg'] += 1\n            else:\n                acc[t]['unc'] += 1\n                acc[t]['best'] = max(acc[t]['best'], 0.3)\n    out = {}\n    for t in OA_TARGETS:\n        a = acc[t]\n        pos, neg, unc, best = (a['pos'], a['neg'], a['unc'], a['best'])\n        if not (pos or unc) and g_pos and FEATURES['oa_inherit']:\n            if neg:\n                score, conf = _grade(0, neg, 0, 0.0)\n                score = max(score, 0.35)\n                conf *= 0.7\n            else:\n                score, conf = _grade(g_pos, 0, 0, g_best * 0.92)\n                conf *= 0.75\n        else:\n            score, conf = _grade(pos, neg + g_neg, unc, best)\n        out[t] = (score, conf, pos, neg)\n    return out\n\ndef extract(report: str) -> dict:\n    cls = clauses(report)\n    out = {}\n    for tgt in PAIRED:\n        s, c, npos, nneg = _score_paired(cls, tgt)\n        out[tgt] = s\n        out[tgt + '__conf'] = c\n        out[tgt + '__npos'] = npos\n        out[tgt + '__nneg'] = nneg\n    for tgt, (s, c, npos, nneg) in _score_oa(cls).items():\n        out[tgt] = s\n        out[tgt + '__conf'] = c\n        out[tgt + '__npos'] = npos\n        out[tgt + '__nneg'] = nneg\n    for tgt in ('Effusion', 'Synovitis', \"Baker's\", 'Contusion', 'Fracture'):\n        if tgt == 'Contusion':\n            s, c, npos, nneg = _score_clauses(cls, DIRECT_MATCH[tgt], None, DECOY.get(tgt), context_penalty=DEGENERATIVE_MARROW, context_bonus=TRAUMA)\n        else:\n            s, c, npos, nneg = _score_clauses(cls, DIRECT_MATCH[tgt], None, DECOY.get(tgt))\n        out[tgt] = s\n        out[tgt + '__conf'] = c\n        out[tgt + '__npos'] = npos\n        out[tgt + '__nneg'] = nneg\n    if FEATURES['synovitis_backoff'] and out['Synovitis__npos'] == 0 and (out['Synovitis__nneg'] == 0):\n        proxy = sum((1 for c in cls if SYNOVIAL_PROXY.search(c) and _polarity(c) == 'positive'))\n        eff = out['Effusion']\n        prior = 0.3 + 0.3 * max(0.0, (eff - 0.5) / 0.45) + 0.06 * min(proxy, 3)\n        out['Synovitis'] = min(0.72, prior)\n        out['Synovitis__conf'] = 0.18\n    return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:54:50.988817Z","iopub.execute_input":"2026-09-02T10:54:50.989533Z","iopub.status.idle":"2026-09-02T10:54:51.027591Z","shell.execute_reply.started":"2026-09-02T10:54:50.989504Z","shell.execute_reply":"2026-09-02T10:54:51.026896Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from __future__ import annotations\nimport os\nfor _v in ('OMP_NUM_THREADS', 'OPENBLAS_NUM_THREADS', 'MKL_NUM_THREADS'):\n    os.environ.setdefault(_v, '4')\nimport gc\nimport hashlib\nimport json\nimport re\nimport time\nimport traceback\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nimport pydicom\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\ndef _cuda_execution_probe(index):\n    dev = torch.device(f'cuda:{index}')\n    try:\n        major, minor = torch.cuda.get_device_capability(index)\n        probe = nn.Conv2d(3, 4, kernel_size=3, padding=1).eval().to(dev)\n        with torch.inference_mode():\n            out = probe(torch.zeros((1, 3, 16, 16), device=dev))\n            if tuple(out.shape) != (1, 4, 16, 16):\n                raise RuntimeError(f'unexpected CUDA probe shape {tuple(out.shape)}')\n        torch.cuda.synchronize(index)\n        print(f'cuda:{index} probe PASS (compute {major}.{minor})')\n        del probe, out\n        torch.cuda.empty_cache()\n        return True\n    except Exception as exc:\n        print(f'cuda:{index} probe FAIL ({type(exc).__name__}: {exc}); using CPU fallback')\n        try:\n            torch.cuda.empty_cache()\n        except Exception:\n            pass\n        return False\nDEVS = []\nif torch.cuda.is_available():\n    DEVS = [torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count()) if _cuda_execution_probe(i)]\nif not DEVS:\n    DEVS = [torch.device('cpu')]\nprint(f'devices: {[str(d) for d in DEVS]}')\nT0 = time.time()\nSEED = 2026\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\nTARGETS = ['ACL', 'MCL', 'Medial Meniscus', 'Lateral Meniscus', 'Medial OA', 'Lateral OA', 'PF OA', 'Effusion', 'Synovitis', \"Baker's\", 'Contusion', 'Fracture']\nCROP_MM = 130.0\nCACHE_IMG = 336\nGROUP = 3\nN_GROUP_MAX = 1\nCACHE_FRACTION = 0.45\nCACHE_BUDGET_MAX_GB = 24.0\nCACHE_BUDGET_GB = 12.0\nTEST_SHARE = 0.3\nHDR_THREADS = 16\nPIX_THREADS = 12\nORDER_THREADS = 32\nORDER_BUDGET_S = 5400\nRUNS = [{'name': 'r224', 'img': 224}, {'name': 'r336', 'img': 336}]\nEPOCHS = 10\nBATCH_STUDIES = 8\nAUG_ROT_DEG = 8.0\nAUG_SCALE = 0.08\nAUG_SHIFT = 0.05\nAUG_INTENSITY = 0.1\nLAT_MIN_OFFSET_MM = 20.0\nSLICE_BAND = (0.2, 0.8)\nRULES_NATIVE = {'order': 'normal', 'lat': 'centre', 'slot_fallback': False, 'decode_fill': 'nearest'}\nRULES_LEGACY = {'order': 'dominant_axis', 'lat': 'corner_x', 'slot_fallback': True, 'decode_fill': 'zero'}\nRULES = dict(RULES_NATIVE)\nLEGACY_LAT_OFFSET_MM = 5.0\nLR_HEAD = 0.001\nLR_BACKBONE = 8e-06\nUNFREEZE_LAST = 6\nWEIGHT_DECAY = 0.02\nEVAL_BATCH = 8\nTIME_BUDGET = 8.0 * 3600\nSLOTS_RECOVERED = [('SAG_FLUID_FS', 'Sagittal', True, True), ('COR_FLUID_FS', 'Coronal', True, True), ('AX_FLUID_FS', 'Axial', True, True), ('SAG_FLUID_NOFS', 'Sagittal', True, False), ('COR_T1', 'Coronal', False, False), ('SAG_T1', 'Sagittal', False, False)]\nSLOTS_PUBLIC = [('SAG_FLUID', 'Sagittal', None, True), ('COR_FLUID', 'Coronal', None, True), ('AX_FLUID', 'Axial', None, True), ('SAG_STRUCT', 'Sagittal', None, False), ('COR_STRUCT', 'Coronal', None, False), ('AX_STRUCT', 'Axial', None, False)]\nSLOT_SCHEME = os.environ.get('SLOT_SCHEME', 'recovered')\nSLOTS = SLOTS_PUBLIC if SLOT_SCHEME == 'public' else SLOTS_RECOVERED\nN_SLOT = len(SLOTS)\nPOOL_PARTS = {'cls_mean': 2, 'cls_mean_focal': 3}\nSLOT_PRIOR_TABLE = {'ACL': (0, 3, 5), 'MCL': (1, 4), 'Medial Meniscus': (0, 1, 3, 4), 'Lateral Meniscus': (0, 1, 3, 4), 'Medial OA': (1, 4, 5), 'Lateral OA': (1, 4, 5), 'PF OA': (0, 2, 5), 'Effusion': (0, 2), 'Synovitis': (0, 2), \"Baker's\": (0,), 'Contusion': (0, 1, 2), 'Fracture': (0, 1, 2, 4, 5)}\nSLOT_PRIOR_STRENGTH = 0.55\nFATSAT_OPTS = {'FS', 'FATSAT', 'FAT_SAT', 'FSAT'}\n_SEP = re.compile('[_\\\\-.]')\n_FATSAT_RX = re.compile('\\\\bfs\\\\b|fatsat|fat sat|\\\\bstir\\\\b|\\\\bspair\\\\b|\\\\bspir\\\\b|\\\\bwe\\\\b|water excit|\\\\btirm\\\\b|\\\\bsting\\\\b|\\\\bfatsup\\\\b')\n_T1_RX = re.compile('\\\\bt1\\\\b|\\\\bt1w\\\\b')\n_T2_RX = re.compile('\\\\bt2\\\\b|\\\\bt2w\\\\b')\n_PD_RX = re.compile('\\\\bpd\\\\b|\\\\bpdw\\\\b|proton|\\\\bdp\\\\b|dens')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:54:58.198882Z","iopub.execute_input":"2026-09-02T10:54:58.199272Z","iopub.status.idle":"2026-09-02T10:55:04.857892Z","shell.execute_reply.started":"2026-09-02T10:54:58.199244Z","shell.execute_reply":"2026-09-02T10:55:04.857254Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def log(msg):\n    print(f'[{time.time() - T0:7.1f}s] {msg}', flush=True)\n\ndef find_root():\n    for c in [Path('/kaggle/input/competitions/rsna-knee-abnormality-detection'), Path('/kaggle/input/rsna-knee-abnormality-detection'), Path('data'), Path('.')]:\n        if (c / 'test.csv').is_file() and (c / 'test_series').is_dir():\n            return c\n    base = Path('/kaggle/input')\n    if base.is_dir():\n        for depth1 in sorted((p for p in base.iterdir() if p.is_dir())):\n            for cand in [depth1] + sorted((p for p in depth1.iterdir() if p.is_dir())):\n                if (cand / 'test.csv').is_file():\n                    return cand\n    raise FileNotFoundError(f'competition mount not found (cwd {Path.cwd()}); expected a directory holding test.csv and test_series/')\n\ndef find_dinov2(variant='small'):\n    base = Path('/kaggle/input')\n    if not base.is_dir():\n        return None\n    hits = []\n    for root, dirs, files in os.walk(base):\n        dirs[:] = [d for d in dirs if d not in ('train_series', 'test_series')]\n        if 'config.json' in files and 'dinov2' in root.lower():\n            hits.append(Path(root))\n    for h in hits:\n        if variant in str(h).lower():\n            return h\n    return hits[0] if hits else None\nLABEL_COLS = TARGETS + [t + '__conf' for t in TARGETS]\n\nclass LabelSourceError(RuntimeError):\n    pass\n\ndef find_label_table():\n    base = Path('/kaggle/input')\n    cands = []\n    if base.is_dir():\n        for root, dirs, files in os.walk(base):\n            dirs[:] = [d for d in dirs if d not in ('train_series', 'test_series')]\n            cands += [Path(root) / f for f in files if f.startswith('report_labels') and f.endswith('.csv')]\n    cands += [p for p in (Path('data/derived/report_labels_v2.csv'),) if p.is_file()]\n    for c in cands:\n        try:\n            head = pd.read_csv(c, nrows=1)\n        except Exception:\n            continue\n        if 'StudyInstanceUID' in head.columns and all((t in head.columns for t in TARGETS)):\n            return c\n    return None\n\ndef label_mount_attached():\n    base = Path('/kaggle/input')\n    if not base.is_dir():\n        return False\n    return any(('label' in p.name.lower() for p in base.iterdir() if p.is_dir()))\n\ndef read_labels(train_df):\n    n = len(train_df)\n    lab = pd.DataFrame([extract(r) for r in train_df['Report'].fillna('')])\n    lab['StudyInstanceUID'] = train_df['StudyInstanceUID'].values\n    lab = lab.set_index('StudyInstanceUID')\n    src = find_label_table()\n    if src is None:\n        if label_mount_attached():\n            raise LabelSourceError('LABEL SOURCE: a label dataset is mounted but no usable table was found in it. Falling back to the lexicon here would train on the weaker labels and say so only in a log line, so the run stops instead.')\n        log(f'LABEL SOURCE: lexicon, {n} studies (no table mounted)')\n        return lab\n    tab = pd.read_csv(src).set_index('StudyInstanceUID')\n    missing = [c for c in LABEL_COLS if c not in tab.columns]\n    if missing:\n        raise LabelSourceError(f'LABEL SOURCE: {src} is missing {len(missing)} expected columns (first: {missing[0]!r}). Refusing to fall back silently.')\n    hit = lab.index.intersection(tab.index)\n    if not len(hit):\n        raise LabelSourceError(f'LABEL SOURCE: {src} shares no StudyInstanceUID with train.csv.')\n    log(f'LABEL SOURCE: {src.name} covers {len(hit)} of {n} studies, lexicon for the remaining {n - len(hit)}')\n    lab.loc[hit, LABEL_COLS] = tab.loc[hit, LABEL_COLS].values\n    return lab\nROOT = find_root()\nlog(f'input root: {ROOT}')\nIMG = CACHE_IMG\n\ndef available_gb():\n    try:\n        with open('/proc/meminfo') as fh:\n            info = {k.strip(): v for k, v in (l.split(':', 1) for l in fh if ':' in l)}\n        return int(info['MemAvailable'].split()[0]) / 1024 ** 2\n    except Exception:\n        return CACHE_BUDGET_GB / CACHE_FRACTION\n\ndef plan_cache(n_study, n_test=0):\n    avail = available_gb()\n    budget = min(avail * CACHE_FRACTION, CACHE_BUDGET_MAX_GB)\n    n_total = n_study + max(n_test, int(TEST_SHARE * n_study))\n    per_slice = n_total * N_SLOT * IMG * IMG\n    afford = int(budget * 1024 ** 3 // max(per_slice, 1))\n    groups = max(1, min(N_GROUP_MAX, afford // GROUP))\n    log(f'memory: {avail:.1f} GB available, {budget:.1f} GB to the cache; sizing for {n_study} train + {n_total - n_study} test studies -> {groups} group(s) of {GROUP} = {groups * GROUP} slices per slot' + (f' (wanted {N_GROUP_MAX})' if groups < N_GROUP_MAX else ''))\n    return groups\nN_GROUP = plan_cache(len(pd.read_csv(ROOT / 'train.csv')), len(pd.read_csv(ROOT / 'test.csv')))\nCACHE_SLICES = GROUP * N_GROUP\nlog(f'cache layout: {N_GROUP} groups x {GROUP} slices = {CACHE_SLICES} per slot')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:55:10.9737Z","iopub.execute_input":"2026-09-02T10:55:10.97454Z","iopub.status.idle":"2026-09-02T10:55:11.143344Z","shell.execute_reply.started":"2026-09-02T10:55:10.974508Z","shell.execute_reply":"2026-09-02T10:55:11.142534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"HDR_TAGS = ['SeriesDescription', 'SequenceName', 'ScanOptions', 'ScanningSequence', 'RepetitionTime', 'EchoTime', 'Laterality', 'PixelSpacing', 'Rows', 'Columns', 'RescaleSlope', 'RescaleIntercept', 'ImagePositionPatient', 'ImageOrientationPatient']\n\ndef _hdr_vec(s, n):\n    if not isinstance(s, str):\n        return None\n    try:\n        v = [float(x) for x in s.split('|')]\n    except ValueError:\n        return None\n    return np.array(v) if len(v) >= n else None\n\ndef side_from_geometry(h):\n    cx = {}\n    for r in h.itertuples(index=False):\n        ipp = _hdr_vec(getattr(r, 'ImagePositionPatient', None), 3)\n        iop = _hdr_vec(getattr(r, 'ImageOrientationPatient', None), 6)\n        ps = _hdr_vec(getattr(r, 'PixelSpacing', None), 2)\n        rows, cols = (getattr(r, 'Rows', None), getattr(r, 'Columns', None))\n        if ipp is None or iop is None or ps is None or (not rows) or (not cols):\n            continue\n        try:\n            c = ipp[:3] + iop[:3] * ps[1] * float(cols) / 2 + iop[3:6] * ps[0] * float(rows) / 2\n        except (TypeError, ValueError):\n            continue\n        cx.setdefault(r.StudyInstanceUID, []).append(float(c[0]))\n    out = {}\n    for st, xs in cx.items():\n        m = float(np.median(xs))\n        out[st] = None if abs(m) < LAT_MIN_OFFSET_MM else 'R' if m < 0 else 'L'\n    return out\n\ndef side_from_corner_x(h):\n    out = {}\n    for st, g in h.groupby('StudyInstanceUID'):\n        xs = []\n        for r in g.itertuples(index=False):\n            ipp = _hdr_vec(getattr(r, 'ImagePositionPatient', None), 3)\n            if ipp is not None and np.isfinite(ipp).all():\n                xs.append(float(ipp[0]))\n        if not xs:\n            out[st] = None\n            continue\n        x = float(np.median(xs))\n        out[st] = None if abs(x) < LEGACY_LAT_OFFSET_MM else 'R' if x < 0 else 'L'\n    return out\n\ndef lat_of(h, tag=''):\n    geo = side_from_corner_x(h) if RULES['lat'] == 'corner_x' else side_from_geometry(h)\n    d, n_tag, n_geo, n_none, n_disagree = ({}, 0, 0, 0, 0)\n    for st, g in h.groupby('StudyInstanceUID'):\n        v = [str(x).strip().upper() for x in g['Laterality'].dropna()]\n        if RULES['lat'] == 'corner_x' and 'ImageLaterality' in g.columns:\n            v += [str(x).strip().upper() for x in g['ImageLaterality'].dropna()]\n        v = [x[0] for x in v if x and x[0] in ('L', 'R')]\n        side = v[0] if v else None\n        if side is not None:\n            n_tag += 1\n            if geo.get(st) is not None and geo[st] != side:\n                n_disagree += 1\n        else:\n            side = geo.get(st)\n            n_geo += side is not None\n            n_none += side is None\n        d[st] = side\n    log(f'{tag}laterality: {n_tag} from the tag, {n_geo} from geometry, {n_none} unresolved; tag and geometry disagree on {n_disagree} ({n_disagree / max(n_tag, 1):.1%} of the tagged)')\n    return d\n\ndef probe(item):\n    split, study, series, path = item\n    row = {'split': split, 'StudyInstanceUID': study, 'SeriesInstanceUID': series, 'dir': path}\n    try:\n        files = sorted((e.name for e in os.scandir(path) if e.name.endswith('.dcm')))\n        row['files'] = files\n        row['n_slices'] = len(files)\n        if not files:\n            return row\n        ds = pydicom.dcmread(os.path.join(path, files[len(files) // 2]), stop_before_pixels=True, force=True)\n        for t in HDR_TAGS:\n            v = getattr(ds, t, None)\n            if v is None:\n                row[t] = None\n            elif isinstance(v, (list, tuple)) or type(v).__name__ == 'MultiValue':\n                row[t] = '|'.join((str(x) for x in v))\n            else:\n                row[t] = str(v)\n    except Exception as exc:\n        row['err'] = str(exc)[:120]\n    return row\n\ndef walk(split):\n    base = ROOT / split\n    items = []\n    if not base.is_dir():\n        return pd.DataFrame(columns=['split', 'StudyInstanceUID', 'SeriesInstanceUID', 'dir', 'files', 'n_slices'] + HDR_TAGS)\n    for study in os.scandir(base):\n        if study.is_dir():\n            for series in os.scandir(study.path):\n                if series.is_dir():\n                    items.append((split, study.name, series.name, series.path))\n    with ThreadPoolExecutor(max_workers=HDR_THREADS) as pool:\n        rows = list(pool.map(probe, items))\n    return pd.DataFrame(rows)\n\ndef annotate(df):\n    desc = df['SeriesDescription'].fillna('') + ' ' + df['SequenceName'].fillna('')\n    desc = desc.str.lower().str.replace(_SEP, ' ', regex=True)\n    opts = df['ScanOptions'].fillna('').str.upper().str.split('|')\n    opts_fs = opts.apply(lambda ts: any((t.strip() in FATSAT_OPTS for t in ts)))\n    df['fatsat'] = desc.str.contains(_FATSAT_RX) | opts_fs\n    tr = pd.to_numeric(df['RepetitionTime'], errors='coerce')\n    te = pd.to_numeric(df['EchoTime'], errors='coerce')\n    gre = df['ScanningSequence'].fillna('').str.upper().str.contains('GR')\n    t1, t2, pdw = (desc.str.contains(_T1_RX), desc.str.contains(_T2_RX), desc.str.contains(_PD_RX))\n    df['weight'] = np.where(t1 & ~t2 & ~pdw, 'T1', np.where(t2 & ~pdw, 'T2', np.where(pdw, 'PD', np.where(gre, 'GRE', np.where(tr < 800, 'T1', np.where(te > 60, 'T2', np.where(tr >= 800, 'PD', 'UNK')))))))\n    df['fluid'] = np.isin(df['weight'], ['PD', 'T2'])\n    df['px'] = pd.to_numeric(df['PixelSpacing'].fillna('').str.split('|').str[0].replace('', np.nan), errors='coerce')\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:55:22.704878Z","iopub.execute_input":"2026-09-02T10:55:22.705469Z","iopub.status.idle":"2026-09-02T10:55:22.727667Z","shell.execute_reply.started":"2026-09-02T10:55:22.70544Z","shell.execute_reply":"2026-09-02T10:55:22.726781Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def pick_slots(series_df, plane_map):\n    series_df = series_df.copy()\n    series_df['plane'] = series_df['SeriesInstanceUID'].map(plane_map)\n    out = {}\n    for study, g in series_df.groupby('StudyInstanceUID'):\n        chosen = {}\n        for name, plane, fluid, fs in SLOTS:\n            sel = (g['plane'] == plane) & (g['fatsat'] == fs)\n            if fluid is not None:\n                sel &= g['fluid'] == fluid\n            cand = g[sel]\n            if len(cand) == 0 and RULES['slot_fallback'] and (fluid is False):\n                cand = g[(g['plane'] == plane) & ~g['fatsat']]\n            if len(cand):\n                chosen[name] = cand.sort_values('n_slices', ascending=False).iloc[0]\n        out[study] = chosen\n    return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:55:27.353652Z","iopub.execute_input":"2026-09-02T10:55:27.354137Z","iopub.status.idle":"2026-09-02T10:55:27.360172Z","shell.execute_reply.started":"2026-09-02T10:55:27.354055Z","shell.execute_reply":"2026-09-02T10:55:27.359509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ORDER_TAGS = [(32, 50), (32, 55), (32, 19)]\nDECODE_FAILED = []\n\n\ndef _natural_key(name):\n    return tuple((int(x) if x.isdigit() else x.lower() for x in re.split('(\\\\d+)', str(name))))\n\ndef _order_dominant_axis(rec):\n    files, d = (rec['files'], rec['dir'])\n    rows = []\n    for pos, f in enumerate(files):\n        ipp = inst = None\n        try:\n            ds = pydicom.dcmread(os.path.join(d, f), force=True, stop_before_pixels=True, specific_tags=['ImagePositionPatient', 'InstanceNumber'])\n            raw = getattr(ds, 'ImagePositionPatient', None)\n            if raw is not None and len(raw) >= 3:\n                c = np.asarray(raw[:3], dtype=np.float64)\n                if np.isfinite(c).all():\n                    ipp = c\n            n = getattr(ds, 'InstanceNumber', None)\n            if n is not None:\n                inst = float(n)\n        except Exception:\n            pass\n        rows.append((f, ipp, inst, pos))\n    placed = [r for r in rows if r[1] is not None]\n    need = max(2, int(0.8 * len(rows)))\n    if len(placed) >= need:\n        xyz = np.stack([r[1] for r in placed])\n        axis = int(np.argmax(np.ptp(xyz, axis=0)))\n        spare = float(np.nanmedian(xyz[:, axis]))\n        rows.sort(key=lambda r: (float(r[1][axis]) if r[1] is not None else spare, r[2] if r[2] is not None else float('inf'), r[3]))\n    elif sum((r[2] is not None for r in rows)) >= need:\n        rows.sort(key=lambda r: (r[2] if r[2] is not None else float('inf'), r[3]))\n    else:\n        rows.sort(key=lambda r: _natural_key(r[0]))\n    return ([r[0] for r in rows], True)\n\ndef order_slices(rec):\n    if RULES['order'] == 'dominant_axis':\n        return _order_dominant_axis(rec)\n    files, d = (rec['files'], rec['dir'])\n    keyed = []\n    for f in files:\n        k = None\n        try:\n            ds = pydicom.dcmread(os.path.join(d, f), force=True, stop_before_pixels=True, specific_tags=ORDER_TAGS)\n            iop = np.asarray(ds.ImageOrientationPatient, dtype=float)\n            ipp = np.asarray(ds.ImagePositionPatient, dtype=float)\n            k = float(np.dot(ipp, np.cross(iop[:3], iop[3:])))\n        except Exception:\n            try:\n                k = float(ds.InstanceNumber)\n            except Exception:\n                k = None\n        keyed.append((k, f))\n    if any((k is None for k, _ in keyed)):\n        return (files, False)\n    return ([f for _, f in sorted(keyed, key=lambda t: t[0])], True)\n\ndef read_slot(rec, n_slice=None, out_size=None):\n    n_slice = GROUP if n_slice is None else n_slice\n    out_size = IMG if out_size is None else out_size\n    files, d, px = (rec.get('ordered') or rec['files'], rec['dir'], rec['px'])\n    n = len(files)\n    if n == 0:\n        return None\n    lo, hi = (int(SLICE_BAND[0] * (n - 1)), int(SLICE_BAND[1] * (n - 1)))\n    idx = np.unique(np.linspace(lo, hi, n_slice).astype(int)) if hi > lo else np.array([n // 2])\n    while len(idx) < n_slice:\n        idx = np.append(idx, idx[-1])\n    planes = []\n    for i in idx[:n_slice]:\n        try:\n            ds = pydicom.dcmread(os.path.join(d, files[int(i)]), force=True)\n            a = ds.pixel_array.astype(np.float32)\n            sl = float(getattr(ds, 'RescaleSlope', 1) or 1)\n            ic = float(getattr(ds, 'RescaleIntercept', 0) or 0)\n            a = a * sl + ic\n        except Exception:\n            a = None\n        planes.append(a)\n    got = [k for k, p in enumerate(planes) if p is not None]\n    if RULES['decode_fill'] == 'zero':\n        if not got:\n            DECODE_FAILED.append(rec.get('SeriesInstanceUID', d))\n        planes = [np.zeros((out_size, out_size), np.float32) if p is None else p for p in planes]\n        got = list(range(len(planes)))\n    if not got:\n        DECODE_FAILED.append(rec.get('SeriesInstanceUID', d))\n        return None\n    if len(got) < len(planes):\n        DECODE_FAILED.append(rec.get('SeriesInstanceUID', d))\n        for k, p in enumerate(planes):\n            if p is None:\n                planes[k] = planes[min(got, key=lambda j: abs(j - k))]\n    shp = planes[0].shape\n    planes = [p if p.shape == shp else np.zeros(shp, np.float32) for p in planes]\n    vol = np.stack(planes)\n    if px and np.isfinite(px) and (px > 0):\n        want = int(round(CROP_MM / px))\n        h, w = shp\n        if 16 < want < min(h, w):\n            cy, cx = (h // 2, w // 2)\n            half = want // 2\n            vol = vol[:, max(0, cy - half):cy + half, max(0, cx - half):cx + half]\n    lo_v, hi_v = np.percentile(vol, [1, 99])\n    vol = np.clip((vol - lo_v) / max(hi_v - lo_v, 1e-06), 0, 1)\n    t = torch.from_numpy(np.ascontiguousarray(vol)).unsqueeze(0)\n    t = F.interpolate(t, size=(out_size, out_size), mode='bilinear', align_corners=False)\n    return (t.squeeze(0) * 255).round().clamp(0, 255).to(torch.uint8)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:00.812893Z","iopub.execute_input":"2026-09-02T10:56:00.813261Z","iopub.status.idle":"2026-09-02T10:56:00.835141Z","shell.execute_reply.started":"2026-09-02T10:56:00.81323Z","shell.execute_reply":"2026-09-02T10:56:00.834206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalise_laterality(img, plane, lat):\n    if lat != 'R':\n        return img\n    if plane in ('Coronal', 'Axial'):\n        return torch.flip(img, dims=[-1])\n    return torch.flip(img, dims=[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:05.345392Z","iopub.execute_input":"2026-09-02T10:56:05.345844Z","iopub.status.idle":"2026-09-02T10:56:05.350744Z","shell.execute_reply.started":"2026-09-02T10:56:05.34581Z","shell.execute_reply":"2026-09-02T10:56:05.349947Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ORDER_CACHE = os.environ.get('RSNA_ORDER_CACHE') or None\n\ndef build_cache(slot_map, plane_map, lat_map, tag):\n    studies = sorted(slot_map)\n    sidx = {s: i for i, s in enumerate(studies)}\n    cache = np.zeros((len(studies), N_SLOT, CACHE_SLICES, IMG, IMG), np.uint8)\n    mask = np.zeros((len(studies), N_SLOT), np.float32)\n    log(f'{tag}: cache {cache.shape} = {cache.nbytes / 1024 ** 3:.1f} GB')\n    jobs = [(st, k, plane, slot_map[st][name]) for st in studies for k, (name, plane, _, _) in enumerate(SLOTS) if name in slot_map[st]]\n    n_job = len(jobs)\n    t_ord = time.time()\n    n_slice_total = sum((len(j[3]['files']) for j in jobs))\n    log(f'{tag}: ordering {len(jobs)} slot-series ({n_slice_total} slice headers)')\n    ok = done = 0\n    CHUNK_O = 1024\n    seen = {}\n    if ORDER_CACHE and Path(ORDER_CACHE).is_file():\n        try:\n            import json as _json\n            seen = _json.loads(Path(ORDER_CACHE).read_text())\n        except (OSError, ValueError):\n            seen = {}\n        hit = 0\n        for _, _, _, rec in jobs:\n            e = seen.get(rec['SeriesInstanceUID'])\n            if e and len(e['files']) == len(rec['files']):\n                rec['ordered'] = e['files']\n                ok += int(e['good'])\n                hit += 1\n        jobs = [j for j in jobs if 'ordered' not in j[3]]\n        log(f'{tag}: {hit} slot-series ordered from {ORDER_CACHE}, {len(jobs)} to read')\n    with ThreadPoolExecutor(max_workers=ORDER_THREADS) as pool:\n        for c0 in range(0, len(jobs), CHUNK_O):\n            block = jobs[c0:c0 + CHUNK_O]\n            for (_, _, _, rec), (files, good) in zip(block, pool.map(lambda j: order_slices(j[3]), block)):\n                rec['ordered'] = files\n                ok += int(good)\n                done += 1\n                if ORDER_CACHE:\n                    seen[rec['SeriesInstanceUID']] = {'files': files, 'good': bool(good)}\n            budget = min(ORDER_BUDGET_S, max(60.0, (TIME_BUDGET - (time.time() - T0)) * 0.35))\n            if time.time() - t_ord > budget:\n                log(f'{tag}: ordering budget spent at {done}/{len(jobs)}; the rest keep file order')\n                break\n    if ORDER_CACHE and done:\n        import json as _json\n        _t = Path(ORDER_CACHE).with_suffix('.tmp')\n        _t.write_text(_json.dumps(seen))\n        _t.replace(Path(ORDER_CACHE))\n    log(f'{tag}: ordered {ok}/{n_job} by geometry ({n_job - ok} kept arbitrary) in {time.time() - t_ord:.0f}s')\n    jobs = [(st, k, plane, slot_map[st][name]) for st in studies for k, (name, plane, _, _) in enumerate(SLOTS) if name in slot_map[st]]\n    log(f'{tag}: decoding {len(jobs)} slot-series')\n    n_failed_before = len(DECODE_FAILED)\n    CHUNK = 512\n    done = 0\n    with ThreadPoolExecutor(max_workers=PIX_THREADS) as pool:\n        for c0 in range(0, len(jobs), CHUNK):\n            block = jobs[c0:c0 + CHUNK]\n            for (st, k, plane, _), img in zip(block, pool.map(lambda j: read_slot(j[3], CACHE_SLICES, IMG), block)):\n                done += 1\n                if img is None:\n                    continue\n                cache[sidx[st], k] = normalise_laterality(img, plane, lat_map.get(st)).numpy()\n                mask[sidx[st], k] = 1.0\n            if done % 4096 < CHUNK:\n                log(f'  {tag} {done}/{len(jobs)}')\n            if time.time() - T0 > TIME_BUDGET:\n                log(f'  {tag}: time budget reached during decode')\n                break\n    n_failed = len(DECODE_FAILED) - n_failed_before\n    log(f'{tag}: {int(mask.sum())}/{len(jobs)} slots filled' + (f'; {n_failed} series had a slice that would not decode' if n_failed else ''))\n    gc.collect()\n    return (studies, cache, mask)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:15.844176Z","iopub.execute_input":"2026-09-02T10:56:15.844912Z","iopub.status.idle":"2026-09-02T10:56:15.859234Z","shell.execute_reply.started":"2026-09-02T10:56:15.844876Z","shell.execute_reply":"2026-09-02T10:56:15.858436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SlotHead(nn.Module):\n\n    def __init__(self, dim, n_slot, n_out, hidden=256, p=0.2, prior=False):\n        super().__init__()\n        self.proj = nn.Sequential(nn.LayerNorm(dim), nn.Linear(dim, hidden), nn.GELU())\n        self.slot_emb = nn.Parameter(torch.randn(n_slot, hidden) * 0.02)\n        self.query = nn.Parameter(torch.randn(n_out, hidden) * 0.02)\n        self.drop = nn.Dropout(p)\n        self.out = nn.Linear(hidden, n_out)\n        self.hidden = hidden\n        p_ = torch.zeros(n_out, n_slot)\n        if prior and n_slot == len(SLOTS) and (n_out == len(TARGETS)):\n            for t, slots in SLOT_PRIOR_TABLE.items():\n                if t in TARGETS:\n                    p_[TARGETS.index(t), list(slots)] = SLOT_PRIOR_STRENGTH\n        self.prior = prior\n        if prior:\n            self.register_buffer('slot_prior', p_)\n\n    def forward(self, x, mask):\n        h = self.proj(x) + self.slot_emb\n        att = torch.einsum('bsh,oh->bos', h, self.query) / self.hidden ** 0.5\n        if self.prior:\n            att = att + self.slot_prior.unsqueeze(0)\n        att = att.masked_fill(mask.unsqueeze(1) < 0.5, -10000.0).softmax(-1)\n        ctx = self.drop(torch.einsum('bos,bsh->boh', att, h))\n        return (ctx * self.out.weight.unsqueeze(0)).sum(-1) + self.out.bias","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:25.307312Z","iopub.execute_input":"2026-09-02T10:56:25.308266Z","iopub.status.idle":"2026-09-02T10:56:25.316901Z","shell.execute_reply.started":"2026-09-02T10:56:25.308225Z","shell.execute_reply":"2026-09-02T10:56:25.316148Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Model(nn.Module):\n\n    def __init__(self, backbone, dim, pool='cls_mean', prior=False):\n        super().__init__()\n        self.backbone = backbone\n        self.pool = pool\n        self.head = SlotHead(dim * POOL_PARTS[pool], N_SLOT, len(TARGETS), prior=prior)\n        self.register_buffer('mean', torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1))\n        self.register_buffer('std', torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1))\n\n    def forward(self, imgs, mask, img_size=None):\n        B, S = imgs.shape[:2]\n        x = imgs.reshape(B * S, *imgs.shape[2:]).float().div_(255.0)\n        if img_size is not None and img_size != x.shape[-1]:\n            x = F.interpolate(x, size=(img_size, img_size), mode='bilinear', align_corners=False)\n        x = (x - self.mean) / self.std\n        out = self.backbone(pixel_values=x).last_hidden_state\n        patch = out[:, 1:]\n        parts = [out[:, 0], patch.mean(1)]\n        if self.pool == 'cls_mean_focal':\n            k = max(1, patch.shape[1] // 8)\n            parts.append(patch.topk(k, dim=1).values.mean(1))\n        feat = torch.cat(parts, dim=1).reshape(B, S, -1)\n        return self.head(feat, mask)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:28.020799Z","iopub.execute_input":"2026-09-02T10:56:28.02138Z","iopub.status.idle":"2026-09-02T10:56:28.029306Z","shell.execute_reply.started":"2026-09-02T10:56:28.021347Z","shell.execute_reply":"2026-09-02T10:56:28.028605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_model(unfreeze_last, source=None, variant='small', pool='cls_mean', prior=False):\n    from transformers import AutoModel\n    p = source if source is not None else find_dinov2(variant)\n    if p is None:\n        raise FileNotFoundError('DINOv2 weights not attached')\n    bb = AutoModel.from_pretrained(str(p))\n    n_layer = len(bb.encoder.layer)\n    for prm in bb.parameters():\n        prm.requires_grad = False\n    for blk in bb.encoder.layer[max(0, n_layer - unfreeze_last):]:\n        for prm in blk.parameters():\n            prm.requires_grad = True\n    for prm in bb.layernorm.parameters():\n        prm.requires_grad = True\n    dim = bb.config.hidden_size\n    trainable = sum((p.numel() for p in bb.parameters() if p.requires_grad))\n    log(f'backbone: {n_layer} blocks, last {unfreeze_last} trainable ({trainable / 1000000.0:.1f}M params), feature dim {dim * POOL_PARTS[pool]}')\n    return Model(bb, dim, pool=pool, prior=prior)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:32.541617Z","iopub.execute_input":"2026-09-02T10:56:32.542362Z","iopub.status.idle":"2026-09-02T10:56:32.548867Z","shell.execute_reply.started":"2026-09-02T10:56:32.542334Z","shell.execute_reply":"2026-09-02T10:56:32.548132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"FINGERPRINT_TOL = 0.002\n\ndef fingerprint(model, dev, img_size, n_slot=None, group=None, seed=None):\n    n_slot = N_SLOT if n_slot is None else n_slot\n    group = GROUP if group is None else group\n    seed = SEED if seed is None else seed\n    g = torch.Generator().manual_seed(seed)\n    imgs = torch.randint(0, 256, (2, n_slot, group, img_size, img_size), generator=g, dtype=torch.uint8).to(dev)\n    mask = torch.ones(2, n_slot, device=dev)\n    mask[1, -1] = 0.0\n    was_training = model.training\n    model.eval()\n    with torch.no_grad():\n        out = model(imgs, mask, img_size).float().cpu().numpy()\n    if was_training:\n        model.train()\n    return out\n\ndef check_fingerprint(model, dev, img_size, expected, tol=FINGERPRINT_TOL, tag=''):\n    got = fingerprint(model, dev, img_size)\n    exp = np.asarray(expected, np.float32)\n    if got.shape != exp.shape:\n        raise WeightsError(f'{tag}fingerprint shape {got.shape} != stored {exp.shape}: the architecture is not the one these weights were fitted to')\n    d = float(np.abs(got - exp).max())\n    if d > tol:\n        raise WeightsError(f'{tag}fingerprint differs by {d:.4g} (tolerance {tol:g}). The weights load but do not compute what they computed when fitted - preprocessing, resolution or architecture has moved between the two runs.')\n    log(f'{tag}fingerprint matches within {d:.2g}')\n    return d\n\nclass WeightsError(RuntimeError):\n    pass\n\ndef find_weights(name='manifest.json'):\n    import json\n    base = Path('/kaggle/input')\n    if not base.is_dir():\n        return None\n    for root, dirs, files in os.walk(base):\n        dirs[:] = [d for d in dirs if d not in ('train_series', 'test_series')]\n        if name not in files:\n            continue\n        try:\n            man = json.loads((Path(root) / name).read_text())\n        except (OSError, ValueError):\n            continue\n        if isinstance(man.get('members'), list) and man['members']:\n            missing = [m['file'] for m in man['members'] if not (Path(root) / m['file']).is_file()]\n            if missing:\n                raise WeightsError(f\"{root} holds a manifest listing {len(man['members'])} members but {len(missing)} of their files are absent (first {missing[0]!r})\")\n            return Path(root)\n    return None\nTTA_OVERLAP = True\nTTA_POOL = 'prob'\nPUBLIC_FRONTIER_TARGET_POOL = {'Fracture': 'max', 'Contusion': 'max', 'Medial Meniscus': 'max', 'Lateral Meniscus': 'max', 'ACL': 'top2', 'MCL': 'top2', \"Baker's\": 'max'}\nTTA_TARGET_POOL = {**PUBLIC_FRONTIER_TARGET_POOL, 'Synovitis': 'original_mean'}\n# No-extra-pass diversity branch: smooth focal pooling is evaluated from\n# the same no-jitter public-member windows already used by the parent.\nLEGACY_FOLD_SOFTPOOL_BETA = {\n    'ACL': 6.0, 'MCL': 6.0,\n    'Medial Meniscus': 8.0, 'Lateral Meniscus': 8.0,\n    \"Baker's\": 8.0, 'Contusion': 8.0, 'Fracture': 10.0,\n}\nLEGACY_FOLD_SOFTPOOL_ALPHA = {\n    'ACL': 0.20, 'MCL': 0.20,\n    'Medial Meniscus': 0.25, 'Lateral Meniscus': 0.25,\n    \"Baker's\": 0.20, 'Contusion': 0.20, 'Fracture': 0.15,\n}\nLEGACY_MEMBER_WEIGHT_BY_TARGET = {'Lateral Meniscus': 15.0, 'Medial OA': 2.5, 'Lateral OA': 15.0, 'Contusion': 5.0}\n\ndef window_starts(n_slice, group, overlap=None):\n    overlap = TTA_OVERLAP if overlap is None else overlap\n    if overlap and n_slice >= group:\n        return list(range(n_slice - group + 1))\n    return [g * group for g in range(max(n_slice // group, 1))]\n\ndef apply_target_window_pool(values, probs, logits, original_probs, mapping, target_idx):\n    for target, mode in mapping.items():\n        j = target_idx[target]\n        if mode == 'max':\n            values[:, j] = probs[:, :, j].max(0).values\n        elif mode == 'mean':\n            values[:, j] = probs[:, :, j].mean(0)\n        elif mode == 'logit_mean':\n            values[:, j] = torch.sigmoid(logits[:, :, j].mean(0))\n        elif mode == 'original_mean':\n            values[:, j] = original_probs[:, :, j].mean(0)\n        elif mode in ('top2', 'top3'):\n            k = min(int(mode[3:]), probs.shape[0])\n            values[:, j] = probs[:, :, j].topk(k, dim=0).values.mean(0)\n        else:\n            raise ValueError(f'unknown TTA pooling mode for {target}: {mode}')\n    return values\n\ndef legacy_fold_soft_window_pool(original_probs, target_idx):\n    values = original_probs.mean(0).clone()\n    for target, beta in LEGACY_FOLD_SOFTPOOL_BETA.items():\n        j = target_idx[target]\n        x = original_probs[:, :, j]\n        weight = torch.softmax(float(beta) * x, dim=0)\n        values[:, j] = (weight * x).sum(0)\n    return values\n\n@torch.no_grad()\ndef predict_member(model, cache, mask, idx, dev, img_size, group=None, pool=None, starts=None, jitter=False, jitter_seed=SEED, return_public_frontier=False):\n    group = GROUP if group is None else group\n    pool = TTA_POOL if pool is None else pool\n    starts = window_starts(cache.shape[2], group) if starts is None else list(starts)\n    if not starts:\n        raise ValueError('predict_member was given no windows to average over')\n    target_idx = {t: j for j, t in enumerate(TARGETS)}\n    unknown = (set(TTA_TARGET_POOL) | set(PUBLIC_FRONTIER_TARGET_POOL)) - set(target_idx)\n    if unknown:\n        raise ValueError(f'unknown target(s) in TTA_TARGET_POOL: {unknown}')\n    jitter_gen = torch.Generator(device=dev)\n    jitter_gen.manual_seed(int(jitter_seed) % (2 ** 63 - 1))\n    model.eval()\n    out, public_frontier_out, public_soft_out = ([], [], [])\n    for b in range(0, len(idx), EVAL_BATCH):\n        sel = idx[b:b + EVAL_BATCH]\n        m = torch.from_numpy(mask[sel]).to(dev)\n        win_probs, win_logits, win_original_probs = ([], [], [])\n        for st in starts:\n            rows = torch.from_numpy(np.ascontiguousarray(cache[sel, :, st:st + group])).to(dev)\n            views = [rows] + ([augment(rows, generator=jitter_gen)] if jitter else [])\n            view_probs, view_logits = ([], [])\n            for view in views:\n                with torch.autocast('cuda', enabled=dev.type == 'cuda'):\n                    z = model(view, m, img_size).float()\n                view_logits.append(z)\n                view_probs.append(torch.sigmoid(z))\n            win_logits.append(torch.stack(view_logits).mean(0))\n            win_probs.append(torch.stack(view_probs).mean(0))\n            win_original_probs.append(view_probs[0])\n        probs = torch.stack(win_probs)\n        logits = torch.stack(win_logits)\n        original_probs = torch.stack(win_original_probs)\n        v = torch.sigmoid(logits.mean(0)) if pool == 'logit' else probs.mean(0)\n        v = apply_target_window_pool(v, probs, logits, original_probs, TTA_TARGET_POOL, target_idx)\n        out.append(v.cpu().numpy())\n        if return_public_frontier:\n            public_v = apply_target_window_pool(original_probs.mean(0), original_probs, logits, original_probs, PUBLIC_FRONTIER_TARGET_POOL, target_idx)\n            public_frontier_out.append(public_v.cpu().numpy())\n            public_soft = legacy_fold_soft_window_pool(original_probs, target_idx)\n            public_soft_out.append(public_soft.cpu().numpy())\n    primary = np.concatenate(out) if out else np.zeros((0, len(TARGETS)), np.float32)\n    if not return_public_frontier:\n        return primary\n    public_frontier = np.concatenate(public_frontier_out) if public_frontier_out else np.zeros((0, len(TARGETS)), np.float32)\n    public_soft = np.concatenate(public_soft_out) if public_soft_out else np.zeros((0, len(TARGETS)), np.float32)\n    return (primary, public_frontier, public_soft)\nBUILD_LOCK = threading.Lock()\nSTATE_LOCK = threading.Lock()\nLEGACY_BUNDLE_FILE = 'rsna_20260807_v1.pt'\nLEGACY_WEIGHT = 0.5\n\ndef find_legacy_bundle():\n    base = Path('/kaggle/input')\n    if not base.is_dir():\n        return None\n    for root, dirs, files in os.walk(base):\n        dirs[:] = [d for d in dirs if d not in ('train_series', 'test_series')]\n        if LEGACY_BUNDLE_FILE in files:\n            return Path(root) / LEGACY_BUNDLE_FILE\n    return None\n\ndef legacy_group_members():\n    p = find_legacy_bundle()\n    if p is None:\n        log('no legacy bundle attached; blending skipped')\n        return {}\n    try:\n        b = torch.load(p, map_location='cpu', weights_only=False)\n        folds = b.get('fold_states') or []\n        b_slots = [tuple(s)[0] for s in b.get('slots', SLOTS)]\n        if list(b.get('targets', TARGETS)) != TARGETS or b_slots != [s[0] for s in SLOTS]:\n            log(f'legacy bundle {p.name}: target/slot contract differs; blending skipped')\n            return {}\n        gr, n_gr = (int(b.get('group', 3)), int(b.get('n_group', 3)))\n        variant = str(b.get('model_variant', 'dinov2-small')).split('-')[-1]\n        key = json.dumps({'img': int(b.get('img', 224)), 'group': gr, 'slices': gr * n_gr, 'crop_mm': 160.0, 'band': [0.2, 0.8], 'rules': RULES_LEGACY, 'slots': [s[0] for s in SLOTS]}, sort_keys=True)\n        ms = [{'id': f\"legacy-f{f.get('fold', k)}\", 'fold': f.get('fold', k), 'state': f['state_dict'], 'holdout': None, 'weight': LEGACY_WEIGHT, 'target_weight': [LEGACY_MEMBER_WEIGHT_BY_TARGET.get(t, 0.0) for t in TARGETS], 'pixel_group': key, 'config': {'unfreeze_last': 6, 'variant': 'base' if variant == 'base' else 'small', 'pool': 'cls_mean_focal', 'prior': True}} for k, f in enumerate(folds)]\n        if ms:\n            active = sorted(set(LEGACY_MEMBER_WEIGHT_BY_TARGET.values()))\n            log(f'legacy bundle {p.name}: {len(ms)} fold(s) join with target-specific per-member weights {active}')\n        return {key: ms} if ms else {}\n    except Exception as exc:\n        log(f'legacy bundle unusable ({type(exc).__name__}: {exc}); blending skipped')\n        return {}\n\ndef _run_member(path, m, dev, Cte, Mte, idx, starts, jitter):\n    t0 = time.time()\n    with BUILD_LOCK:\n        if 'state' in m:\n            state, fp = (m['state'], None)\n        else:\n            ck = torch.load(Path(path) / m['file'], map_location='cpu', weights_only=False)\n            state, fp = (ck['model'], ck.get('fingerprint'))\n        model = build_model(int(m['config']['unfreeze_last']), variant=m['config']['variant'], pool=m['config'].get('pool', 'cls_mean'), prior=bool(m['config'].get('prior', False))).to(dev)\n        model.load_state_dict(state)\n        if fp is not None:\n            check_fingerprint(model, dev, IMG, fp, tag=f\"{m['id']}: \")\n        else:\n            log(f\"  {m['id']}: no stored fingerprint (legacy bundle) -- accepted at reduced weight\")\n    t_ready = time.time()\n    jitter_seed = SEED + int(hashlib.sha256(str(m['id']).encode()).hexdigest()[:8], 16)\n    public_member = 'state' not in m\n    predicted = predict_member(model, Cte, Mte, idx, dev, IMG, starts=starts, jitter=jitter, jitter_seed=jitter_seed, return_public_frontier=public_member)\n    if public_member:\n        p, public_p, public_soft = predicted\n    else:\n        p, public_p, public_soft = (predicted, None, None)\n    t_done = time.time()\n    del model, state\n    gc.collect()\n    if dev.type == 'cuda':\n        with torch.cuda.device(dev):\n            torch.cuda.empty_cache()\n    passes = len(starts) * (2 if jitter else 1)\n    return (p, public_p, public_soft, (t_ready - t0, (t_done - t_ready) / max(passes, 1)))\n\ndef _combine(per_member):\n    all_ids = sorted({s for m in per_member for s in m['ids']})\n    pos = {s: i for i, s in enumerate(all_ids)}\n    acc = np.zeros((len(all_ids), len(TARGETS)), np.float64)\n    tot = np.zeros(len(TARGETS), np.float64)\n    for m in per_member:\n        target_weight = m.get('target_weight')\n        w = np.asarray(target_weight if target_weight is not None else [float(m.get('weight', 1.0))] * len(TARGETS), dtype=np.float64)\n        if w.shape != (len(TARGETS),) or np.any(w < 0):\n            raise ValueError(f\"invalid target weights for {m.get('id')}: {w}\")\n        r = pd.DataFrame(m['pred']).rank(pct=True).to_numpy()\n        acc[[pos[s] for s in m['ids']]] += r * w[None, :]\n        tot += w\n    if np.any(tot <= 0):\n        raise ValueError(f'at least one target has no ensemble vote: {tot}')\n    return (all_ids, acc / tot[None, :])\n\ndef combine_public_members_by_fold(per_member, pred_key='pred'):\n    # Raw-average the four members within each fold, rank each fold,\n    # then give all five folds equal weight.\n    all_ids = sorted({study for member in per_member for study in member['ids']})\n    position = {study: i for i, study in enumerate(all_ids)}\n    groups = {}\n    for i, member in enumerate(per_member):\n        fold = member.get('fold')\n        key = f'fold_{fold}' if fold is not None else f'member_{i}'\n        groups.setdefault(key, []).append(member)\n    fold_ranks, diagnostics = ([], [])\n    for key, members_in_fold in sorted(groups.items()):\n        matrices = []\n        for member in members_in_fold:\n            values = np.full((len(all_ids), len(TARGETS)), np.nan, np.float64)\n            values[[position[study] for study in member['ids']]] = np.asarray(member[pred_key], np.float64)\n            if np.isnan(values).any():\n                raise WeightsError(f\"{member.get('id')}: incomplete {pred_key} coverage\")\n            matrices.append(values)\n        raw_fold_mean = np.mean(matrices, axis=0)\n        fold_ranks.append(pd.DataFrame(raw_fold_mean).rank(method='average', pct=True).to_numpy(np.float64))\n        diagnostics.append({'ensemble_group': key, 'members': len(members_in_fold)})\n    if len(fold_ranks) != 5:\n        raise WeightsError(f'legacy branch requires five folds, found {len(fold_ranks)}')\n    return all_ids, np.mean(fold_ranks, axis=0), pd.DataFrame(diagnostics)\n\ndef blend_legacy_frontier_and_soft(frontier_rank, soft_rank):\n    output = np.asarray(frontier_rank, np.float64).copy()\n    for j, target in enumerate(TARGETS):\n        alpha = float(LEGACY_FOLD_SOFTPOOL_ALPHA.get(target, 0.0))\n        if alpha:\n            output[:, j] = (1.0 - alpha) * frontier_rank[:, j] + alpha * soft_rank[:, j]\n    return output\n\ndef infer_from_package(path, dev=None):\n    man = json.loads((Path(path) / 'manifest.json').read_text())\n    members = man['members']\n    log(f'weights package: {len(members)} member(s) from {path}; {len(DEVS)} device(s)')\n    test_df = pd.read_csv(ROOT / 'test.csv')\n    test_series = pd.read_csv(ROOT / 'test_series.csv')\n    plane_map = dict(zip(test_series['SeriesInstanceUID'], test_series['Anatomical_Plane']))\n    hte = annotate(walk('test_series'))\n    log(f'test header pass: {len(hte)} series')\n    groups = {}\n    for m in members:\n        groups.setdefault(m['pixel_group'], []).append(m)\n    groups.update(legacy_group_members())\n    per_member, public_frontier_members = ([], [])\n    est = {'fixed': None, 'win': None}\n\n    def bank(m, ids, pred, starts, jitter, public_pred=None, public_soft=None):\n        if float(np.std(pred)) < 1e-09:\n            log(f\"  {m['id']}: degenerate predictions; not banked\")\n            return\n        with STATE_LOCK:\n            per_member.append({'id': m['id'], 'fold': m.get('fold'), 'ids': ids, 'pred': pred, 'weight': m.get('weight', 1.0), 'target_weight': m.get('target_weight'), 'holdout': m.get('holdout')})\n            if public_pred is not None and len(starts) == len(starts_full):\n                if float(np.std(public_pred)) < 1e-09:\n                    raise WeightsError(f\"{m['id']}: degenerate public-frontier prediction\")\n                public_frontier_members.append({'id': m['id'], 'fold': m.get('fold'), 'ids': ids, 'pred': public_pred, 'soft_pred': public_soft})\n            elif public_pred is not None:\n                log(f\"  {m['id']}: public-frontier vote omitted because only {len(starts)} / {len(starts_full)} windows completed\")\n            all_ids, acc = _combine(per_member)\n            write_submission(acc, all_ids, test_df, 'submission.csv')\n            log(f\"  banked {m['id']} fold {m.get('fold', '?')} ({len(starts)} window(s){(', jitter' if jitter else '')}); submission.csv = weighted rank mean of {len(per_member)} member(s)\")\n    for gi, (key, gm) in enumerate(groups.items(), 1):\n        cfg = json.loads(key)\n        adopt_config_globals(cfg)\n        log(f\"decode group {gi}/{len(groups)}: {cfg['img']}px x {cfg['slices']} slices, crop {cfg['crop_mm']} mm -> {len(gm)} member(s)\")\n        st_te, Cte, Mte = build_cache(pick_slots(hte, plane_map), plane_map, lat_of(hte, 'test '), f'test g{gi}')\n        idx = np.arange(len(st_te))\n        starts_full = window_starts(Cte.shape[2], GROUP)\n        pending = sorted(gm, key=lambda m: -(m.get('holdout') or 0))\n        left_after = sum((len(g) for j, (_, g) in enumerate(groups.items(), 1) if j > gi))\n\n        def pop_next():\n            with STATE_LOCK:\n                if not pending:\n                    return (None, None, False)\n                left = TIME_BUDGET - (time.time() - T0)\n                remaining = len(pending) + left_after\n                slots_left = -(-remaining // len(DEVS))\n                starts, jit = (starts_full, False)\n                if est['fixed'] is not None and est['win'] is not None:\n                    afford = max(left * 0.9, 0.0)\n                    room = afford / max(slots_left, 1)\n                    if est['fixed'] + est['win'] > room:\n                        log(f'  {left / 60:.0f} min left: surrendering {len(pending)} member(s); not one more fits')\n                        pending.clear()\n                        return (None, None, False)\n                    jit = est['fixed'] + 2 * len(starts_full) * est['win'] <= room * 0.6\n                    per_win = est['win'] * (2 if jit else 1)\n                    n_win = int((room - est['fixed']) / per_win) if per_win > 0 else len(starts_full)\n                    n_win = max(1, min(len(starts_full), n_win))\n                    if n_win < len(starts_full):\n                        mid = (len(starts_full) - n_win) // 2\n                        starts = starts_full[mid:mid + n_win]\n                return (pending.pop(0), starts, jit)\n\n        def worker(dev):\n            others = [d for d in DEVS if d is not dev]\n            while True:\n                m, starts, jit = pop_next()\n                if m is None:\n                    return\n                for attempt, d in enumerate([dev] + others[:1]):\n                    try:\n                        p, public_p, public_soft, (fs, ws) = _run_member(path, m, d, Cte, Mte, idx, starts, jit)\n                        with STATE_LOCK:\n                            est['fixed'], est['win'] = (fs, ws)\n                        bank(m, st_te, p, starts, jit, public_p, public_soft)\n                        break\n                    except Exception as exc:\n                        log(f\"  MEMBER {m['id']} failed on {d} ({type(exc).__name__}: {exc}); \" + ('retrying on peer device' if attempt == 0 and others else 'dropped -- costs one vote, not the run'))\n                        if d.type == 'cuda':\n                            with torch.cuda.device(d):\n                                torch.cuda.empty_cache()\n        threads = [threading.Thread(target=worker, args=(d,)) for d in DEVS]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n        del Cte, Mte\n        gc.collect()\n    if not per_member:\n        raise WeightsError('no member produced predictions; submission stays at 0.5')\n    all_ids, acc = _combine(per_member)\n    sub = write_submission(acc, all_ids, test_df, 'submission.csv')\n    log(f'final submission.csv = weighted rank mean of {len(per_member)} member(s); {sub.shape}; nulls {int(sub[TARGETS].isna().sum().sum())}')\n    if len(public_frontier_members) == len(members):\n        frontier_ids, frontier_acc = _combine(public_frontier_members)\n        frontier_sub = write_submission(frontier_acc, frontier_ids, test_df, 'submission_public_0899.csv')\n        log(f'submission_public_0899.csv = exact no-jitter public-frontier rank mean of {len(public_frontier_members)} member(s); {frontier_sub.shape}; nulls {int(frontier_sub[TARGETS].isna().sum().sum())}')\n        fold_ids, fold_frontier, fold_diagnostics = combine_public_members_by_fold(public_frontier_members, 'pred')\n        soft_ids, fold_soft, _ = combine_public_members_by_fold(public_frontier_members, 'soft_pred')\n        if fold_ids != soft_ids:\n            raise WeightsError('legacy hard/soft study order mismatch')\n        legacy_prediction = blend_legacy_frontier_and_soft(fold_frontier, fold_soft)\n        legacy_sub = write_submission(legacy_prediction, fold_ids, test_df, 'submission_legacy_fold_blend.csv')\n        fold_diagnostics.to_csv('legacy_fold_diagnostics.csv', index=False)\n        log(f'legacy DINO aggregation written from five folds; {legacy_sub.shape}')\n    else:\n        log(f'public-frontier fallback not emitted: {len(public_frontier_members)} / {len(members)} required public members completed')\n    return sub\n\ndef adopt_config_globals(cfg):\n    global IMG, CACHE_IMG, GROUP, CACHE_SLICES, N_GROUP, CROP_MM, SLICE_BAND, RULES\n    CACHE_IMG = IMG = int(cfg['img'])\n    GROUP = int(cfg['group'])\n    CACHE_SLICES = int(cfg['slices'])\n    N_GROUP = max(CACHE_SLICES // GROUP, 1)\n    CROP_MM = float(cfg['crop_mm'])\n    SLICE_BAND = tuple((float(x) for x in cfg['band']))\n    rules = cfg.get('rules') or RULES_NATIVE\n    unknown = {k: v for k, v in rules.items() if k not in RULES_NATIVE or v not in (RULES_NATIVE[k], RULES_LEGACY[k])}\n    if unknown:\n        raise WeightsError(f'the members record pixel rules this pipeline cannot reproduce: {unknown}')\n    RULES = {**RULES_NATIVE, **rules}\n    if [s[0] for s in SLOTS] != list(cfg['slots']):\n        raise WeightsError(f\"the members were fitted on slots {cfg['slots']} and this pipeline defines {[s[0] for s in SLOTS]}; a weight would be read against the wrong slot\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:40.026821Z","iopub.execute_input":"2026-09-02T10:56:40.027345Z","iopub.status.idle":"2026-09-02T10:56:40.088216Z","shell.execute_reply.started":"2026-09-02T10:56:40.027316Z","shell.execute_reply":"2026-09-02T10:56:40.087572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def take_group(cache_rows, g):\n    return cache_rows[:, :, g * GROUP:(g + 1) * GROUP]\n\ndef augment(imgs, generator=None):\n    lead = imgs.shape[:-3]\n    x = imgs.reshape(-1, *imgs.shape[-3:]).float()\n    n, dev = (x.shape[0], x.device)\n    rot = (torch.rand(n, device=dev, generator=generator) - 0.5) * 2 * (AUG_ROT_DEG * np.pi / 180)\n    sc = 1.0 + torch.rand(n, device=dev, generator=generator) * AUG_SCALE\n    tx = (torch.rand(n, device=dev, generator=generator) - 0.5) * 2 * AUG_SHIFT\n    ty = (torch.rand(n, device=dev, generator=generator) - 0.5) * 2 * AUG_SHIFT\n    cos, sin = (torch.cos(rot) / sc, torch.sin(rot) / sc)\n    theta = torch.zeros(n, 2, 3, device=dev, dtype=torch.float32)\n    theta[:, 0, 0], theta[:, 0, 1], theta[:, 0, 2] = (cos, -sin, tx)\n    theta[:, 1, 0], theta[:, 1, 1], theta[:, 1, 2] = (sin, cos, ty)\n    grid = F.affine_grid(theta, x.shape, align_corners=False)\n    x = F.grid_sample(x, grid, mode='bilinear', padding_mode='border', align_corners=False)\n    scale = 1.0 + (torch.rand(n, 1, 1, 1, device=dev, generator=generator) - 0.5) * 2 * AUG_INTENSITY\n    x = (x * scale).clamp(0, 255)\n    return x.reshape(*lead, *x.shape[-3:]).to(imgs.dtype)\n\n@torch.no_grad()\ndef predict(model, cache, mask, idx, dev, img_size=None):\n    model.eval()\n    out = []\n    for b in range(0, len(idx), EVAL_BATCH):\n        sel = idx[b:b + EVAL_BATCH]\n        m = torch.from_numpy(mask[sel]).to(dev)\n        acc = None\n        for g in range(N_GROUP):\n            rows = torch.from_numpy(np.ascontiguousarray(cache[sel, :, g * GROUP:(g + 1) * GROUP])).to(dev)\n            with torch.autocast('cuda', enabled=dev.type == 'cuda'):\n                z = model(rows, m, img_size).float()\n            acc = z if acc is None else acc + z\n        out.append(torch.sigmoid(acc / N_GROUP).cpu().numpy())\n    return np.concatenate(out) if out else np.zeros((0, len(TARGETS)), np.float32)\n\ndef macro_auc(y, p):\n    from sklearn.metrics import roc_auc_score\n    return float(np.nanmean([roc_auc_score(y[:, j], p[:, j]) if len(set(y[:, j])) > 1 else np.nan for j in range(y.shape[1])]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:49.505855Z","iopub.execute_input":"2026-09-02T10:56:49.506581Z","iopub.status.idle":"2026-09-02T10:56:49.518326Z","shell.execute_reply.started":"2026-09-02T10:56:49.506548Z","shell.execute_reply":"2026-09-02T10:56:49.517486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math\nimport cv2\n\n\n'Runtime helpers embedded into the V26 Kaggle notebook.\\n\\nThe exact RTAHMIL class from the public report-teacher notebook is prepended by the\\ncandidate builder. This file contains only hidden-test feature extraction, checkpoint\\ninference, and the fail-safe Synovitis blend.\\n'\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:54.870889Z","iopub.execute_input":"2026-09-02T10:56:54.871297Z","iopub.status.idle":"2026-09-02T10:56:55.104855Z","shell.execute_reply.started":"2026-09-02T10:56:54.871265Z","shell.execute_reply":"2026-09-02T10:56:55.104113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import base64\nimport gc\nimport hashlib\nimport io\nimport json\nimport math\nimport os\nimport random\nimport time\nimport zlib\nfrom concurrent.futures import ThreadPoolExecutor\nfrom functools import lru_cache\nfrom pathlib import Path\nimport cv2\nimport joblib\nimport numpy as np\nimport pandas as pd\nimport pydicom\nfrom scipy.stats import rankdata\nfrom sklearn.ensemble import ExtraTreesClassifier, HistGradientBoostingClassifier\nfrom sklearn.decomposition import PCA\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom transformers import AutoModel\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:56:59.980779Z","iopub.execute_input":"2026-09-02T10:56:59.981204Z","iopub.status.idle":"2026-09-02T10:57:17.783866Z","shell.execute_reply.started":"2026-09-02T10:56:59.981175Z","shell.execute_reply":"2026-09-02T10:57:17.782934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def write_submission(pred, studies, test_df, path):\n    sub = pd.DataFrame(pd.DataFrame(pred).rank(pct=True).values, columns=TARGETS)\n    sub.insert(0, 'StudyInstanceUID', studies)\n    sub = test_df[['StudyInstanceUID']].merge(sub, on='StudyInstanceUID', how='left')\n    sub[TARGETS] = sub[TARGETS].fillna(0.5)\n    sub.to_csv(path, index=False)\n    return sub\n\ndef write_benchmark_submission():\n    t = pd.read_csv(ROOT / 'test.csv')\n    for c in TARGETS:\n        t[c] = 0.5\n    t.to_csv('submission.csv', index=False)\n\ndef _v37_validate_submission(path, test_df, tag):\n    path = Path(path)\n    frame = pd.read_csv(path)\n    expected = ['StudyInstanceUID'] + TARGETS\n    if list(frame.columns) != expected:\n        raise ValueError(f'{tag}: columns differ from the competition contract')\n    if len(frame) != len(test_df) or not frame['StudyInstanceUID'].is_unique:\n        raise ValueError(f'{tag}: row count or StudyInstanceUID uniqueness failed')\n    if set(frame['StudyInstanceUID'].astype(str)) != set(test_df['StudyInstanceUID'].astype(str)):\n        raise ValueError(f'{tag}: StudyInstanceUID set differs from test.csv')\n    values = frame[TARGETS].to_numpy(np.float64)\n    if not np.isfinite(values).all():\n        raise ValueError(f'{tag}: non-finite prediction')\n    return test_df[['StudyInstanceUID']].merge(frame, on='StudyInstanceUID', how='left')\n\n\ndef main():\n    write_benchmark_submission()\n    pkg = find_weights()\n    if pkg is not None:\n        dev = DEVS[0]\n        infer_from_package(pkg, dev)\n        try:\n            test_df = pd.read_csv(ROOT / 'test.csv')\n            native_path = Path('submission.csv')\n            public_path = Path('submission_public_0899.csv')\n            native = _v37_validate_submission(native_path, test_df, 'native 24-member')\n            public = _v37_validate_submission(public_path, test_df, 'public DINO frontier')\n            native.to_csv('submission_native_v38.csv', index=False)\n            public.to_csv(native_path, index=False)\n            promoted = _v37_validate_submission(native_path, test_df, 'V40 primary')\n            if not promoted.equals(public):\n                raise AssertionError('V40 serialization differs from validated public frontier')\n            log('V40 primary = exact no-jitter public-frontier target pooling; native 24-member output retained')\n        except Exception as public_frontier_error:\n            log(f'public-frontier promotion skipped safely: {public_frontier_error}')\n            traceback.print_exc()\n        log('done')\n        return\n    read_labels(pd.read_csv(ROOT / 'train.csv', usecols=['StudyInstanceUID', 'Report']))\n    test_df = pd.read_csv(ROOT / 'test.csv')\n    test_series = pd.read_csv(ROOT / 'test_series.csv')\n    train_df = pd.read_csv(ROOT / 'train.csv')\n    train_series = pd.read_csv(ROOT / 'train_series.csv')\n    log(f'train {train_df.shape} test {test_df.shape}')\n    both = pd.concat([train_series, test_series])\n    plane_map = dict(zip(both['SeriesInstanceUID'], both['Anatomical_Plane']))\n    log('header pass: test')\n    hte = annotate(walk('test_series'))\n    log(f'  {len(hte)} test series')\n    log('header pass: train')\n    htr = annotate(walk('train_series'))\n    log(f'  {len(htr)} train series')\n    slots_te, slots_tr = (pick_slots(hte, plane_map), pick_slots(htr, plane_map))\n    cov = pd.Series([len(v) for v in slots_tr.values()]).describe()\n    log(f\"train slots per study: mean {cov['mean']:.2f} min {cov['min']:.0f} max {cov['max']:.0f}\")\n    st_tr, Ctr, Mtr = build_cache(slots_tr, plane_map, lat_of(htr, 'train '), 'train')\n    st_te, Cte, Mte = build_cache(slots_te, plane_map, lat_of(hte, 'test '), 'test')\n    t_lab = time.time()\n    lab = read_labels(train_df)\n    log(f'derived labels for {len(lab)} studies in {time.time() - t_lab:.1f}s')\n    gold = train_df.set_index('StudyInstanceUID')[TARGETS]\n    gold = gold[gold.notna().all(axis=1)]\n    Y = np.zeros((len(st_tr), len(TARGETS)), np.float32)\n    W = np.zeros_like(Y)\n    for i, st in enumerate(st_tr):\n        if st in gold.index:\n            Y[i], W[i] = (gold.loc[st].values, 3.0)\n        elif st in lab.index:\n            r = lab.loc[st]\n            Y[i] = r[TARGETS].values\n            W[i] = 0.25 + 0.75 * r[[t + '__conf' for t in TARGETS]].values\n    keep = np.where(W.sum(1) > 0)[0]\n    log(f'supervised {len(keep)} of {len(st_tr)} studies (annotated {len(gold)})')\n    import hashlib\n    rep = train_df.set_index('StudyInstanceUID')['Report'].fillna('')\n    grp = np.array([int(hashlib.md5(rep.get(s, s).encode()).hexdigest()[:8], 16) % 5 for s in st_tr])\n    va = np.array([i for i in keep if grp[i] == 0])\n    tr = np.array([i for i in keep if grp[i] != 0])\n    if len(va) == 0 or len(tr) < BATCH_STUDIES:\n        cut = max(1, len(keep) // 5)\n        va, tr = (keep[:cut], keep[cut:])\n    log(f'train {len(tr)} / holdout {len(va)} studies')\n    gpos = {s: i for i, s in enumerate(st_tr)}\n    va_set = set(va.tolist())\n    gi = np.array([gpos[s] for s in gold.index if s in gpos and gpos[s] in va_set])\n    gold_y = gold.loc[[st_tr[i] for i in gi]].values.astype(int) if len(gi) else None\n    yv = (Y[va] > 0.5).astype(int)\n    log(f'annotation check: {len(gi)} of {len(gold)} annotated studies are in the holdout')\n    dev = DEVS[0]\n    results, test_preds = ({}, {})\n    for cfg in RUNS:\n        pitch = CROP_MM / cfg['img']\n        log(f\"=== {cfg['name']}: {cfg['img']} px, {pitch:.3f} mm/pixel, {pitch * 14:.2f} mm per patch token ===\")\n        torch.manual_seed(SEED)\n        model = build_model(UNFREEZE_LAST).to(dev)\n        opt = torch.optim.AdamW([{'params': [p for p in model.backbone.parameters() if p.requires_grad], 'lr': LR_BACKBONE}, {'params': model.head.parameters(), 'lr': LR_HEAD}], weight_decay=WEIGHT_DECAY)\n        steps = max(EPOCHS * (len(tr) // BATCH_STUDIES), 1)\n        sched = torch.optim.lr_scheduler.OneCycleLR(opt, max_lr=[LR_BACKBONE, LR_HEAD], total_steps=steps, pct_start=0.15)\n        scaler = torch.amp.GradScaler('cuda', enabled=dev.type == 'cuda')\n        best, best_state, best_annot = (-1.0, None, float('nan'))\n        for ep in range(EPOCHS):\n            model.train()\n            perm = np.random.permutation(tr)\n            tot, nstep = (0.0, 0)\n            for b in range(0, len(perm) - BATCH_STUDIES + 1, BATCH_STUDIES):\n                sel = perm[b:b + BATCH_STUDIES]\n                rows = torch.from_numpy(Ctr[sel]).to(dev)\n                g = int(torch.randint(N_GROUP, (1,)).item())\n                imgs = augment(take_group(rows, g))\n                m = torch.from_numpy(Mtr[sel]).to(dev)\n                y = torch.from_numpy(Y[sel]).to(dev)\n                w = torch.from_numpy(W[sel]).to(dev)\n                with torch.autocast('cuda', enabled=dev.type == 'cuda'):\n                    loss = (F.binary_cross_entropy_with_logits(model(imgs, m, cfg['img']), y, reduction='none') * w).mean()\n                opt.zero_grad(set_to_none=True)\n                scaler.scale(loss).backward()\n                scaler.step(opt)\n                scaler.update()\n                sched.step()\n                tot += loss.item()\n                nstep += 1\n            pv = predict(model, Ctr, Mtr, va, dev, cfg['img'])\n            d = macro_auc(yv, pv)\n            g_auc = float('nan')\n            if gold_y is not None and len(gi):\n                g_auc = macro_auc(gold_y, predict(model, Ctr, Mtr, gi, dev, cfg['img']))\n            log(f'  epoch {ep + 1}/{EPOCHS}  loss {tot / max(nstep, 1):.4f}  holdout {d:.4f}  annot(n={len(gi)}) {g_auc:.4f}')\n            if d > best:\n                best, best_annot = (d, g_auc)\n                best_state = {k: v.detach().cpu().clone() for k, v in model.state_dict().items()}\n            if time.time() - T0 > TIME_BUDGET:\n                log('  time budget reached')\n                break\n        if best_state is not None:\n            model.load_state_dict(best_state)\n        results[cfg['name']] = (best, best_annot)\n        test_preds[cfg['name']] = predict(model, Cte, Mte, np.arange(len(st_te)), dev, cfg['img'])\n        log(f\"  {cfg['name']}: best holdout {best:.4f} (annot {best_annot:.4f})\")\n        del model, opt, sched, scaler, best_state\n        gc.collect()\n        if dev.type == 'cuda':\n            torch.cuda.empty_cache()\n    log('---- summary ----')\n    for n, (d, g_auc) in results.items():\n        log(f'  {n:12s} holdout {d:.4f}   annot {g_auc:.4f}')\n    pick = max(results, key=lambda k: results[k][0])\n    log(f'best on the holdout: {pick} ({results[pick][0]:.4f})')\n    for name, pred in test_preds.items():\n        sub = write_submission(pred, st_te, test_df, f'submission_{name}.csv')\n        log(f'  submission_{name}.csv {sub.shape}; nulls {int(sub[TARGETS].isna().sum().sum())}')\n    ens = np.mean([pd.DataFrame(p).rank(pct=True).values for p in test_preds.values()], axis=0)\n    write_submission(ens, st_te, test_df, 'submission_rankmean.csv')\n    log(f'  submission_rankmean.csv (rank mean of {len(test_preds)})')\n    sub = write_submission(test_preds[pick], st_te, test_df, 'submission.csv')\n    log(f'submission.csv = {pick}; {sub.shape}; nulls {int(sub[TARGETS].isna().sum().sum())}')\n    print(sub.head().to_string())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:57:26.782007Z","iopub.execute_input":"2026-09-02T10:57:26.782817Z","iopub.status.idle":"2026-09-02T10:57:26.813337Z","shell.execute_reply.started":"2026-09-02T10:57:26.782783Z","shell.execute_reply":"2026-09-02T10:57:26.812448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"try:\n    main()\nexcept LabelSourceError:\n    traceback.print_exc()\n    raise\nexcept Exception:\n    traceback.print_exc()\n    t = pd.read_csv(find_root() / 'test.csv')\n    for c in TARGETS:\n        t[c] = 0.5\n    t.to_csv('submission.csv', index=False)\n    print('wrote fallback submission.csv')\nlog('done')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:57:34.406825Z","iopub.execute_input":"2026-09-02T10:57:34.40767Z","iopub.status.idle":"2026-09-02T10:58:52.0992Z","shell.execute_reply.started":"2026-09-02T10:57:34.407637Z","shell.execute_reply":"2026-09-02T10:58:52.098558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# COATNET_TRANSFORMER_BLEND_V1\n#!/usr/bin/env python3\n\"\"\"Knee MRI: twelve findings from a single model\n\nThis notebook takes a knee MRI study and scores twelve findings at once: ACL tear, MCL tear,\nmedial and lateral meniscus tears, osteoarthritis in the medial, lateral and patellofemoral\ncompartments, joint effusion, synovitis, a Baker's cyst, bone contusion and fracture. It scores\n0.924 on the public leaderboard using one model, with no ensembling and no test-time augmentation.\n\nThis is the inference half of the work. The model was trained separately and its weights are\nattached as a dataset, so this notebook only loads them and predicts:\nhttps://www.kaggle.com/datasets/dreaddevelopment/raptor-knee-widedense\n\nWhere the training labels came from\n\nWorth saying up front, because it shapes everything else. The competition gives you 4,407 studies\nbut structured labels for only 58 of them. Every other study arrives with a free-text radiology\nreport and nothing more, so there is very little to train against out of the box.\n\nThe labels behind these weights were made by reading those reports with a language model and\nturning each into twelve probabilities rather than twelve yes or no answers. A report that says a\ntear is suspected becomes a number near 0.8, not a 1, which is a fairer target than forcing every\nhedged sentence into a hard label. That yields 4,349 studies to train on. The 58 studies that came\nwith real labels were never trained on and are used to check the result honestly; the model reaches\n0.9167 macro-AUC on them.\n\nBuilding a fixed input from studies that are all shaped differently\n\nThe hard part of this competition is not the network, it is that no two studies look alike. A\nstudy holds several DICOM series shot in different planes, the number of series varies, and the\nnumber of slices in a series varies more. Anything that expects a fixed-size input has to be given\none.\n\nThe approach here is to fill five fixed slots per study, always in the same order, for a stack of\n64 images:\n\n  18 slices from a sagittal series, preferring a fluid-sensitive one\n  14 slices from a second sagittal series, preferring one that is not fluid-sensitive\n  12 slices from a coronal series, preferring a fluid-sensitive one\n   8 slices from a second coronal series\n  12 slices from an axial series\n\nPreferring a fluid-sensitive series for some slots and not for others is deliberate. Fluid-\nsensitive sequences show swelling, effusion and acute injury clearly, while the other sequences\nshow anatomy and cartilage better, and the twelve findings are split across both. If a study has\nno series for a slot, the slot is left as zeros and the model is told to skip it rather than being\nfed something misleading.\n\nWithin a series, slices are taken evenly across 6 to 94 percent of the stack rather than from the\nmiddle. The outer slices are where the collateral ligaments and the lateral meniscus sit, and\ncutting them was measurably costing accuracy on exactly those findings.\n\nEvery slice is cropped to a 140 mm box around the centre of the image using the pixel spacing from\nthe DICOM header, then resized to 336 pixels. Cropping by millimetres rather than by pixel count\nmatters: it means a knee occupies the same fraction of the frame whether the scan was acquired at\n0.3 or 0.5 mm per pixel, so the model is not asked to learn scale differences that carry no medical\ninformation.\n\nHow the model reads the stack\n\nThree neighbouring slices are stacked into the three channels of one image. The network then sees\na little of what lies above and below the slice in the middle, which is most of the benefit of a 3D\nmodel at the cost of a 2D one. Each of these three-slice windows is passed through a CoAtNet\nbackbone at 384 pixels.\n\nThe windows are combined with an attention layer that has separate weights for each of the twelve\nfindings. This is the part that matters most. A cruciate tear may be visible on two sagittal slices\nwhile osteoarthritis is spread across many coronal ones, and a single pooled score forces those two\nto share one notion of which slices are important. Giving each finding its own attention weights\nlets each one draw on the slices that actually show it.\n\nRunning it\n\nScoring uses 42 windows per study. Inference runs in half precision and automatically retries a\nstudy in full precision if it fails, so no study is ever dropped from the submission. The notebook\nneeds no internet: the backbone is loaded from the attached weights rather than downloaded.\n\"\"\"\nimport os, sys, glob, time, json, gc\nos.environ.setdefault(\"HF_HUB_OFFLINE\", \"1\")\nos.environ.setdefault(\"TRANSFORMERS_OFFLINE\", \"1\")\nos.environ.setdefault(\"HF_HUB_DISABLE_TELEMETRY\", \"1\")\nimport numpy as np\nimport torch, torch.nn as nn, torch.nn.functional as F\nimport timm\n# T4 (Turing) cuDNN v9 has fp16/fp32 conv engines but NOT bf16 for these shapes\n# (\"GET was unable to find an engine...\"); benchmark lets it pick a valid algo for\n# the fixed (1,24,3,res,res) input.\ntorch.backends.cudnn.benchmark = True\ntorch.backends.cuda.matmul.allow_tf32 = True\n\n# ---- fixed config (must match training exactly) -----------------------------\nIMG = 336\nCROP_MM = 140.0\n# 64 slices per study instead of 44, same proportions. Must match the corpus the weights\n# were trained on (knee_corpus_v4.py).\nSLOTS = [(\"Sagittal\", 1, 18), (\"Sagittal\", 0, 14), (\"Coronal\", 1, 12),\n         (\"Coronal\", 0, 8), (\"Axial\", -1, 12)]\nMAXS = sum(s[2] for s in SLOTS)                     # 64\nK_EVAL = 62   # every window position the volume holds, not an evenly spaced subset\nNORM = \"imagenet\"\nLAB = [\"ACL\", \"MCL\", \"Medial Meniscus\", \"Lateral Meniscus\", \"Medial OA\", \"Lateral OA\",\n       \"PF OA\", \"Effusion\", \"Synovitis\", \"Baker's\", \"Contusion\", \"Fracture\"]\n_MEAN = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1)\n_STD = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1)\n\n# Three arms: (weights filename, fallback arch, fallback res). ck carries arch+res too.\n# Selected 2026-08-19 by greedy forward selection AND exhaustive subset search over a 7-arm\n# panel on the 45-study gold set (phase2/blend_panel.py); both agree on this exact set.\n# Singles: coatnet384 0.9025 | swinbase384 0.8825 | effv2l480 0.8716.\n# Blend {coatnet+swin+effv2l} = 0.9068 (2-arm {coatnet+swin} = 0.9059, coatnet alone 0.9025).\n# Dropped as redundant: cnn336 (0.8833, the former champion), cnbase384 (0.8754),\n# cnlarge384 (0.8752), maxvit384 (0.8438).\n#\n# SINGLE ARM: coatnet_rmlp_2_rw_384 retrained on the EXPANDED 4,349-study corpus.\n#\n# Why one arm and not the 3-arm blend: on the live leaderboard CoAtNet alone scored 0.914 while\n# every blend scored 0.914-0.915, so ensembling is worth ~+0.001 there -- the ~+0.010 it showed\n# on the old 45-study gold set was gold-set noise. One arm is also 1/3 the kernel runtime.\n#\n# Corpus expansion: the corpus previously held 3,200 of the 4,349 labelled studies and only 45\n# of the 58 gold studies. Rebuilt to 4,407 studies (+37.8% training data, 58-study gate).\n#\n# Measured on the 58-study gate (the incumbent re-scored on the SAME gate for a fair compare):\n#   incumbent CoAtNet (3,155-study corpus) 0.8923\n#   this model       (4,349-study corpus) 0.9054   (+0.0131, better in 92.7% of 2000 bootstraps)\n# Biggest gains land on the findings that were capping us: Lateral Meniscus +0.071,\n# Fracture +0.057, Lateral OA +0.048, Medial Meniscus +0.035, ACL +0.028.\nARMS = [\n    # v5 maxspan: CoAtNet v5 full SWA (0.935 LB when blended 50/50 with baseline)\n    {\"file\": \"raptor_ft_coatnet_v5_full_swa.pt\", \"arch\": \"coatnet_rmlp_2_rw_384.sw_in12k_ft_in1k\", \"res\": 384, \"w\": 1.0},\n]\n\n\n# ============================================================================\n# Model -- verbatim from finetune_raptor.py\n# ============================================================================\ndef build_backbone(arch, pretrained=False):\n    # maxvit/maxxvit/coatnet are conv-attention hybrids: NO CLS token, NO interpolatable\n    # pos-embed -> avg pool. The \"vit\" substring in \"coatnet\"/\"maxvit\" must NOT route them\n    # down the ViT path (mirrors finetune_raptor.py exactly).\n    hybrid = arch.startswith((\"maxvit\", \"maxxvit\", \"coatnet\", \"coat_\", \"convnext\"))\n    is_vit = (not hybrid) and any(k in arch for k in (\"vit\", \"deit\", \"dinov2\", \"eva\", \"beit\"))\n    kw = dict(pretrained=pretrained, num_classes=0, in_chans=3)\n    if is_vit:\n        kw.update(global_pool=\"token\", dynamic_img_size=True)\n    else:\n        kw.update(global_pool=\"avg\")\n    return timm.create_model(arch, **kw)\n\n\nclass RaptorClassifier(nn.Module):\n    def __init__(self, backbone, F_dim=768, n=12, drop=0.2):\n        super().__init__()\n        self.backbone = backbone\n        self.norm = nn.LayerNorm(F_dim)\n        self.att = nn.Sequential(nn.Linear(F_dim, 256), nn.Tanh(), nn.Dropout(drop),\n                                 nn.Linear(256, n))\n        self.clsW = nn.Parameter(torch.zeros(n, F_dim))\n        self.clsb = nn.Parameter(torch.zeros(n))\n        nn.init.trunc_normal_(self.clsW, std=0.02)\n        self.n = n\n\n    def encode(self, x):\n        B, K = x.shape[:2]\n        f = self.backbone(x.flatten(0, 1))\n        return f.view(B, K, -1)\n\n    def head(self, feats):\n        h = self.norm(feats)\n        a = self.att(h)\n        a = torch.softmax(a, dim=1)\n        pooled = torch.einsum(\"bkn,bkf->bnf\", a, h)\n        logits = (pooled * self.clsW).sum(-1) + self.clsb\n        return logits\n\n    def forward(self, x):\n        return self.head(self.encode(x))\n\n\ndef load_model(pt_path, arch_default, res_default, device, ngpu=1):\n    ck = torch.load(pt_path, map_location=\"cpu\", weights_only=False)\n    arch = ck.get(\"arch\", arch_default)\n    ck_res = int(ck.get(\"res\", res_default))\n    bb = build_backbone(arch, pretrained=False)\n    model = RaptorClassifier(bb, F_dim=bb.num_features)\n    model.load_state_dict(ck[\"model\"], strict=True)\n    model.eval().to(device)\n    # NOTE: DataParallel removed on purpose. On the full hidden test it drove a system-RAM OOM\n    # (per-forward module replication over many studies); a single T4 handles K_EVAL=24 windows\n    # fine. Arms are also run SEQUENTIALLY (see main) so peak RAM == one model, not two.\n    del ck\n    gc.collect()\n    return model, ck_res\n\n\n# ============================================================================\n# Eval windowing -- verbatim from finetune_raptor.py StudyWindows (train=False)\n# ============================================================================\ndef _eval_centers(mask, D, k):\n    valid = np.where(mask > 0)[0]\n    if len(valid) < 3:\n        valid = np.arange(min(3, D))\n    lo, hi = int(valid.min()), int(valid.max())\n    cs = [c for c in range(lo + 1, hi) if c - 1 >= lo and c + 1 <= hi]\n    if not cs:\n        cs = [max(1, min((lo + hi) // 2, D - 2))]\n    idx = np.linspace(0, len(cs) - 1, k).round().astype(int)\n    return [cs[i] for i in idx]\n\n\ndef eval_windows(vol, mask, k, res, norm=NORM):\n    D = vol.shape[0]\n    cs = _eval_centers(mask, D, k)\n    wins = np.empty((len(cs), 3, res, res), np.float32)\n    for j, c in enumerate(cs):\n        c = max(1, min(c, D - 2))\n        tri = np.stack([vol[c - 1], vol[c], vol[c + 1]], 0).astype(np.float32) / 255.0\n        t = torch.from_numpy(tri)\n        if t.shape[-1] != res:\n            t = F.interpolate(t[None], size=(res, res), mode=\"bilinear\",\n                              align_corners=False)[0]\n        wins[j] = t.numpy()\n    x = torch.from_numpy(wins)\n    if norm == \"imagenet\":\n        x = (x - _MEAN) / _STD\n    return x\n\n\n@torch.no_grad()\ndef infer_probs(model, xwins, device):\n    x = xwins.unsqueeze(0).to(device)\n    use_cuda = device != \"cpu\" and str(device).startswith(\"cuda\")\n    if use_cuda:\n        # fp16 conv on T4 is fully cuDNN-supported (bf16 is NOT -> \"no engine\").\n        try:\n            with torch.autocast(\"cuda\", dtype=torch.float16):\n                o = torch.sigmoid(model(x).float())\n            return o[0].cpu().numpy()\n        except RuntimeError:\n            # fp32 always has a Turing conv engine; slower but never drops a study.\n            torch.cuda.empty_cache()\n            o = torch.sigmoid(model(x).float())\n            return o[0].cpu().numpy()\n    o = torch.sigmoid(model(x).float())\n    return o[0].cpu().numpy()\n\n\ndef rankpct(x):                                   # per-column percentile rank in [0,1]\n    order = x.argsort(0).argsort(0).astype(np.float64)\n    return order / max(1, (x.shape[0] - 1))\n\n\n# ============================================================================\n# Preprocessing -- verbatim from kprep2/dino_preprocess.py, retargeted to TEST\n# ============================================================================\ndef _make_reader():\n    import pydicom, cv2\n    from pydicom.pixel_data_handlers.util import apply_modality_lut\n\n    def order_and_meta(sdir):\n        fs = glob.glob(sdir + \"/*.dcm\"); recs = []; ps_list = []\n        for f in fs:\n            try:\n                h = pydicom.dcmread(f, stop_before_pixels=True)\n                iop = getattr(h, 'ImageOrientationPatient', None)\n                ipp = getattr(h, 'ImagePositionPatient', None)\n                if iop is not None and ipp is not None and len(iop) == 6:\n                    r = np.array(iop[:3], float); c = np.array(iop[3:], float)\n                    n = np.cross(r, c); pos = float(np.dot(np.array(ipp, float), n))\n                else:\n                    pos = float(getattr(h, 'InstanceNumber', 0) or 0)\n                ps = getattr(h, 'PixelSpacing', None); ps = float(ps[0]) if ps is not None else 0.5\n                ps_list.append(ps); recs.append((pos, f, ps))\n            except Exception:\n                recs.append((0.0, f, 0.5))\n        recs.sort(key=lambda x: x[0])\n        med_ps = float(np.median(ps_list)) if ps_list else 0.5\n        return [(f, ps) for _, f, ps in recs], med_ps\n\n    def read_px(f):\n        d = pydicom.dcmread(f)\n        a = apply_modality_lut(d.pixel_array, d).astype(np.float32)\n        if str(getattr(d, 'PhotometricInterpretation', '')) == 'MONOCHROME1':\n            a = a.max() - a\n        return a\n\n    def mm_crop_resize(a, ps):\n        h, w = a.shape; cpx = int(round(CROP_MM / max(ps, 1e-3)))\n        cpx = min(cpx, min(h, w)); y0 = (h - cpx) // 2; x0 = (w - cpx) // 2\n        a = a[y0:y0 + cpx, x0:x0 + cpx]\n        return cv2.resize(a, (IMG, IMG), interpolation=cv2.INTER_AREA)\n\n    return order_and_meta, read_px, mm_crop_resize\n\n\ndef _pick_series_for_slot(rows, plane, fluid, used):\n    cands = [r for r in rows if r['Anatomical_Plane'] == plane and r['SeriesInstanceUID'] not in used]\n    if fluid in (0, 1):\n        pref = [r for r in cands if int(r.get('Fluid_Sensitive', 0) or 0) == fluid]\n        if pref:\n            return pref[0]\n    return cands[0] if cands else None\n\n\ndef build_study(sid, ser_records, tsdir, reader):\n    order_and_meta, read_px, mm_crop_resize = reader\n    rows = ser_records.get(sid, [])\n    vol = np.zeros((MAXS, IMG, IMG), np.uint8); idx = 0; used = set()\n    for plane, fluid, k in SLOTS:\n        r = _pick_series_for_slot(rows, plane, fluid, used)\n        if r is None:\n            idx += k; continue\n        used.add(r['SeriesInstanceUID'])\n        files, med_ps = order_and_meta(f\"{tsdir}/{sid}/{r['SeriesInstanceUID']}\")\n        if not files:\n            idx += k; continue\n        # wide span: the collateral ligaments and lateral meniscus live in the\n        # peripheral slices the old 0.15-0.85 crop threw away. Must match the corpus\n        # the weights were trained on (knee_corpus_v2.py, SPAN_LO/SPAN_HI).\n        n = len(files); lo, hi = int(n * 0.02), int(n * 0.98) - 1; hi = max(hi, lo)\n        picks = np.linspace(lo, hi, k).round().astype(int) if n > 1 else [0] * k\n        arrs = []; pss = []\n        for p in picks:\n            fp, ps = files[min(p, n - 1)]\n            try:\n                arrs.append(read_px(fp)); pss.append(ps)\n            except Exception:\n                arrs.append(None); pss.append(med_ps)\n        valid = [a for a in arrs if a is not None]\n        if valid:\n            allpx = np.concatenate([a.ravel() for a in valid])\n            loq, hiq = np.percentile(allpx, [2.0, 98.0])\n        else:\n            loq, hiq = 0.0, 1.0\n        for a, ps in zip(arrs, pss):\n            if idx >= MAXS: break\n            if a is None: idx += 1; continue\n            aw = np.clip((a - loq) / (hiq - loq + 1e-6), 0, 1)\n            aw = mm_crop_resize(aw, ps if ps > 0 else med_ps)\n            vol[idx] = (aw * 255).astype(np.uint8); idx += 1\n        if idx >= MAXS: break\n    mask = (vol.reshape(MAXS, -1).sum(1) > 0).astype(np.uint8)\n    return vol, mask\n\n\n# ============================================================================\n# Test-root discovery + weights + main\n# ============================================================================\ndef find_test_root():\n    cands = [\"/kaggle/input/competitions/rsna-knee-abnormality-detection\",\n             \"/kaggle/input/rsna-knee-abnormality-detection\"]\n    for b in cands:\n        if os.path.exists(b + \"/test.csv\"):\n            return b\n    for d, _, f in os.walk(\"/kaggle/input\"):\n        if \"test.csv\" in f and (os.path.isdir(d + \"/test_series\") or os.path.isdir(d + \"/test_images\")):\n            return d\n    for d, _, f in os.walk(\"/kaggle/input\"):\n        if \"test.csv\" in f:\n            return d\n    raise RuntimeError(\"no test root under /kaggle/input\")\n\n\ndef find_weight_file(fname):\n    # direct dataset mounts first; NEVER recursive-glob the competitions DICOM tree.\n    direct = [f\"/kaggle/input/raptor-knee-arms-x/{fname}\",\n              f\"/kaggle/input/raptor-knee-arms-x/1/{fname}\",\n              f\"/kaggle/input/raptor-knee-maxspan/{fname}\",\n              f\"/kaggle/input/raptor-knee-arms/{fname}\",\n              f\"/kaggle/input/raptor-knee-arms/1/{fname}\",\n              f\"/kaggle/input/raptor-cnn336/{fname}\"]\n    for p in direct:\n        if os.path.exists(p):\n            return p\n    for d in sorted(glob.glob(\"/kaggle/input/*/\")):\n        if \"competition\" in d.lower():\n            continue\n        hits = glob.glob(os.path.join(d, \"**\", fname), recursive=True)\n        if hits:\n            return hits[0]\n    raise RuntimeError(f\"{fname} not found under /kaggle/input\")\n\n\ndef main():\n    import pandas as pd\n    t0 = time.time()\n    dev = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    ngpu = torch.cuda.device_count()\n    print(f\"device {dev} | gpus {ngpu} | torch {torch.__version__}\", flush=True)\n\n    ROOT = find_test_root()\n    tsdir = ROOT + \"/test_series\"\n    if not os.path.isdir(tsdir):\n        tsdir = ROOT + \"/test_images\"\n    print(\"test root:\", ROOT, \"| series dir:\", tsdir, flush=True)\n\n    test = pd.read_csv(ROOT + \"/test.csv\"); test[\"StudyInstanceUID\"] = test[\"StudyInstanceUID\"].astype(str)\n    test_ids = test[\"StudyInstanceUID\"].tolist()\n    tser = pd.read_csv(ROOT + \"/test_series.csv\")\n    tser[\"StudyInstanceUID\"] = tser[\"StudyInstanceUID\"].astype(str)\n    tser[\"SeriesInstanceUID\"] = tser[\"SeriesInstanceUID\"].astype(str)\n    SER = {k: v.to_dict(\"records\") for k, v in tser.groupby(\"StudyInstanceUID\")}\n    print(f\"test studies {len(test_ids)} | test series {len(tser)}\", flush=True)\n\n    sub_cols = [\"StudyInstanceUID\"] + LAB\n    ssub = os.path.join(ROOT, \"sample_submission.csv\")\n    if os.path.exists(ssub):\n        sub_cols = list(pd.read_csv(ssub, nrows=1).columns)\n\n    reader = _make_reader()\n    N = len(test_ids); A = len(ARMS)\n    arm_probs = [np.full((N, len(LAB)), 0.5, np.float32) for _ in range(A)]\n\n    # SEQUENTIAL ARMS (the OOM fix): only ONE model is resident at a time, so peak system RAM ==\n    # one model == the single-arm champion's footprint (which graded fine at 0.879). Holding both\n    # arms simultaneously OOM'd system RAM on the full hidden test. Each study is re-preprocessed\n    # per arm (build_study is cheap vs inference) and every per-study buffer is freed. Same models,\n    # same windowing, same rank-mean blend -> identical 0.8893 result, just serialized.\n    for a, arm in enumerate(ARMS):\n        wp = find_weight_file(arm[\"file\"])\n        model, res = load_model(wp, arm[\"arch\"], arm[\"res\"], dev)\n        print(f\"[arm {a}] loaded {arm['file']} | res {res} | {time.time()-t0:.0f}s\", flush=True)\n        for i, sid in enumerate(test_ids):\n            try:\n                vol, mask = build_study(sid, SER, tsdir, reader)\n                xw = eval_windows(vol, mask, k=K_EVAL, res=res, norm=NORM)\n                arm_probs[a][i] = infer_probs(model, xw, dev)\n                del vol, mask, xw\n            except Exception as e:\n                print(f\"  [arm {a}] study {i} {sid[:16]} FALLBACK ({type(e).__name__}: {e})\", flush=True)\n            if (i + 1) % 100 == 0 or i + 1 == N:\n                print(f\"  [arm {a}] {i+1}/{N} | {time.time()-t0:.0f}s\", flush=True)\n        del model\n        gc.collect()\n        if str(dev).startswith(\"cuda\"):\n            torch.cuda.empty_cache()\n        print(f\"[arm {a}] done + freed | {time.time()-t0:.0f}s\", flush=True)\n\n    # WEIGHTED rank-mean blend across the test set, per finding (the offline recipe).\n    # Weights come from ARMS[*][\"w\"] and are normalised here, so dropping/adding an arm can\n    # never silently change the scale. Falls back to equal weights if none are declared.\n    _w = np.array([float(a.get(\"w\", 1.0)) for a in ARMS], dtype=np.float64)\n    _w = _w / _w.sum()\n    print(f\"[blend] weighted rank-mean w={dict(zip([a['file'] for a in ARMS], _w.round(4)))}\", flush=True)\n    ranks = np.tensordot(_w, np.stack([rankpct(np.clip(p, 0, 1)) for p in arm_probs]),\n                         axes=(0, 0))                                          # (N,12) in [0,1]\n    if not np.isfinite(ranks).all():\n        ranks[~np.isfinite(ranks)] = 0.5\n\n    sub = pd.DataFrame(ranks.astype(np.float32), columns=LAB)\n    sub.insert(0, \"StudyInstanceUID\", test_ids)\n    sub = sub[sub_cols]\n    assert list(sub.columns) == sub_cols, \"column order drift\"\n    assert sub[\"StudyInstanceUID\"].tolist() == test_ids, \"row identity drift\"\n    assert np.isfinite(sub[LAB].values).all()\n    out = \"/kaggle/working/submission_coatnet.csv\"\n    sub.to_csv(out, index=False)\n    print(\"wrote\", out, \"|\", len(sub), \"rows x\", len(sub.columns), \"cols\", flush=True)\n    print(sub.head().to_string(index=False), flush=True)\n    print(f\"DONE {time.time()-t0:.0f}s\", flush=True)\n\n\nif __name__ == \"__main__\":\n    try:\n        main()\n    except Exception as _coat_exc:\n        import traceback as _coat_traceback\n        print(f\"CoAtNet branch failed; retaining transformer submission: {type(_coat_exc).__name__}: {_coat_exc}\", flush=True)\n        _coat_traceback.print_exc()\n\n\n\n# Blend two independently validated rank predictors. The default remains the transformer\n# submission if the CoAtNet branch did not complete, so a recoverable branch failure\n# cannot erase a valid competition artifact.\nfrom pathlib import Path as _BlendPath\nimport numpy as _blend_np\nimport pandas as _blend_pd\n\n_blend_work = _BlendPath('/kaggle/working')\n_blend_transformer_path = _blend_work / 'submission.csv'\n_blend_coatnet_path = _blend_work / 'submission_coatnet.csv'\nif _blend_coatnet_path.is_file():\n    _blend_transformer = _blend_pd.read_csv(_blend_transformer_path, dtype={'StudyInstanceUID': str})\n    _blend_coatnet = _blend_pd.read_csv(_blend_coatnet_path, dtype={'StudyInstanceUID': str})\n    _blend_labels = [c for c in _blend_transformer.columns if c != 'StudyInstanceUID']\n    if _blend_coatnet.columns.tolist() != _blend_transformer.columns.tolist():\n        raise RuntimeError('CoAtNet/transformer submission schema mismatch')\n    if _blend_coatnet['StudyInstanceUID'].tolist() != _blend_transformer['StudyInstanceUID'].tolist():\n        raise RuntimeError('CoAtNet/transformer study order mismatch')\n    _blend_tr = _blend_transformer[\n        _blend_labels\n    ].rank(\n        method='average',\n        pct=True,\n    )\n\n    _blend_cr = _blend_coatnet[\n        _blend_labels\n    ].rank(\n        method='average',\n        pct=True,\n    )\n\n    _blend_output = (\n        _blend_transformer.copy()\n    )\n\n    _coatnet_weight = {\n        label: 0.50\n        for label in _blend_labels\n    }\n\n    if globals().get(\n        'V18_CALIBRATOR_APPLIED',\n        False,\n    ):\n        _coatnet_weight.update(\n            {\n                'Medial Meniscus': 0.52,\n                'Lateral Meniscus': 0.54,\n                'Fracture': 0.54,\n            }\n        )\n\n    for _label in _blend_labels:\n        _cw = float(\n            _coatnet_weight[\n                _label\n            ]\n        )\n\n        _blend_output[\n            _label\n        ] = (\n            (\n                1.0\n                - _cw\n            )\n            * _blend_tr[\n                _label\n            ]\n            + _cw\n            * _blend_cr[\n                _label\n            ]\n        )\n\n    _blend_output[\n        _blend_labels\n    ] = _blend_output[\n        _blend_labels\n    ].rank(\n        method='average',\n        pct=True,\n    )\n\n    print(\n        '[V18] CoAtNet target weights: '\n        + ', '.join(\n            f'{label}='\n            f'{_coatnet_weight[label]:.2f}'\n            for label in _blend_labels\n            if (\n                _coatnet_weight[label]\n                != 0.50\n            )\n        ),\n        flush=True,\n    )\n\n    _blend_values = _blend_output[\n        _blend_labels\n    ].to_numpy(\n        _blend_np.float64\n    )\n    if not _blend_np.isfinite(_blend_values).all() or _blend_values.min() < 0 or _blend_values.max() > 1:\n        raise RuntimeError('invalid blended prediction values')\n    _blend_output.to_csv(_blend_transformer_path, index=False)\n    print(f'final submission.csv = V18 calibrated transformer + CoAtNet rank blend; {_blend_output.shape}', flush=True)\nelse:\n    print('CoAtNet output unavailable; submission.csv remains the validated transformer ensemble', flush=True)\n\n# V18 output hygiene.\nfor _v18_temp in (\n    _blend_work / 'submission_coatnet.csv',\n    _blend_work / 'submission_transformer_0920.csv',\n):\n    try:\n        if _v18_temp.is_file():\n            _v18_temp.unlink()\n    except OSError:\n        pass\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-02T10:58:52.100766Z","iopub.execute_input":"2026-09-02T10:58:52.101001Z","iopub.status.idle":"2026-09-02T10:59:25.158064Z","shell.execute_reply.started":"2026-09-02T10:58:52.100978Z","shell.execute_reply":"2026-09-02T10:59:25.157512Z"}},"outputs":[],"execution_count":null}]}