{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":113558,"databundleVersionId":14456136,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 🔧 FIXED & IMPROVED SCIENTIFIC OBJECT DETECTION PROJECT\n\nimport os\nimport cv2\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\nimport yaml\nimport json\nfrom datetime import datetime\n\nprint(\"🚀 Starting Improved Scientific Object Detection Project...\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T09:32:20.517532Z","iopub.execute_input":"2025-11-26T09:32:20.519649Z","iopub.status.idle":"2025-11-26T09:32:20.529752Z","shell.execute_reply.started":"2025-11-26T09:32:20.519493Z","shell.execute_reply":"2025-11-26T09:32:20.528618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EnhancedScientificDatasetCreator:\n    def __init__(self, output_dir=\"enhanced_scientific_dataset\"):\n        self.output_dir = output_dir\n        self.classes = [\"cell\", \"particle\", \"crystal\", \"fiber\", \"vesicle\"]\n        self.colors = {\n            \"cell\": (0, 255, 0),       # Green\n            \"particle\": (255, 0, 0),   # Blue  \n            \"crystal\": (0, 0, 255),    # Red\n            \"fiber\": (255, 255, 0),    # Cyan\n            \"vesicle\": (255, 0, 255)   # Magenta\n        }\n        \n    def create_microscope_background(self, height=512, width=512):\n        \"\"\"Create more realistic microscope background\"\"\"\n        # Base noise with more variation\n        background = np.random.normal(180, 25, (height, width)).astype(np.uint8)\n        background = np.clip(background, 150, 220)\n        background = cv2.merge([background, background, background])\n        \n        # Add gradient and texture\n        x, y = np.meshgrid(np.linspace(0, 1, width), np.linspace(0, 1, height))\n        gradient = (x * 0.3 + y * 0.2) * 40 + 160\n        gradient = gradient.astype(np.uint8)\n        gradient = cv2.merge([gradient, gradient, gradient])\n        \n        # Add some noise texture\n        texture = np.random.randint(0, 10, (height, width, 3), dtype=np.uint8)\n        \n        # Combine\n        background = cv2.addWeighted(background, 0.6, gradient, 0.3, 0)\n        background = cv2.addWeighted(background, 0.9, texture, 0.1, 0)\n        \n        return background\n    \n    def create_cell(self, img, center):\n        \"\"\"Create more realistic cell structure\"\"\"\n        x, y = center\n        # Cell body with variation\n        axes = (np.random.randint(25, 45), np.random.randint(25, 45))\n        angle = np.random.randint(0, 180)\n        color = self.colors[\"cell\"]\n        \n        # Main cell body with gradient\n        for i in range(3):\n            current_axes = (axes[0]-i*2, axes[1]-i*2)\n            if current_axes[0] > 5 and current_axes[1] > 5:\n                shade = max(0, color[0] - i*20)\n                current_color = (shade, color[1], color[2])\n                cv2.ellipse(img, (x, y), current_axes, angle, 0, 360, current_color, -1)\n        \n        # Nucleus\n        nucleus_axes = (axes[0]//3, axes[1]//3)\n        nucleus_offset_x = np.random.randint(-10, 10)\n        nucleus_offset_y = np.random.randint(-10, 10)\n        cv2.ellipse(img, (x+nucleus_offset_x, y+nucleus_offset_y), nucleus_axes, \n                   angle, 0, 360, (0, 200, 100), -1)\n        \n        bbox = [x-axes[0], y-axes[1], x+axes[0], y+axes[1]]\n        return self.adjust_bbox_to_image(bbox, img.shape)\n    \n    def create_particle(self, img, center):\n        \"\"\"Create nanoparticle cluster\"\"\"\n        x, y = center\n        main_radius = np.random.randint(8, 18)\n        color = self.colors[\"particle\"]\n        \n        # Main particle\n        cv2.circle(img, (x, y), main_radius, color, -1)\n        cv2.circle(img, (x, y), main_radius, (150, 0, 0), 1)\n        \n        # Smaller surrounding particles\n        for _ in range(np.random.randint(2, 5)):\n            offset_x = np.random.randint(-20, 20)\n            offset_y = np.random.randint(-20, 20)\n            small_radius = np.random.randint(2, 6)\n            if (x+offset_x) > small_radius and (y+offset_y) > small_radius:\n                cv2.circle(img, (x+offset_x, y+offset_y), small_radius, color, -1)\n        \n        bbox = [x-main_radius, y-main_radius, x+main_radius, y+main_radius]\n        return self.adjust_bbox_to_image(bbox, img.shape)\n    \n    def create_crystal(self, img, center):\n        \"\"\"Create crystal structure with facets\"\"\"\n        x, y = center\n        size = np.random.randint(20, 35)\n        color = self.colors[\"crystal\"]\n        \n        # Create hexagonal crystal\n        points = []\n        for i in range(6):\n            angle_deg = 60 * i - 30\n            angle_rad = np.radians(angle_deg)\n            px = x + size * np.cos(angle_rad)\n            py = y + size * np.sin(angle_rad)\n            points.append([px, py])\n        \n        points = np.array(points, dtype=np.int32)\n        cv2.fillPoly(img, [points], color)\n        \n        # Add internal facets\n        inner_size = size * 0.6\n        inner_points = []\n        for i in range(6):\n            angle_deg = 60 * i - 30\n            angle_rad = np.radians(angle_deg)\n            px = x + inner_size * np.cos(angle_rad)\n            py = y + inner_size * np.sin(angle_rad)\n            inner_points.append([px, py])\n        \n        inner_points = np.array(inner_points, dtype=np.int32)\n        cv2.polylines(img, [inner_points], True, (0, 0, 150), 1)\n        \n        bbox = [x-size, y-size, x+size, y+size]\n        return self.adjust_bbox_to_image(bbox, img.shape)\n    \n    def create_fiber(self, img, center):\n        \"\"\"Create fiber structure with texture\"\"\"\n        x, y = center\n        length = np.random.randint(40, 80)\n        width = np.random.randint(4, 10)\n        angle = np.random.randint(0, 180)\n        color = self.colors[\"fiber\"]\n        \n        # Calculate endpoints\n        rad = np.radians(angle)\n        x2 = int(x + length * np.cos(rad))\n        y2 = int(y + length * np.sin(rad))\n        \n        # Draw main fiber\n        cv2.line(img, (x, y), (x2, y2), color, width)\n        \n        # Add fiber texture\n        for i in range(width//2):\n            offset = i - width//2\n            cv2.line(img, (x, y+offset), (x2, y2+offset), (200, 200, 100), 1)\n        \n        bbox = [min(x, x2)-width, min(y, y2)-width, \n                max(x, x2)+width, max(y, y2)+width]\n        return self.adjust_bbox_to_image(bbox, img.shape)\n    \n    def create_vesicle(self, img, center):\n        \"\"\"Create vesicle with membrane\"\"\"\n        x, y = center\n        radius = np.random.randint(15, 30)\n        color = self.colors[\"vesicle\"]\n        \n        # Outer membrane\n        cv2.circle(img, (x, y), radius, color, 2)\n        \n        # Inner content with gradient\n        for r in range(radius-1, 1, -2):\n            intensity = int(200 * (r / radius))\n            cv2.circle(img, (x, y), r, (intensity, intensity//2, intensity), -1)\n        \n        # Add some internal structures\n        for _ in range(3):\n            internal_x = x + np.random.randint(-radius//2, radius//2)\n            internal_y = y + np.random.randint(-radius//2, radius//2)\n            internal_r = np.random.randint(2, 5)\n            cv2.circle(img, (internal_x, internal_y), internal_r, (255, 150, 255), -1)\n        \n        bbox = [x-radius, y-radius, x+radius, y+radius]\n        return self.adjust_bbox_to_image(bbox, img.shape)\n    \n    def adjust_bbox_to_image(self, bbox, img_shape):\n        \"\"\"Ensure bbox is within image boundaries\"\"\"\n        h, w = img_shape[:2]\n        x1, y1, x2, y2 = bbox\n        x1 = max(0, min(x1, w-1))\n        y1 = max(0, min(y1, h-1))\n        x2 = max(0, min(x2, w-1))\n        y2 = max(0, min(y2, h-1))\n        return [x1, y1, x2, y2]\n    \n    def create_dataset(self, num_images=200, train_ratio=0.8):\n        \"\"\"Create enhanced dataset\"\"\"\n        # Create directories\n        images_dir = os.path.join(self.output_dir, \"images\")\n        labels_dir = os.path.join(self.output_dir, \"labels\")\n        \n        for split in ['train', 'val']:\n            os.makedirs(os.path.join(images_dir, split), exist_ok=True)\n            os.makedirs(os.path.join(labels_dir, split), exist_ok=True)\n        \n        # Create dataset info\n        dataset_info = {\n            'description': 'Enhanced Scientific Object Detection Dataset',\n            'created': datetime.now().isoformat(),\n            'num_images': num_images,\n            'classes': self.classes,\n            'image_size': (512, 512)\n        }\n        \n        with open(os.path.join(self.output_dir, 'dataset_info.json'), 'w') as f:\n            json.dump(dataset_info, f, indent=2)\n        \n        # Generate images\n        num_train = int(num_images * train_ratio)\n        \n        for i in range(num_images):\n            split = 'train' if i < num_train else 'val'\n            \n            # Create background\n            img = self.create_microscope_background()\n            labels = []\n            \n            # Add random objects with controlled distribution\n            num_objects = np.random.randint(2, 6)  # Fewer objects for better learning\n            \n            for obj_idx in range(num_objects):\n                # Ensure class distribution\n                obj_type = np.random.choice(self.classes, p=[0.3, 0.25, 0.2, 0.15, 0.1])\n                \n                # Position objects to avoid heavy overlap\n                max_attempts = 10\n                for attempt in range(max_attempts):\n                    x = np.random.randint(80, 432)\n                    y = np.random.randint(80, 432)\n                    \n                    # Check for overlap with existing objects\n                    overlap = False\n                    for existing_label in labels:\n                        existing_data = existing_label.split()\n                        if len(existing_data) == 5:\n                            ex_x = float(existing_data[1]) * 512\n                            ex_y = float(existing_data[2]) * 512\n                            ex_w = float(existing_data[3]) * 512\n                            ex_h = float(existing_data[4]) * 512\n                            \n                            # Simple overlap check\n                            if abs(x - ex_x) < 50 and abs(y - ex_y) < 50:\n                                overlap = True\n                                break\n                    \n                    if not overlap:\n                        break\n                \n                bbox = getattr(self, f\"create_{obj_type}\")(img, (x, y))\n                class_id = self.classes.index(obj_type)\n                \n                # Convert to YOLO format\n                img_h, img_w = img.shape[:2]\n                x_center = ((bbox[0] + bbox[2]) / 2) / img_w\n                y_center = ((bbox[1] + bbox[3]) / 2) / img_h\n                width = (bbox[2] - bbox[0]) / img_w\n                height = (bbox[3] - bbox[1]) / img_h\n                \n                # Ensure valid coordinates\n                if width > 0.01 and height > 0.01:  # Minimum size threshold\n                    labels.append(f\"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\")\n            \n            # Save image\n            img_filename = f\"{split}_{i:04d}.jpg\"\n            img_path = os.path.join(images_dir, split, img_filename)\n            cv2.imwrite(img_path, img)\n            \n            # Save labels\n            label_filename = f\"{split}_{i:04d}.txt\"\n            label_path = os.path.join(labels_dir, split, label_filename)\n            with open(label_path, 'w') as f:\n                f.write('\\n'.join(labels))\n            \n            if i % 25 == 0:\n                print(f\"Created {i}/{num_images} images\")\n        \n        # Display samples\n        self.display_sample_images(images_dir)\n        \n        print(f\"✅ Enhanced dataset created: {self.output_dir}\")\n        print(f\"📊 Classes: {self.classes}\")\n        print(f\"📁 Train: {num_train} images\")\n        print(f\"📁 Val: {num_images - num_train} images\")\n        print(f\"📏 Image size: 512x512\")\n    \n    def display_sample_images(self, images_dir):\n        \"\"\"Display sample images from the dataset\"\"\"\n        train_images = [f for f in os.listdir(os.path.join(images_dir, 'train')) if f.endswith('.jpg')]\n        sample_images = train_images[:3]\n        \n        fig, axes = plt.subplots(1, 3, figsize=(15, 5))\n        \n        for i, img_name in enumerate(sample_images):\n            img_path = os.path.join(images_dir, 'train', img_name)\n            img = cv2.imread(img_path)\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            \n            # Load corresponding labels\n            label_path = img_path.replace('images', 'labels').replace('.jpg', '.txt')\n            if os.path.exists(label_path):\n                with open(label_path, 'r') as f:\n                    num_objects = len(f.readlines())\n            else:\n                num_objects = 0\n            \n            axes[i].imshow(img)\n            axes[i].set_title(f'{img_name}\\n{num_objects} objects')\n            axes[i].axis('off')\n        \n        plt.tight_layout()\n        plt.show()\n\n# Create the enhanced dataset\nprint(\"📊 Creating enhanced scientific dataset...\")\ncreator = EnhancedScientificDatasetCreator()\ncreator.create_dataset(num_images=200)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T09:32:30.663217Z","iopub.execute_input":"2025-11-26T09:32:30.664007Z","iopub.status.idle":"2025-11-26T09:32:37.487973Z","shell.execute_reply.started":"2025-11-26T09:32:30.663945Z","shell.execute_reply":"2025-11-26T09:32:37.486133Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ImprovedYOLO(nn.Module):\n    def __init__(self, num_classes=5, num_anchors=3):\n        super(ImprovedYOLO, self).__init__()\n        self.num_classes = num_classes\n        self.num_anchors = num_anchors\n        \n        # Enhanced backbone with more features\n        self.backbone = nn.Sequential(\n            # Block 1: 3x416x416 -> 32x208x208\n            nn.Conv2d(3, 32, 3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.LeakyReLU(0.1),\n            nn.MaxPool2d(2, 2),\n            \n            # Block 2: 32x208x208 -> 64x104x104\n            nn.Conv2d(32, 64, 3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.LeakyReLU(0.1),\n            nn.MaxPool2d(2, 2),\n            \n            # Block 3: 64x104x104 -> 128x52x52\n            nn.Conv2d(64, 128, 3, padding=1),\n            nn.BatchNorm2d(128),\n            nn.LeakyReLU(0.1),\n            nn.MaxPool2d(2, 2),\n            \n            # Block 4: 128x52x52 -> 256x26x26\n            nn.Conv2d(128, 256, 3, padding=1),\n            nn.BatchNorm2d(256),\n            nn.LeakyReLU(0.1),\n            nn.MaxPool2d(2, 2),\n            \n            # Block 5: 256x26x26 -> 512x13x13\n            nn.Conv2d(256, 512, 3, padding=1),\n            nn.BatchNorm2d(512),\n            nn.LeakyReLU(0.1),\n            nn.MaxPool2d(2, 2),\n            \n            # Additional conv layers for better features\n            nn.Conv2d(512, 1024, 3, padding=1),\n            nn.BatchNorm2d(1024),\n            nn.LeakyReLU(0.1),\n            \n            nn.Conv2d(1024, 512, 1),\n            nn.BatchNorm2d(512),\n            nn.LeakyReLU(0.1),\n        )\n        \n        # Detection head\n        self.detection_head = nn.Sequential(\n            nn.Conv2d(512, 256, 3, padding=1),\n            nn.LeakyReLU(0.1),\n            nn.Conv2d(256, 128, 3, padding=1),\n            nn.LeakyReLU(0.1),\n            nn.Conv2d(128, (5 + num_classes) * num_anchors, 1),\n        )\n        \n        # Initialize weights\n        self._initialize_weights()\n    \n    def _initialize_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='leaky_relu')\n                if m.bias is not None:\n                    nn.init.constant_(m.bias, 0)\n            elif isinstance(m, nn.BatchNorm2d):\n                nn.init.constant_(m.weight, 1)\n                nn.init.constant_(m.bias, 0)\n    \n    def forward(self, x):\n        features = self.backbone(x)\n        detection = self.detection_head(features)\n        \n        # Reshape output\n        batch_size, _, grid_h, grid_w = detection.shape\n        detection = detection.view(batch_size, self.num_anchors, 5 + self.num_classes, grid_h, grid_w)\n        detection = detection.permute(0, 1, 3, 4, 2).contiguous()\n        \n        return detection\n\nclass FixedYOLOLoss(nn.Module):\n    def __init__(self, num_classes=5):\n        super(FixedYOLOLoss, self).__init__()\n        self.num_classes = num_classes\n        self.mse_loss = nn.MSELoss()\n        self.bce_loss = nn.BCEWithLogitsLoss()\n        \n    def forward(self, predictions, targets):\n        \"\"\"\n        Fixed loss function - handles tensor unpacking correctly\n        predictions: [batch, anchors, grid_h, grid_w, 5+num_classes]\n        targets: [batch, max_objects, 6] where last dim is [class, x, y, w, h, exists]\n        \"\"\"\n        batch_size = predictions.shape[0]\n        total_loss = 0\n        coord_loss = 0\n        obj_loss = 0\n        class_loss = 0\n        \n        for batch_idx in range(batch_size):\n            pred = predictions[batch_idx]  # [anchors, grid_h, grid_w, 5+num_classes]\n            target = targets[batch_idx]    # [max_objects, 6]\n            \n            # Find objects that exist\n            obj_mask = target[:, 5] == 1\n            existing_objects = target[obj_mask]\n            \n            if len(existing_objects) == 0:\n                continue\n                \n            for obj_idx in range(len(existing_objects)):\n                obj = existing_objects[obj_idx]\n                \n                # FIXED: Proper tensor unpacking\n                class_id = int(obj[0].item())\n                x = obj[1].item()\n                y = obj[2].item()\n                w = obj[3].item()\n                h = obj[4].item()\n                \n                # Convert normalized coordinates to grid coordinates (13x13 grid)\n                grid_x = int(x * 13)\n                grid_y = int(y * 13)\n                \n                # Ensure grid coordinates are within bounds\n                grid_x = max(0, min(grid_x, 12))\n                grid_y = max(0, min(grid_y, 12))\n                \n                # Get predictions for this grid cell (using first anchor)\n                anchor_pred = pred[0, grid_y, grid_x]\n                \n                # Extract components\n                pred_bbox = anchor_pred[:4]  # [x, y, w, h]\n                pred_conf = anchor_pred[4:5]  # objectness\n                pred_class = anchor_pred[5:]  # class probabilities\n                \n                # Target values\n                target_bbox = torch.tensor([x, y, w, h], \n                                         dtype=torch.float32, \n                                         device=pred_bbox.device)\n                target_conf = torch.tensor([1.0], \n                                         dtype=torch.float32, \n                                         device=pred_conf.device)\n                target_class = torch.zeros(self.num_classes, \n                                         dtype=torch.float32, \n                                         device=pred_class.device)\n                target_class[class_id] = 1.0\n                \n                # Calculate losses\n                coord_loss += self.mse_loss(pred_bbox, target_bbox)\n                obj_loss += self.bce_loss(pred_conf, target_conf)\n                class_loss += self.bce_loss(pred_class, target_class)\n        \n        # Normalize by number of objects\n        num_objects = max(1, (targets[:, :, 5] == 1).sum().item())\n        coord_loss = coord_loss / num_objects\n        obj_loss = obj_loss / num_objects\n        class_loss = class_loss / num_objects\n        \n        total_loss = coord_loss + obj_loss + class_loss\n        \n        return total_loss, coord_loss, obj_loss, class_loss\n\n# Test the improved model\nprint(\"🧠 Testing improved YOLO model...\")\nmodel = ImprovedYOLO(num_classes=5)\ntest_input = torch.randn(2, 3, 416, 416)\ntest_output = model(test_input)\nprint(f\"✅ Improved model working!\")\nprint(f\"   Input shape: {test_input.shape}\")\nprint(f\"   Output shape: {test_output.shape}\")\nprint(f\"   Parameters: {sum(p.numel() for p in model.parameters()):,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T09:33:01.817019Z","iopub.execute_input":"2025-11-26T09:33:01.818931Z","iopub.status.idle":"2025-11-26T09:33:02.903318Z","shell.execute_reply.started":"2025-11-26T09:33:01.818885Z","shell.execute_reply":"2025-11-26T09:33:02.901830Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EnhancedScientificDataset(Dataset):\n    def __init__(self, data_dir, split='train', img_size=416, max_objects=15):\n        self.data_dir = data_dir\n        self.split = split\n        self.img_size = img_size\n        self.max_objects = max_objects\n        self.image_files = []\n        self.label_files = []\n        \n        # Find all images and labels\n        image_dir = os.path.join(data_dir, 'images', split)\n        label_dir = os.path.join(data_dir, 'labels', split)\n        \n        for f in sorted(os.listdir(image_dir)):\n            if f.endswith('.jpg'):\n                self.image_files.append(os.path.join(image_dir, f))\n                label_file = os.path.join(label_dir, f.replace('.jpg', '.txt'))\n                self.label_files.append(label_file)\n        \n        print(f\"📁 Loaded {len(self.image_files)} {split} images\")\n    \n    def __len__(self):\n        return len(self.image_files)\n    \n    def __getitem__(self, idx):\n        # Load image\n        image_path = self.image_files[idx]\n        image = cv2.imread(image_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        original_h, original_w = image.shape[:2]\n        \n        # Resize image\n        image = cv2.resize(image, (self.img_size, self.img_size))\n        \n        # Normalize\n        image = image / 255.0\n        image = torch.from_numpy(image).permute(2, 0, 1).float()\n        \n        # Load labels and create fixed-size target tensor\n        label_path = self.label_files[idx]\n        targets = torch.zeros((self.max_objects, 6))  # [class_id, x, y, w, h, exists_flag]\n        \n        if os.path.exists(label_path):\n            with open(label_path, 'r') as f:\n                lines = f.readlines()\n                for i, line in enumerate(lines):\n                    if i >= self.max_objects:\n                        break\n                    data = line.strip().split()\n                    if len(data) == 5:\n                        class_id = int(data[0])\n                        x_center = float(data[1])\n                        y_center = float(data[2])\n                        width = float(data[3])\n                        height = float(data[4])\n                        \n                        targets[i] = torch.tensor([class_id, x_center, y_center, width, height, 1.0])\n        \n        return image, targets\n\ndef enhanced_collate_fn(batch):\n    \"\"\"Enhanced collate function\"\"\"\n    images, targets = zip(*batch)\n    images = torch.stack(images, 0)\n    targets = torch.stack(targets, 0)\n    return images, targets\n\n# Test the enhanced data loader\nprint(\"📊 Testing enhanced data loader...\")\ndataset = EnhancedScientificDataset('enhanced_scientific_dataset', 'train')\ndataloader = DataLoader(dataset, batch_size=4, shuffle=True, collate_fn=enhanced_collate_fn)\n\nimages, targets = next(iter(dataloader))\nprint(f\"✅ Enhanced data loader working!\")\nprint(f\"   Batch images shape: {images.shape}\")\nprint(f\"   Batch targets shape: {targets.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T09:33:24.516150Z","iopub.execute_input":"2025-11-26T09:33:24.517405Z","iopub.status.idle":"2025-11-26T09:33:24.590646Z","shell.execute_reply.started":"2025-11-26T09:33:24.517363Z","shell.execute_reply":"2025-11-26T09:33:24.589401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ScientificTrainer:\n    def __init__(self):\n        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n        print(f\"🚀 Using device: {self.device}\")\n        \n        # Model and training setup\n        self.model = ImprovedYOLO(num_classes=5).to(self.device)\n        self.criterion = FixedYOLOLoss(num_classes=5)\n        self.optimizer = optim.AdamW(self.model.parameters(), lr=0.001, weight_decay=1e-4)\n        \n        # Learning rate scheduler\n        self.scheduler = optim.lr_scheduler.CosineAnnealingLR(self.optimizer, T_max=10)\n        \n        # Enhanced data loading\n        self.train_dataset = EnhancedScientificDataset('enhanced_scientific_dataset', 'train', max_objects=10)\n        self.val_dataset = EnhancedScientificDataset('enhanced_scientific_dataset', 'val', max_objects=10)\n        \n        self.train_loader = DataLoader(\n            self.train_dataset, batch_size=8, shuffle=True, \n            collate_fn=enhanced_collate_fn, num_workers=0\n        )\n        self.val_loader = DataLoader(\n            self.val_dataset, batch_size=8, shuffle=False, \n            collate_fn=enhanced_collate_fn, num_workers=0\n        )\n        \n        # Training history\n        self.train_losses = []\n        self.val_losses = []\n        self.learning_rates = []\n        \n        print(f\"📊 Training on {len(self.train_dataset)} images\")\n        print(f\"📊 Validating on {len(self.val_dataset)} images\")\n        print(f\"🧠 Model parameters: {sum(p.numel() for p in self.model.parameters()):,}\")\n    \n    def train_epoch(self, epoch):\n        self.model.train()\n        running_loss = 0.0\n        running_coord = 0.0\n        running_obj = 0.0\n        running_class = 0.0\n        \n        progress_bar = tqdm(self.train_loader, desc=f'Epoch {epoch+1}')\n        for batch_idx, (images, targets) in enumerate(progress_bar):\n            images = images.to(self.device)\n            targets = targets.to(self.device)\n            \n            self.optimizer.zero_grad()\n            \n            # Forward pass\n            predictions = self.model(images)\n            total_loss, coord_loss, obj_loss, class_loss = self.criterion(predictions, targets)\n            \n            # Backward pass\n            total_loss.backward()\n            \n            # Gradient clipping\n            torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)\n            \n            self.optimizer.step()\n            \n            # Accumulate losses\n            running_loss += total_loss.item()\n            running_coord += coord_loss.item()\n            running_obj += obj_loss.item()\n            running_class += class_loss.item()\n            \n            # Update progress bar\n            if batch_idx % 2 == 0:\n                progress_bar.set_postfix({\n                    'Loss': f'{total_loss.item():.4f}',\n                    'Coord': f'{coord_loss.item():.4f}',\n                    'Obj': f'{obj_loss.item():.4f}',\n                    'Class': f'{class_loss.item():.4f}'\n                })\n        \n        # Calculate averages\n        avg_loss = running_loss / len(self.train_loader)\n        avg_coord = running_coord / len(self.train_loader)\n        avg_obj = running_obj / len(self.train_loader)\n        avg_class = running_class / len(self.train_loader)\n        \n        self.train_losses.append(avg_loss)\n        self.learning_rates.append(self.optimizer.param_groups[0]['lr'])\n        \n        return avg_loss, avg_coord, avg_obj, avg_class\n    \n    def validate(self, epoch):\n        self.model.eval()\n        running_loss = 0.0\n        \n        with torch.no_grad():\n            for images, targets in self.val_loader:\n                images = images.to(self.device)\n                targets = targets.to(self.device)\n                \n                predictions = self.model(images)\n                loss, _, _, _ = self.criterion(predictions, targets)\n                running_loss += loss.item()\n        \n        avg_loss = running_loss / len(self.val_loader)\n        self.val_losses.append(avg_loss)\n        \n        return avg_loss\n    \n    def train(self, epochs=15):\n        print(\"🚀 Starting Enhanced Training...\")\n        os.makedirs('enhanced_checkpoints', exist_ok=True)\n        \n        best_val_loss = float('inf')\n        \n        for epoch in range(epochs):\n            print(f\"\\n{'='*50}\")\n            print(f\"🎯 Epoch {epoch+1}/{epochs}\")\n            print(f\"{'='*50}\")\n            \n            # Train\n            train_loss, coord_loss, obj_loss, class_loss = self.train_epoch(epoch)\n            \n            # Validate\n            val_loss = self.validate(epoch)\n            \n            # Update learning rate\n            self.scheduler.step()\n            \n            # Save best model\n            if val_loss < best_val_loss:\n                best_val_loss = val_loss\n                torch.save({\n                    'epoch': epoch,\n                    'model_state_dict': self.model.state_dict(),\n                    'optimizer_state_dict': self.optimizer.state_dict(),\n                    'train_loss': train_loss,\n                    'val_loss': val_loss,\n                    'train_losses': self.train_losses,\n                    'val_losses': self.val_losses,\n                }, 'enhanced_checkpoints/best_model.pth')\n                print(f\"💾 New best model saved! Val Loss: {val_loss:.4f}\")\n            \n            # Save checkpoint every 5 epochs\n            if (epoch + 1) % 5 == 0:\n                torch.save({\n                    'epoch': epoch,\n                    'model_state_dict': self.model.state_dict(),\n                    'optimizer_state_dict': self.optimizer.state_dict(),\n                }, f'enhanced_checkpoints/checkpoint_epoch_{epoch+1}.pth')\n                print(f\"📦 Checkpoint saved at epoch {epoch+1}\")\n            \n            print(f\"✅ Training Results:\")\n            print(f\"   Train Loss: {train_loss:.4f}\")\n            print(f\"   Val Loss: {val_loss:.4f}\")\n            print(f\"   Learning Rate: {self.optimizer.param_groups[0]['lr']:.6f}\")\n            print(f\"   Components - Coord: {coord_loss:.4f}, Obj: {obj_loss:.4f}, Class: {class_loss:.4f}\")\n        \n        # Save final model\n        torch.save(self.model.state_dict(), 'enhanced_checkpoints/final_model.pth')\n        \n        # Plot results\n        self.plot_training_results()\n        \n        print(\"🎉 Enhanced training completed!\")\n        print(f\"📊 Best validation loss: {best_val_loss:.4f}\")\n        print(f\"📈 Final training loss: {self.train_losses[-1]:.4f}\")\n    \n    def plot_training_results(self):\n        \"\"\"Plot comprehensive training results\"\"\"\n        fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 10))\n        \n        # Loss curves\n        epochs = range(1, len(self.train_losses) + 1)\n        ax1.plot(epochs, self.train_losses, 'b-', label='Training Loss', linewidth=2)\n        ax1.plot(epochs, self.val_losses, 'r-', label='Validation Loss', linewidth=2)\n        ax1.set_xlabel('Epoch')\n        ax1.set_ylabel('Loss')\n        ax1.set_title('Training and Validation Loss')\n        ax1.legend()\n        ax1.grid(True, alpha=0.3)\n        \n        # Learning rate\n        ax2.plot(epochs, self.learning_rates, 'g-', linewidth=2)\n        ax2.set_xlabel('Epoch')\n        ax2.set_ylabel('Learning Rate')\n        ax2.set_title('Learning Rate Schedule')\n        ax2.grid(True, alpha=0.3)\n        \n        # Final validation sample\n        self.show_validation_sample(ax3)\n        \n        # Model architecture info\n        ax4.axis('off')\n        ax4.text(0.1, 0.9, f\"Model Summary:\", fontsize=12, fontweight='bold')\n        ax4.text(0.1, 0.7, f\"Parameters: {sum(p.numel() for p in self.model.parameters()):,}\", fontsize=10)\n        ax4.text(0.1, 0.6, f\"Classes: 5\", fontsize=10)\n        ax4.text(0.1, 0.5, f\"Best Val Loss: {min(self.val_losses):.4f}\", fontsize=10)\n        ax4.text(0.1, 0.4, f\"Final Train Loss: {self.train_losses[-1]:.4f}\", fontsize=10)\n        ax4.text(0.1, 0.3, f\"Training Images: {len(self.train_dataset)}\", fontsize=10)\n        ax4.text(0.1, 0.2, f\"Validation Images: {len(self.val_dataset)}\", fontsize=10)\n        \n        plt.tight_layout()\n        plt.savefig('enhanced_training_results.png', dpi=300, bbox_inches='tight')\n        plt.show()\n    \n    def show_validation_sample(self, ax):\n        \"\"\"Show a sample from validation set\"\"\"\n        self.model.eval()\n        with torch.no_grad():\n            sample_image, sample_target = self.val_dataset[0]\n            sample_image_display = sample_image.permute(1, 2, 0).cpu().numpy()\n            sample_image_display = (sample_image_display * 255).astype(np.uint8)\n            \n            ax.imshow(sample_image_display)\n            ax.set_title('Sample Validation Image')\n            ax.axis('off')\n\n# Start enhanced training\nprint(\"🚀 Starting enhanced training pipeline...\")\ntrainer = ScientificTrainer()\ntrainer.train(epochs=15)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T09:33:42.783969Z","iopub.execute_input":"2025-11-26T09:33:42.785411Z","iopub.status.idle":"2025-11-26T09:58:08.370364Z","shell.execute_reply.started":"2025-11-26T09:33:42.785369Z","shell.execute_reply":"2025-11-26T09:58:08.369044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EnhancedScientificDetector:\n    def __init__(self, model_path=None, num_classes=5, conf_threshold=0.3, iou_threshold=0.4):\n        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n        self.conf_threshold = conf_threshold\n        self.iou_threshold = iou_threshold\n        \n        # Load model\n        self.model = ImprovedYOLO(num_classes=num_classes)\n        if model_path and os.path.exists(model_path):\n            if model_path.endswith('.pth'):\n                checkpoint = torch.load(model_path, map_location=self.device)\n                if 'model_state_dict' in checkpoint:\n                    self.model.load_state_dict(checkpoint['model_state_dict'])\n                else:\n                    self.model.load_state_dict(checkpoint)\n            print(f\"✅ Loaded trained model from {model_path}\")\n        else:\n            print(\"⚠️  Using randomly initialized model\")\n        \n        self.model.to(self.device)\n        self.model.eval()\n        \n        # Class information\n        self.class_names = ['cell', 'particle', 'crystal', 'fiber', 'vesicle']\n        self.colors = [\n            (0, 255, 0),    # Green - cell\n            (255, 0, 0),    # Blue - particle\n            (0, 0, 255),    # Red - crystal\n            (255, 255, 0),  # Cyan - fiber\n            (255, 0, 255)   # Magenta - vesicle\n        ]\n    \n    def non_max_suppression(self, boxes, scores, iou_threshold):\n        \"\"\"Simple Non-Maximum Suppression\"\"\"\n        if len(boxes) == 0:\n            return []\n        \n        # Sort by score\n        sorted_indices = np.argsort(scores)[::-1]\n        keep = []\n        \n        while len(sorted_indices) > 0:\n            # Pick the box with highest score\n            current_idx = sorted_indices[0]\n            keep.append(current_idx)\n            \n            if len(sorted_indices) == 1:\n                break\n            \n            # Calculate IoU with remaining boxes\n            current_box = boxes[current_idx]\n            remaining_boxes = boxes[sorted_indices[1:]]\n            \n            # Calculate IoU\n            x1 = np.maximum(current_box[0], remaining_boxes[:, 0])\n            y1 = np.maximum(current_box[1], remaining_boxes[:, 1])\n            x2 = np.minimum(current_box[2], remaining_boxes[:, 2])\n            y2 = np.minimum(current_box[3], remaining_boxes[:, 3])\n            \n            intersection = np.maximum(0, x2 - x1) * np.maximum(0, y2 - y1)\n            area_current = (current_box[2] - current_box[0]) * (current_box[3] - current_box[1])\n            area_remaining = (remaining_boxes[:, 2] - remaining_boxes[:, 0]) * (remaining_boxes[:, 3] - remaining_boxes[:, 1])\n            union = area_current + area_remaining - intersection\n            \n            iou = intersection / union\n            \n            # Keep boxes with IoU less than threshold\n            keep_indices = np.where(iou <= iou_threshold)[0]\n            sorted_indices = sorted_indices[keep_indices + 1]\n        \n        return keep\n    \n    def detect(self, image_path_or_array):\n        \"\"\"Run detection on an image\"\"\"\n        # Load image\n        if isinstance(image_path_or_array, str):\n            image = cv2.imread(image_path_or_array)\n            if image is None:\n                raise ValueError(f\"Could not load image from {image_path_or_array}\")\n        else:\n            image = image_path_or_array.copy()\n        \n        original_image = image.copy()\n        image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        original_h, original_w = image.shape[:2]\n        \n        # Preprocess\n        input_image = cv2.resize(image_rgb, (416, 416))\n        input_image = input_image / 255.0\n        input_tensor = torch.from_numpy(input_image).permute(2, 0, 1).float().unsqueeze(0)\n        input_tensor = input_tensor.to(self.device)\n        \n        # Inference\n        with torch.no_grad():\n            predictions = self.model(input_tensor)\n        \n        # Process predictions\n        detections = self.process_predictions(predictions, original_w, original_h)\n        \n        # Draw detections\n        result_image = self.draw_detections(original_image, detections)\n        \n        return result_image, detections\n    \n    def process_predictions(self, predictions, original_w, original_h):\n        \"\"\"Process model predictions into detections\"\"\"\n        detections = []\n        pred = predictions[0]  # First batch\n        \n        # Extract predictions\n        boxes = []\n        scores = []\n        class_ids = []\n        \n        for anchor in range(pred.shape[0]):  # Loop through anchors\n            for i in range(pred.shape[1]):   # Grid y\n                for j in range(pred.shape[2]):  # Grid x\n                    cell = pred[anchor, i, j]\n                    \n                    # Get objectness score\n                    objectness = torch.sigmoid(cell[4]).item()\n                    \n                    if objectness > self.conf_threshold:\n                        # Get class probabilities\n                        class_probs = torch.softmax(cell[5:], dim=0)\n                        class_score, class_id = torch.max(class_probs, 0)\n                        class_score = class_score.item()\n                        class_id = class_id.item()\n                        \n                        # Combined score\n                        score = objectness * class_score\n                        \n                        if score > self.conf_threshold:\n                            # Convert grid coordinates to image coordinates\n                            x_center = (torch.sigmoid(cell[0]) + j) / 13\n                            y_center = (torch.sigmoid(cell[1]) + i) / 13\n                            width = torch.sigmoid(cell[2])\n                            height = torch.sigmoid(cell[3])\n                            \n                            # Convert to pixel coordinates\n                            x1 = int((x_center - width/2) * original_w)\n                            y1 = int((y_center - height/2) * original_h)\n                            x2 = int((x_center + width/2) * original_w)\n                            y2 = int((y_center + height/2) * original_h)\n                            \n                            # Ensure valid coordinates\n                            x1 = max(0, min(x1, original_w-1))\n                            y1 = max(0, min(y1, original_h-1))\n                            x2 = max(0, min(x2, original_w-1))\n                            y2 = max(0, min(y2, original_h-1))\n                            \n                            if x2 > x1 and y2 > y1:  # Valid box\n                                boxes.append([x1, y1, x2, y2])\n                                scores.append(score)\n                                class_ids.append(class_id)\n        \n        # Apply Non-Maximum Suppression\n        if len(boxes) > 0:\n            keep_indices = self.non_max_suppression(np.array(boxes), np.array(scores), self.iou_threshold)\n            \n            for idx in keep_indices:\n                detections.append({\n                    'bbox': boxes[idx],\n                    'confidence': scores[idx],\n                    'class_id': class_ids[idx],\n                    'class_name': self.class_names[class_ids[idx]]\n                })\n        \n        return detections\n    \n    def draw_detections(self, image, detections):\n        \"\"\"Draw bounding boxes and labels on image\"\"\"\n        result = image.copy()\n        \n        for det in detections:\n            x1, y1, x2, y2 = det['bbox']\n            confidence = det['confidence']\n            class_name = det['class_name']\n            class_id = det['class_id']\n            \n            color = self.colors[class_id]\n            \n            # Draw bounding box\n            cv2.rectangle(result, (x1, y1), (x2, y2), color, 2)\n            \n            # Draw label background\n            label = f\"{class_name}: {confidence:.2f}\"\n            label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)[0]\n            \n            cv2.rectangle(result, (x1, y1 - label_size[1] - 10), \n                        (x1 + label_size[0], y1), color, -1)\n            \n            # Draw label text\n            cv2.putText(result, label, (x1, y1 - 5), \n                      cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n        \n        return result\n\n    def evaluate_on_dataset(self, dataset_split='val'):\n        \"\"\"Evaluate model on the entire dataset split\"\"\"\n        dataset = EnhancedScientificDataset('enhanced_scientific_dataset', dataset_split)\n        \n        total_detections = 0\n        total_objects = 0\n        correct_detections = 0\n        \n        print(f\"🔍 Evaluating on {dataset_split} set ({len(dataset)} images)...\")\n        \n        for i in range(min(20, len(dataset))):  # Evaluate on first 20 images for speed\n            image, targets = dataset[i]\n            \n            # Convert tensor to numpy image for detection\n            image_np = image.permute(1, 2, 0).numpy()\n            image_np = (image_np * 255).astype(np.uint8)\n            \n            # Run detection\n            result_image, detections = self.detect(image_np)\n            \n            # Count objects in ground truth\n            num_objects = (targets[:, 5] == 1).sum().item()\n            total_objects += num_objects\n            total_detections += len(detections)\n            \n            # Simple accuracy: if we detected roughly the right number of objects\n            if abs(len(detections) - num_objects) <= 2:\n                correct_detections += 1\n        \n        accuracy = correct_detections / min(20, len(dataset))\n        \n        print(f\"📊 Evaluation Results:\")\n        print(f\"   Images processed: {min(20, len(dataset))}\")\n        print(f\"   Total objects: {total_objects}\")\n        print(f\"   Total detections: {total_detections}\")\n        print(f\"   Detection accuracy: {accuracy:.2%}\")\n        \n        return accuracy\n\n# Test the enhanced detector\nprint(\"🔮 Testing enhanced detector...\")\ndetector = EnhancedScientificDetector('enhanced_checkpoints/best_model.pth')\n\n# Test on sample images\nsample_images = [\n    'enhanced_scientific_dataset/images/train/train_0000.jpg',\n    'enhanced_scientific_dataset/images/train/train_0020.jpg',\n    'enhanced_scientific_dataset/images/val/val_0000.jpg'\n]\n\nfig, axes = plt.subplots(2, 3, figsize=(18, 12))\n\nfor i, img_path in enumerate(sample_images):\n    if os.path.exists(img_path):\n        result_img, detections = detector.detect(img_path)\n        result_img_rgb = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)\n        \n        axes[0, i].imshow(result_img_rgb)\n        axes[0, i].set_title(f'Detection: {len(detections)} objects')\n        axes[0, i].axis('off')\n        \n        # Show detection details\n        detection_text = \"\\n\".join([f\"{d['class_name']}: {d['confidence']:.2f}\" \n                                  for d in detections[:3]])  # Show first 3\n        axes[1, i].text(0.1, 0.9, f\"Detections ({len(detections)}):\", \n                       fontsize=12, fontweight='bold', transform=axes[1, i].transAxes)\n        axes[1, i].text(0.1, 0.7, detection_text, \n                       fontsize=10, transform=axes[1, i].transAxes)\n        axes[1, i].axis('off')\n\nplt.tight_layout()\nplt.show()\n\n# Evaluate on validation set\ndetector.evaluate_on_dataset('val')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T09:58:27.954769Z","iopub.execute_input":"2025-11-26T09:58:27.956244Z","iopub.status.idle":"2025-11-26T09:58:33.964044Z","shell.execute_reply.started":"2025-11-26T09:58:27.956198Z","shell.execute_reply":"2025-11-26T09:58:33.962963Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"🎉 ENHANCED SCIENTIFIC OBJECT DETECTION PROJECT COMPLETED! 🎉\")\nprint(\"=\" * 70)\n\nprint(\"\\n✅ IMPROVEMENTS MADE:\")\nprint(\"   🔧 Fixed: Tensor unpacking error in loss function\")\nprint(\"   🔧 Fixed: Proper bounding box coordinate handling\")\nprint(\"   🔧 Enhanced: More realistic synthetic data generation\")\nprint(\"   🔧 Enhanced: Better model architecture with proper initialization\")\nprint(\"   🔧 Enhanced: Non-maximum suppression for detection\")\nprint(\"   🔧 Enhanced: Comprehensive training visualization\")\nprint(\"   🔧 Enhanced: Model evaluation metrics\")\n\nprint(\"\\n📊 PROJECT CAPABILITIES:\")\nprint(\"   🎯 Detects 5 scientific object types:\")\nprint(\"      🟢 Cells | 🔵 Particles | 🔴 Crystals | 🟡 Fibers | 🟣 Vesicles\")\nprint(f\"   📁 Dataset: {len(trainer.train_dataset)} training + {len(trainer.val_dataset)} validation images\")\nprint(f\"   🧠 Model: {sum(p.numel() for p in trainer.model.parameters()):,} parameters\")\nprint(f\"   📈 Training: {len(trainer.train_losses)} epochs completed\")\n\nprint(\"\\n🚀 IMMEDIATE NEXT STEPS:\")\nprint(\"   1. Train for more epochs (50-100) for better accuracy\")\nprint(\"   2. Add data augmentation (random flips, rotations, color changes)\")\nprint(\"   3. Implement proper mAP (mean Average Precision) metrics\")\nprint(\"   4. Add confidence threshold tuning\")\nprint(\"   5. Test on real scientific microscope images\")\n\nprint(\"\\n🔬 RESEARCH APPLICATIONS:\")\nprint(\"   - Cell biology: Count and classify cells in microscopy\")\nprint(\"   - Materials science: Analyze nanoparticle distributions\")\nprint(\"   - Pharmaceutical research: Study drug delivery vesicles\")\nprint(\"   - Quality control: Detect crystal formations\")\nprint(\"   - Biomedical engineering: Analyze fiber structures\")\n\nprint(\"\\n💡 DEPLOYMENT OPTIONS:\")\nprint(\"   - Web application with Streamlit or Gradio\")\nprint(\"   - REST API for batch processing\")\nprint(\"   - Integration with laboratory equipment\")\nprint(\"   - Real-time analysis for live microscopy\")\n\n# Show final project architecture\nprint(\"\\n🏗️ PROJECT ARCHITECTURE:\")\nproject_structure = \"\"\"\nenhanced_scientific_detection/\n├── enhanced_scientific_dataset/    # Generated dataset\n│   ├── images/train/               # Training images\n│   ├── images/val/                 # Validation images  \n│   ├── labels/train/               # YOLO format labels\n│   └── labels/val/                 # Validation labels\n├── enhanced_checkpoints/           # Model checkpoints\n│   ├── best_model.pth             # Best performing model\n│   ├── final_model.pth            # Final trained model\n│   └── checkpoint_epoch_*.pth     # Training checkpoints\n├── enhanced_training_results.png   # Training visualization\n└── dataset_info.json              # Dataset metadata\n\"\"\"\n\nprint(project_structure)\n\nprint(\"✨ PROJECT READY FOR SCIENTIFIC RESEARCH! ✨\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T09:58:56.595800Z","iopub.execute_input":"2025-11-26T09:58:56.596148Z","iopub.status.idle":"2025-11-26T09:58:56.609234Z","shell.execute_reply.started":"2025-11-26T09:58:56.596126Z","shell.execute_reply":"2025-11-26T09:58:56.607588Z"}},"outputs":[],"execution_count":null}]}