{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install pydicom albumentations timm -q\n\nimport os\nimport cv2\nimport glob\nimport pydicom\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm\nfrom tqdm.notebook import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# 1. إعدادات المسابقة (معدلة خصيصاً لمسابقة RSNA Knee)\nclass CFG:\n    seed = 42\n    model_name = 'tf_efficientnet_b4_ns'\n    img_size = 256\n    epochs = 5\n    batch_size = 16\n    lr = 1e-4\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    \n    # مسارات مسابقة RSNA Knee\n    train_csv = '/kaggle/input/rsna-knee-abnormality-detection/train.csv'\n    train_dir = '/kaggle/input/rsna-knee-abnormality-detection/train_series'\n    \n    # أسماء الأعمدة الـ 12 المستهدفة للتنبؤ\n    target_cols = [\n        'ACL', 'MCL', 'Medial Meniscus', 'Lateral Meniscus', \n        'Medial OA', 'Lateral OA', 'PF OA', 'Joint Effusion', \n        'Synovitis', 'Baker Cyst', 'Bone Contusion', 'Fracture'\n    ]\n\ntorch.manual_seed(CFG.seed)\nnp.random.seed(CFG.seed)\n\n# 2. قراءة صور الرنين المغناطيسي (DICOM)\ndef read_dicom(path):\n    dicom = pydicom.dcmread(path)\n    data = dicom.pixel_array\n    \n    data = data - np.min(data)\n    if np.max(data) != 0:\n        data = data / np.max(data)\n    data = (data * 255).astype(np.uint8)\n    \n    if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        data = 255 - data\n        \n    return data\n\n# 3. هيكل البيانات (تم التعديل ليناسب StudyInstanceUID و 12 مرضاً)\nclass RSNAKneeDataset(Dataset):\n    def __init__(self, df, img_dir, transforms=None, is_test=False):\n        self.df = df\n        self.img_dir = img_dir\n        self.transforms = transforms\n        self.is_test = is_test\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        # في هذه المسابقة، المجلدات مسماة بـ StudyInstanceUID\n        study_id = row['StudyInstanceUID']\n        study_dir = os.path.join(self.img_dir, str(study_id))\n        \n        # جلب كل شرائح الرنين المغناطيسي لهذا المريض\n        dcm_files = sorted(glob.glob(os.path.join(study_dir, '**/*.dcm'), recursive=True))\n        \n        # أخذ الشريحة الوسطى (لأنها عادة ما تظهر أوضح تفاصيل الركبة)\n        if len(dcm_files) > 0:\n            middle_idx = len(dcm_files) // 2\n            img_path = dcm_files[middle_idx]\n            image = read_dicom(img_path)\n            image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)\n        else:\n            # صورة سوداء احتياطية في حال كان المجلد فارغاً\n            image = np.zeros((CFG.img_size, CFG.img_size, 3), dtype=np.uint8)\n        \n        if self.transforms:\n            image = self.transforms(image=image)['image']\n            \n        if self.is_test:\n            return image\n        else:\n            # استخراج جميع التقييمات الـ 12 كـ Vector واحد\n            labels = row[CFG.target_cols].values.astype(np.float32)\n            return image, torch.tensor(labels)\n\n# 4. تحسين الصور (Augmentations)\ndef get_transforms():\n    return A.Compose([\n        A.Resize(CFG.img_size, CFG.img_size),\n        A.HorizontalFlip(p=0.5),\n        A.RandomBrightnessContrast(p=0.2),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2(),\n    ])\n\n# 5. النموذج الطبي (معدل ليُخرج 12 تنبؤ بدلاً من واحد)\nclass RSNAKneeModel(nn.Module):\n    def __init__(self, model_name, num_classes=12):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=True, in_chans=3)\n        self.n_features = self.model.classifier.in_features\n        self.model.classifier = nn.Identity()\n        \n        # 12 مخرج، مخرج لكل نوع من إصابات الركبة\n        self.fc = nn.Linear(self.n_features, num_classes)\n        \n    def forward(self, x):\n        features = self.model(x)\n        output = self.fc(features)\n        return output\n\n# 6. دالة التدريب\ndef train_fn(dataloader, model, criterion, optimizer, scaler, device):\n    model.train()\n    total_loss = 0\n    progress = tqdm(dataloader, desc=\"Training\")\n    \n    for images, labels in progress:\n        images = images.to(device)\n        labels = labels.to(device) # Labels هنا حجمها [Batch, 12]\n        \n        optimizer.zero_grad()\n        with torch.cuda.amp.autocast():\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            \n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        \n        total_loss += loss.item()\n        progress.set_postfix({'loss': loss.item()})\n        \n    return total_loss / len(dataloader)\n\n# ========================================================\n# 7. التشغيل (Main)\n# ========================================================\nprint(\"جاري تحميل البيانات الحقيقية لمسابقة الركبة...\")\ndf = pd.read_csv(CFG.train_csv)\n\n\nCFG.target_cols = [col for col in CFG.target_cols if col in df.columns]\nprint(f\"تم العثور على {len(CFG.target_cols)} أمراض للتنبؤ بها.\")\n\ntrain_dataset = RSNAKneeDataset(df, CFG.train_dir, get_transforms())\ntrain_loader = DataLoader(train_dataset, batch_size=CFG.batch_size, shuffle=True, num_workers=2)\n\nmodel = RSNAKneeModel(CFG.model_name, num_classes=len(CFG.target_cols)).to(CFG.device)\n# استخدمنا BCEWithLogitsLoss لأن المريض قد يعاني من مرضين في نفس الوقت (Multi-label)\ncriterion = nn.BCEWithLogitsLoss() \noptimizer = torch.optim.AdamW(model.parameters(), lr=CFG.lr)\nscaler = torch.cuda.amp.GradScaler()\n\nprint(\"بدء التدريب الفعلي...\")\nfor epoch in range(CFG.epochs):\n    print(f\"Epoch {epoch+1}/{CFG.epochs}\")\n    train_loss = train_fn(train_loader, model, criterion, optimizer, scaler, CFG.device)\n    print(f\"Average Training Loss: {train_loss:.4f}\")\n\ntorch.save(model.state_dict(), 'rsna_knee_model.pth')\nprint(\"✅ تم تدريب الموديل وحفظه بنجاح!\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-15T21:34:35.503062Z","iopub.execute_input":"2026-09-15T21:34:35.503441Z","iopub.status.idle":"2026-09-15T21:34:39.834704Z","shell.execute_reply.started":"2026-09-15T21:34:35.50339Z","shell.execute_reply":"2026-09-15T21:34:39.833342Z"}},"outputs":[],"execution_count":null}]}