{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":107469,"databundleVersionId":13058354,"sourceType":"competition"},{"sourceId":10766655,"sourceType":"kernelVersion"},{"sourceId":65736731,"sourceType":"kernelVersion"},{"sourceId":470538,"sourceType":"modelInstanceVersion","modelInstanceId":379586,"modelId":399493},{"sourceId":469389,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":378680,"modelId":398808}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#!/usr/bin/env python3\n\"\"\"\nEnhanced Multi-Source Object Detection Ensemble with Self-Tuning\nAdvanced ensemble with more models, intelligent strategies, and automatic optimization\n\"\"\"\n\n# Install required packages with error handling\nimport subprocess\nimport sys\n\ndef install_package(package):\n    \"\"\"Install package with error handling\"\"\"\n    try:\n        subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", package, \"-q\"])\n        return True\n    except:\n        print(f\"Warning: Failed to install {package}\")\n        return False\n\n# Core packages + additional for enhanced features\npackages = [\n    'ultralytics',\n    'opencv-python-headless',\n    'pandas',\n    'matplotlib',\n    'seaborn',\n    'tqdm',\n    'ensemble-boxes',\n    'torch',\n    'torchvision',\n    'transformers',\n    'timm',\n    'huggingface_hub',\n    'Pillow',\n    'requests',\n    'gdown',\n    'optuna',  # For hyperparameter optimization\n    'scikit-learn',  # For validation splitting\n    'albumentations',  # For advanced augmentations\n    'pycocotools',  # For COCO evaluation\n    'super-gradients',  # For YOLO-NAS\n]\n\nprint(\"Installing required packages...\")\nfor package in packages:\n    install_package(package)\n\nimport os\nimport cv2\nimport json\nimport torch\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom pathlib import Path\nfrom tqdm import tqdm\nimport warnings\nimport time\nimport requests\nfrom PIL import Image\nimport traceback\nfrom typing import List, Dict, Tuple, Optional, Union\nfrom collections import defaultdict\nimport gc\nimport hashlib\nfrom datetime import datetime\nwarnings.filterwarnings(\"ignore\")\n\n# Conditional imports with fallbacks\ntry:\n    from ultralytics import YOLO\n    YOLO_AVAILABLE = True\nexcept:\n    YOLO_AVAILABLE = False\n    print(\"Warning: YOLO not available\")\n\ntry:\n    from transformers import (\n        AutoModelForObjectDetection, \n        AutoImageProcessor,\n        DetrForObjectDetection,\n        YolosForObjectDetection,\n        pipeline,\n        DetrImageProcessor,\n        ConditionalDetrForObjectDetection,\n        DeformableDetrForObjectDetection,\n        TableTransformerForObjectDetection,\n        DPTForDepthEstimation\n    )\n    TRANSFORMERS_AVAILABLE = True\nexcept:\n    TRANSFORMERS_AVAILABLE = False\n    print(\"Warning: Transformers not available\")\n\ntry:\n    from ensemble_boxes import weighted_boxes_fusion, nms, soft_nms, non_maximum_weighted\n    ENSEMBLE_BOXES_AVAILABLE = True\nexcept:\n    ENSEMBLE_BOXES_AVAILABLE = False\n    print(\"Warning: Ensemble boxes not available\")\n\ntry:\n    from huggingface_hub import hf_hub_download, list_models, snapshot_download\n    HF_AVAILABLE = True\nexcept:\n    HF_AVAILABLE = False\n    print(\"Warning: HuggingFace Hub not available\")\n\ntry:\n    import timm\n    TIMM_AVAILABLE = True\nexcept:\n    TIMM_AVAILABLE = False\n    print(\"Warning: TIMM not available\")\n\ntry:\n    import optuna\n    OPTUNA_AVAILABLE = True\nexcept:\n    OPTUNA_AVAILABLE = False\n    print(\"Warning: Optuna not available\")\n\ntry:\n    from sklearn.model_selection import KFold\n    from sklearn.metrics import average_precision_score\n    SKLEARN_AVAILABLE = True\nexcept:\n    SKLEARN_AVAILABLE = False\n    print(\"Warning: Scikit-learn not available\")\n\ntry:\n    import albumentations as A\n    ALBUMENTATIONS_AVAILABLE = True\nexcept:\n    ALBUMENTATIONS_AVAILABLE = False\n    print(\"Warning: Albumentations not available\")\n\ntry:\n    from super_gradients.training import models as sg_models\n    SG_AVAILABLE = True\nexcept:\n    SG_AVAILABLE = False\n    print(\"Warning: Super-gradients not available\")\n\n\nclass EnhancedObjectDetectionEnsemble:\n    \"\"\"\n    Enhanced ensemble with self-tuning capabilities and advanced strategies\n    \"\"\"\n    def __init__(self, device=None, enable_self_tuning=True):\n        self.models = []\n        self.model_info = []\n        self.device = device or ('cuda' if torch.cuda.is_available() else 'cpu')\n        self.model_performance = defaultdict(lambda: {'mAP': 0.0, 'speed': 0.0, 'predictions': 0})\n        self.failed_models = []\n        self.retry_attempts = 3\n        self.enable_self_tuning = enable_self_tuning\n        \n        # Self-tuning parameters\n        self.adaptive_weights = {}\n        self.class_specific_thresholds = defaultdict(lambda: 0.001)\n        self.model_correlations = {}\n        self.ensemble_params = {\n            'iou_threshold': 0.5,\n            'conf_threshold': 0.001,\n            'sigma': 0.5,\n            'skip_box_threshold': 0.001\n        }\n        \n        # Advanced ensemble strategies\n        self.ensemble_strategies = {\n            'wbf': self._weighted_boxes_fusion,\n            'soft_nms': self._soft_nms,\n            'nms': self._standard_nms,\n            'nmw': self._non_maximum_weighted,\n            'adaptive': self._adaptive_ensemble,\n            'cascade': self._cascade_ensemble,\n            'class_specific': self._class_specific_ensemble,\n            'size_aware': self._size_aware_ensemble,\n            'hybrid': self._hybrid_ensemble\n        }\n        \n        # Test Time Augmentation settings\n        self.tta_transforms = self._setup_tta_transforms()\n        \n        print(f\"Using device: {self.device}\")\n        print(f\"Self-tuning: {'Enabled' if enable_self_tuning else 'Disabled'}\")\n        \n    def _setup_tta_transforms(self):\n        \"\"\"Setup Test Time Augmentation transforms\"\"\"\n        if not ALBUMENTATIONS_AVAILABLE:\n            return None\n            \n        return A.Compose([\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.5),\n            A.RandomRotate90(p=0.5),\n            A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5),\n            A.RandomScale(scale_limit=0.2, p=0.5),\n        ], bbox_params=A.BboxParams(format='pascal_voc', label_fields=['labels']))\n    \n    def safe_model_load(self, load_func, model_name, max_retries=3):\n        \"\"\"Safely load a model with retries and error handling\"\"\"\n        for attempt in range(max_retries):\n            try:\n                model = load_func()\n                if model is not None:\n                    # Initialize performance tracking\n                    self.model_performance[model_name] = {\n                        'mAP': 0.5,  # Initial assumption\n                        'speed': 1.0,\n                        'predictions': 0,\n                        'successes': 0,\n                        'failures': 0\n                    }\n                    return model\n            except Exception as e:\n                print(f\"Attempt {attempt + 1} failed for {model_name}: {str(e)}\")\n                if attempt < max_retries - 1:\n                    time.sleep(2 ** attempt)\n                else:\n                    self.failed_models.append(model_name)\n        return None\n    \n    def download_enhanced_yolo_models(self):\n        \"\"\"Download comprehensive YOLO model collection\"\"\"\n        if not YOLO_AVAILABLE:\n            print(\"Skipping YOLO models - package not available\")\n            return\n            \n        print(\"\\n\" + \"=\"*60)\n        print(\"DOWNLOADING ENHANCED YOLO MODELS\")\n        print(\"=\"*60)\n        \n        # Extended YOLO model collection\n        yolo_models = [\n            # YOLOv8 complete series\n            {'name': 'yolov8n.pt', 'weight': 0.7, 'type': 'detection'},\n            {'name': 'yolov8s.pt', 'weight': 0.8, 'type': 'detection'},\n            {'name': 'yolov8m.pt', 'weight': 0.9, 'type': 'detection'},\n            {'name': 'yolov8l.pt', 'weight': 1.0, 'type': 'detection'},\n            {'name': 'yolov8x.pt', 'weight': 1.1, 'type': 'detection'},\n            \n            # YOLOv8 6.0 versions (larger input size)\n            {'name': 'yolov8n6.pt', 'weight': 0.75, 'type': 'detection'},\n            {'name': 'yolov8s6.pt', 'weight': 0.85, 'type': 'detection'},\n            {'name': 'yolov8m6.pt', 'weight': 0.95, 'type': 'detection'},\n            {'name': 'yolov8l6.pt', 'weight': 1.05, 'type': 'detection'},\n            \n            # YOLOv5 models\n            {'name': 'yolov5n.pt', 'weight': 0.6, 'type': 'detection'},\n            {'name': 'yolov5s.pt', 'weight': 0.7, 'type': 'detection'},\n            {'name': 'yolov5m.pt', 'weight': 0.8, 'type': 'detection'},\n            {'name': 'yolov5l.pt', 'weight': 0.9, 'type': 'detection'},\n            {'name': 'yolov5x.pt', 'weight': 1.0, 'type': 'detection'},\n            \n            # YOLOv5 6.0 versions\n            {'name': 'yolov5n6.pt', 'weight': 0.65, 'type': 'detection'},\n            {'name': 'yolov5s6.pt', 'weight': 0.75, 'type': 'detection'},\n            \n            # Specialized models\n            {'name': 'yolov8n-seg.pt', 'weight': 0.7, 'type': 'segmentation'},\n            {'name': 'yolov8s-seg.pt', 'weight': 0.8, 'type': 'segmentation'},\n            {'name': 'yolov8m-seg.pt', 'weight': 0.9, 'type': 'segmentation'},\n            {'name': 'yolov8l-seg.pt', 'weight': 1.0, 'type': 'segmentation'},\n            {'name': 'yolov8x-seg.pt', 'weight': 1.1, 'type': 'segmentation'},\n            \n            # World models\n            {'name': 'yolov8s-world.pt', 'weight': 0.8, 'type': 'world'},\n            {'name': 'yolov8m-world.pt', 'weight': 0.9, 'type': 'world'},\n            {'name': 'yolov8l-world.pt', 'weight': 1.0, 'type': 'world'},\n            \n            # Pose estimation models (can be adapted for bbox)\n            {'name': 'yolov8n-pose.pt', 'weight': 0.7, 'type': 'pose'},\n            {'name': 'yolov8s-pose.pt', 'weight': 0.8, 'type': 'pose'},\n        ]\n        \n        for model_config in yolo_models:\n            model_name = model_config['name']\n            \n            def load_yolo():\n                return YOLO(model_name)\n            \n            model = self.safe_model_load(load_yolo, model_name)\n            if model:\n                self.models.append(model)\n                self.model_info.append({\n                    'name': model_name,\n                    'type': 'yolo',\n                    'subtype': model_config['type'],\n                    'weight': model_config['weight'],\n                    'architecture': 'YOLO',\n                    'source': 'ultralytics',\n                    'input_size': 640 if '6' not in model_name else 1280\n                })\n                print(f\"✓ Loaded {model_name}\")\n    \n    def download_advanced_transformer_models(self):\n        \"\"\"Download advanced transformer-based detection models\"\"\"\n        if not TRANSFORMERS_AVAILABLE:\n            print(\"Skipping Transformer models - package not available\")\n            return\n            \n        print(\"\\n\" + \"=\"*60)\n        print(\"DOWNLOADING ADVANCED TRANSFORMER MODELS\")\n        print(\"=\"*60)\n        \n        transformer_models = [\n            # DETR family\n            {\n                'name': 'facebook/detr-resnet-50',\n                'weight': 1.0,\n                'processor': 'facebook/detr-resnet-50'\n            },\n            {\n                'name': 'facebook/detr-resnet-101',\n                'weight': 1.1,\n                'processor': 'facebook/detr-resnet-101'\n            },\n            {\n                'name': 'facebook/detr-resnet-50-dc5',\n                'weight': 1.05,\n                'processor': 'facebook/detr-resnet-50-dc5'\n            },\n            {\n                'name': 'facebook/detr-resnet-101-dc5',\n                'weight': 1.15,\n                'processor': 'facebook/detr-resnet-101-dc5'\n            },\n            \n            # Conditional DETR\n            {\n                'name': 'microsoft/conditional-detr-resnet-50',\n                'weight': 1.2,\n                'processor': 'microsoft/conditional-detr-resnet-50'\n            },\n            \n            # Deformable DETR\n            {\n                'name': 'SenseTime/deformable-detr',\n                'weight': 1.3,\n                'processor': 'SenseTime/deformable-detr'\n            },\n            {\n                'name': 'SenseTime/deformable-detr-single-scale',\n                'weight': 1.25,\n                'processor': 'SenseTime/deformable-detr-single-scale'\n            },\n            \n            # YOLOS variants\n            {\n                'name': 'hustvl/yolos-tiny',\n                'weight': 0.8,\n                'processor': 'hustvl/yolos-tiny'\n            },\n            {\n                'name': 'hustvl/yolos-small',\n                'weight': 0.9,\n                'processor': 'hustvl/yolos-small'\n            },\n            {\n                'name': 'hustvl/yolos-base',\n                'weight': 1.0,\n                'processor': 'hustvl/yolos-base'\n            },\n            \n            # Table Transformer\n            {\n                'name': 'microsoft/table-transformer-detection',\n                'weight': 0.9,\n                'processor': 'microsoft/table-transformer-detection'\n            },\n            {\n                'name': 'microsoft/table-transformer-structure-recognition',\n                'weight': 0.85,\n                'processor': 'microsoft/table-transformer-structure-recognition'\n            },\n            \n            # DINO (DETR with Improved deNoising anchOr boxes)\n            {\n                'name': 'IDEA-Research/dino-vitb16-4scale',\n                'weight': 1.4,\n                'processor': 'IDEA-Research/dino-vitb16-4scale'\n            },\n            \n            # Specialized models\n            {\n                'name': 'valentinafeve/yolos-fashionpedia',\n                'weight': 0.9,\n                'processor': 'valentinafeve/yolos-fashionpedia'\n            },\n            {\n                'name': 'nickmuchi/yolos-small-finetuned-masks',\n                'weight': 0.85,\n                'processor': 'nickmuchi/yolos-small-finetuned-masks'\n            },\n            {\n                'name': 'devonho/detr-resnet-50_finetuned_coco',\n                'weight': 1.0,\n                'processor': 'devonho/detr-resnet-50_finetuned_coco'\n            }\n        ]\n        \n        for model_config in transformer_models:\n            model_name = model_config['name']\n            \n            def load_transformer():\n                try:\n                    # Try different loading strategies\n                    model = None\n                    processor = None\n                    \n                    # Strategy 1: Standard loading\n                    try:\n                        model = AutoModelForObjectDetection.from_pretrained(\n                            model_name,\n                            trust_remote_code=True,\n                            ignore_mismatched_sizes=True\n                        )\n                        processor = AutoImageProcessor.from_pretrained(\n                            model_config['processor'],\n                            trust_remote_code=True\n                        )\n                    except:\n                        # Strategy 2: Try specific model classes\n                        if 'detr' in model_name.lower():\n                            if 'conditional' in model_name:\n                                model = ConditionalDetrForObjectDetection.from_pretrained(model_name)\n                            elif 'deformable' in model_name:\n                                model = DeformableDetrForObjectDetection.from_pretrained(model_name)\n                            else:\n                                model = DetrForObjectDetection.from_pretrained(model_name)\n                            processor = DetrImageProcessor.from_pretrained(model_config['processor'])\n                        elif 'yolos' in model_name.lower():\n                            model = YolosForObjectDetection.from_pretrained(model_name)\n                            processor = AutoImageProcessor.from_pretrained(model_config['processor'])\n                        elif 'table' in model_name.lower():\n                            model = TableTransformerForObjectDetection.from_pretrained(model_name)\n                            processor = AutoImageProcessor.from_pretrained(model_config['processor'])\n                    \n                    if model and processor:\n                        # Move to device\n                        model = model.to(self.device)\n                        model.eval()\n                        return {'model': model, 'processor': processor}\n                    \n                    # Strategy 3: Pipeline fallback\n                    pipe = pipeline(\"object-detection\", model=model_name, device=0 if self.device == 'cuda' else -1)\n                    return {'pipeline': pipe}\n                    \n                except Exception as e:\n                    print(f\"Failed to load transformer {model_name}: {e}\")\n                    return None\n            \n            result = self.safe_model_load(load_transformer, model_name)\n            if result:\n                self.models.append(result)\n                self.model_info.append({\n                    'name': model_name,\n                    'type': 'transformer',\n                    'weight': model_config['weight'],\n                    'architecture': 'Transformer',\n                    'source': 'huggingface'\n                })\n                print(f\"✓ Loaded {model_name}\")\n    \n    def download_yolov9_models(self):\n        \"\"\"Download YOLOv9 models\"\"\"\n        print(\"\\n\" + \"=\"*60)\n        print(\"DOWNLOADING YOLOV9 MODELS\")\n        print(\"=\"*60)\n        \n        yolov9_models = [\n            {\n                'name': 'YOLOv9-C',\n                'url': 'https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-c.pt',\n                'weight': 1.2,\n            },\n            {\n                'name': 'YOLOv9-E',\n                'url': 'https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-e.pt',\n                'weight': 1.3,\n            },\n            {\n                'name': 'YOLOv9-C-converted',\n                'url': 'https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-c-converted.pt',\n                'weight': 1.2,\n            },\n            {\n                'name': 'YOLOv9-E-converted',\n                'url': 'https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-e-converted.pt',\n                'weight': 1.3,\n            }\n        ]\n        \n        for model_config in yolov9_models:\n            model_name = model_config['name']\n            \n            def download_and_load():\n                try:\n                    # Download model\n                    response = requests.get(model_config['url'], stream=True)\n                    response.raise_for_status()\n                    \n                    model_path = f\"./downloaded_models/{model_name}.pt\"\n                    os.makedirs(\"./downloaded_models\", exist_ok=True)\n                    \n                    with open(model_path, 'wb') as f:\n                        total_size = int(response.headers.get('content-length', 0))\n                        block_size = 8192\n                        with tqdm(total=total_size, unit='iB', unit_scale=True, desc=model_name) as pbar:\n                            for chunk in response.iter_content(chunk_size=block_size):\n                                pbar.update(len(chunk))\n                                f.write(chunk)\n                    \n                    # Load model\n                    if YOLO_AVAILABLE and 'converted' in model_name:\n                        return YOLO(model_path)\n                    else:\n                        return torch.load(model_path, map_location=self.device)\n                except:\n                    return None\n            \n            model = self.safe_model_load(download_and_load, model_name)\n            if model:\n                self.models.append(model)\n                self.model_info.append({\n                    'name': model_name,\n                    'type': 'yolo',\n                    'weight': model_config['weight'],\n                    'architecture': 'YOLOv9',\n                    'source': 'github'\n                })\n                print(f\"✓ Downloaded and loaded {model_name}\")\n    \n    def download_yolo_nas_models(self):\n        \"\"\"Download YOLO-NAS models using super-gradients\"\"\"\n        if not SG_AVAILABLE:\n            print(\"Skipping YOLO-NAS models - super-gradients not available\")\n            return\n            \n        print(\"\\n\" + \"=\"*60)\n        print(\"DOWNLOADING YOLO-NAS MODELS\")\n        print(\"=\"*60)\n        \n        yolo_nas_models = [\n            {'name': 'yolo_nas_s', 'weight': 0.9},\n            {'name': 'yolo_nas_m', 'weight': 1.0},\n            {'name': 'yolo_nas_l', 'weight': 1.1},\n        ]\n        \n        for model_config in yolo_nas_models:\n            model_name = model_config['name']\n            \n            def load_yolo_nas():\n                try:\n                    model = sg_models.get(model_name, pretrained_weights=\"coco\")\n                    return model\n                except:\n                    return None\n            \n            model = self.safe_model_load(load_yolo_nas, model_name)\n            if model:\n                self.models.append(model)\n                self.model_info.append({\n                    'name': model_name,\n                    'type': 'yolo-nas',\n                    'weight': model_config['weight'],\n                    'architecture': 'YOLO-NAS',\n                    'source': 'super-gradients'\n                })\n                print(f\"✓ Loaded {model_name}\")\n    \n    def download_specialized_hf_models(self):\n        \"\"\"Download specialized and fine-tuned models from HuggingFace\"\"\"\n        if not HF_AVAILABLE:\n            print(\"Skipping specialized HuggingFace models\")\n            return\n            \n        print(\"\\n\" + \"=\"*60)\n        print(\"DOWNLOADING SPECIALIZED HUGGINGFACE MODELS\")\n        print(\"=\"*60)\n        \n        specialized_models = [\n            # Domain-specific YOLO models\n            {\n                'repo': 'keremberke/yolov8n-forklift-detection',\n                'filename': 'best.pt',\n                'weight': 0.8,\n                'type': 'yolo'\n            },\n            {\n                'repo': 'keremberke/yolov8m-hard-hat-detection',\n                'filename': 'best.pt',\n                'weight': 0.9,\n                'type': 'yolo'\n            },\n            {\n                'repo': 'keremberke/yolov8s-traffic-sign-detection',\n                'filename': 'best.pt',\n                'weight': 0.85,\n                'type': 'yolo'\n            },\n            {\n                'repo': 'keremberke/yolov8n-pothole-segmentation',\n                'filename': 'best.pt',\n                'weight': 0.8,\n                'type': 'yolo'\n            },\n            {\n                'repo': 'keremberke/yolov8s-plane-detection',\n                'filename': 'best.pt',\n                'weight': 0.85,\n                'type': 'yolo'\n            },\n            {\n                'repo': 'mshamrai/yolov8x-visdrone',\n                'filename': 'yolov8x_visdrone.pt',\n                'weight': 1.0,\n                'type': 'yolo'\n            },\n            {\n                'repo': 'fcakyon/yolov5s-v7.0',\n                'filename': 'yolov5s.pt',\n                'weight': 0.8,\n                'type': 'yolo'\n            },\n            {\n                'repo': 'fcakyon/yolov5m-v7.0',\n                'filename': 'yolov5m.pt',\n                'weight': 0.9,\n                'type': 'yolo'\n            },\n            \n            # RT-DETR models\n            {\n                'repo': 'PekingU/rtdetr_r50vd_coco_o365',\n                'filename': 'model.pth',\n                'weight': 1.1,\n                'type': 'rtdetr'\n            },\n            {\n                'repo': 'PekingU/rtdetr_r101vd_coco_o365',\n                'filename': 'model.pth',\n                'weight': 1.2,\n                'type': 'rtdetr'\n            },\n            \n            # Transformer models\n            {\n                'repo': 'jozhang97/deta-resnet-50',\n                'filename': None,\n                'weight': 1.1,\n                'type': 'transformer'\n            },\n            {\n                'repo': 'jozhang97/deta-swin-large',\n                'filename': None,\n                'weight': 1.2,\n                'type': 'transformer'\n            },\n            {\n                'repo': 'SenseTime/deformable-detr-with-box-refine',\n                'filename': None,\n                'weight': 1.3,\n                'type': 'transformer'\n            },\n            {\n                'repo': 'SenseTime/deformable-detr-with-box-refine-two-stage',\n                'filename': None,\n                'weight': 1.35,\n                'type': 'transformer'\n            }\n        ]\n        \n        for model_config in specialized_models:\n            repo = model_config['repo']\n            \n            def load_specialized():\n                try:\n                    if model_config['filename'] and model_config['type'] == 'yolo':\n                        # Download specific file for YOLO models\n                        model_path = hf_hub_download(\n                            repo_id=repo,\n                            filename=model_config['filename'],\n                            cache_dir='./hf_cache'\n                        )\n                        if YOLO_AVAILABLE:\n                            return YOLO(model_path)\n                        else:\n                            return torch.load(model_path, map_location=self.device)\n                    else:\n                        # Load transformer models\n                        if model_config['type'] == 'transformer':\n                            return pipeline(\"object-detection\", model=repo, device=0 if self.device == 'cuda' else -1)\n                        else:\n                            # Generic loading\n                            model = AutoModelForObjectDetection.from_pretrained(repo)\n                            return model\n                except:\n                    return None\n            \n            model = self.safe_model_load(load_specialized, repo, max_retries=2)\n            if model:\n                self.models.append(model)\n                self.model_info.append({\n                    'name': repo.split('/')[-1],\n                    'type': 'specialized',\n                    'subtype': model_config['type'],\n                    'weight': model_config['weight'],\n                    'architecture': model_config['type'].upper(),\n                    'source': 'huggingface'\n                })\n                print(f\"✓ Loaded {repo}\")\n    \n    def download_efficientdet_models(self):\n        \"\"\"Download EfficientDet models\"\"\"\n        if not TIMM_AVAILABLE:\n            print(\"Skipping EfficientDet models - TIMM not available\")\n            return\n            \n        print(\"\\n\" + \"=\"*60)\n        print(\"DOWNLOADING EFFICIENTDET MODELS\")\n        print(\"=\"*60)\n        \n        efficientdet_models = [\n            {'name': 'tf_efficientdet_d0', 'weight': 0.8},\n            {'name': 'tf_efficientdet_d1', 'weight': 0.85},\n            {'name': 'tf_efficientdet_d2', 'weight': 0.9},\n            {'name': 'tf_efficientdet_d3', 'weight': 0.95},\n            {'name': 'tf_efficientdet_d4', 'weight': 1.0},\n            {'name': 'tf_efficientdet_d5', 'weight': 1.05},\n            {'name': 'tf_efficientdet_d6', 'weight': 1.1},\n            {'name': 'tf_efficientdet_d7', 'weight': 1.15},\n            {'name': 'tf_efficientdetv2_s', 'weight': 0.9},\n            {'name': 'tf_efficientdetv2_m', 'weight': 1.0},\n            {'name': 'tf_efficientdetv2_l', 'weight': 1.1},\n        ]\n        \n        for model_config in efficientdet_models:\n            model_name = model_config['name']\n            \n            def load_efficientdet():\n                try:\n                    model = timm.create_model(\n                        model_name,\n                        pretrained=True,\n                        checkpoint_path='',\n                        num_classes=1000,\n                        features_only=False\n                    )\n                    model.eval()\n                    return model\n                except:\n                    return None\n            \n            model = self.safe_model_load(load_efficientdet, model_name)\n            if model:\n                self.models.append(model)\n                self.model_info.append({\n                    'name': model_name,\n                    'type': 'efficientdet',\n                    'weight': model_config['weight'],\n                    'architecture': 'EfficientDet',\n                    'source': 'timm'\n                })\n                print(f\"✓ Loaded {model_name}\")\n    \n    def auto_discover_and_rank_models(self, limit=15):\n        \"\"\"Automatically discover and rank models from HuggingFace\"\"\"\n        if not HF_AVAILABLE:\n            return\n            \n        print(\"\\n\" + \"=\"*60)\n        print(\"AUTO-DISCOVERING AND RANKING MODELS\")\n        print(\"=\"*60)\n        \n        try:\n            # Search for object detection models\n            detection_models = list(list_models(\n                filter=[\"object-detection\"],\n                sort=\"downloads\",\n                direction=-1,\n                limit=limit * 2  # Get more to filter\n            ))\n            \n            # Rank models based on multiple criteria\n            ranked_models = []\n            for model in detection_models:\n                model_id = model.modelId\n                \n                # Skip test/demo models\n                if any(skip in model_id.lower() for skip in ['demo', 'test', 'example', 'tutorial']):\n                    continue\n                \n                # Calculate ranking score\n                score = 0\n                \n                # Downloads factor\n                downloads = getattr(model, 'downloads', 0)\n                score += min(downloads / 10000, 10)  # Normalize\n                \n                # Likes factor\n                likes = getattr(model, 'likes', 0)\n                score += min(likes / 100, 5)\n                \n                # Model size factor (prefer balanced sizes)\n                try:\n                    tags = getattr(model, 'tags', [])\n                    if 'yolo' in str(tags).lower():\n                        score += 3\n                    if 'detr' in str(tags).lower():\n                        score += 2\n                    if 'transformer' in str(tags).lower():\n                        score += 2\n                except:\n                    pass\n                \n                ranked_models.append((model_id, score))\n            \n            # Sort by score\n            ranked_models.sort(key=lambda x: x[1], reverse=True)\n            \n            # Load top models\n            for model_id, score in ranked_models[:limit]:\n                if len(self.models) >= 50:  # Total model limit\n                    break\n                \n                def load_discovered():\n                    try:\n                        return pipeline(\n                            \"object-detection\", \n                            model=model_id, \n                            device=0 if self.device == 'cuda' else -1\n                        )\n                    except:\n                        return None\n                \n                pipe = self.safe_model_load(load_discovered, model_id, max_retries=1)\n                if pipe:\n                    self.models.append(pipe)\n                    self.model_info.append({\n                        'name': model_id.split('/')[-1],\n                        'type': 'discovered',\n                        'weight': 0.8 + (score / 20),  # Dynamic weight based on ranking\n                        'architecture': 'Unknown',\n                        'source': 'huggingface-auto',\n                        'ranking_score': score\n                    })\n                    print(f\"✓ Auto-discovered {model_id} (score: {score:.2f})\")\n                    \n        except Exception as e:\n            print(f\"Model discovery failed: {e}\")\n    \n    def setup_self_tuning(self, validation_data=None):\n        \"\"\"Setup self-tuning with validation data\"\"\"\n        if not self.enable_self_tuning:\n            return\n            \n        print(\"\\n\" + \"=\"*60)\n        print(\"SETTING UP SELF-TUNING\")\n        print(\"=\"*60)\n        \n        if validation_data:\n            # Evaluate models on validation data\n            self._evaluate_models_on_validation(validation_data)\n            \n        # Initialize adaptive weights based on performance\n        self._initialize_adaptive_weights()\n        \n        # Optimize ensemble parameters if Optuna available\n        if OPTUNA_AVAILABLE and validation_data:\n            self._optimize_ensemble_parameters(validation_data)\n    \n    def _evaluate_models_on_validation(self, validation_data):\n        \"\"\"Evaluate each model on validation data\"\"\"\n        print(\"Evaluating models on validation data...\")\n        \n        for idx, (model, info) in enumerate(zip(self.models, self.model_info)):\n            model_name = info['name']\n            total_predictions = 0\n            successful_predictions = 0\n            \n            # Sample evaluation on subset\n            for img_path, ground_truth in validation_data[:10]:  # Sample\n                try:\n                    predictions = self.predict_with_model(model, cv2.imread(img_path), info)\n                    if predictions:\n                        successful_predictions += 1\n                    total_predictions += 1\n                except:\n                    pass\n            \n            # Update performance metrics\n            if total_predictions > 0:\n                success_rate = successful_predictions / total_predictions\n                self.model_performance[model_name]['mAP'] = success_rate\n                self.model_performance[model_name]['predictions'] = total_predictions\n                self.model_performance[model_name]['successes'] = successful_predictions\n    \n    def _initialize_adaptive_weights(self):\n        \"\"\"Initialize adaptive weights based on model performance\"\"\"\n        for info in self.model_info:\n            model_name = info['name']\n            base_weight = info['weight']\n            \n            # Adjust weight based on performance\n            performance = self.model_performance.get(model_name, {})\n            mAP = performance.get('mAP', 0.5)\n            \n            # Adaptive weight = base_weight * performance_factor\n            performance_factor = 0.5 + mAP  # Range: 0.5 to 1.5\n            self.adaptive_weights[model_name] = base_weight * performance_factor\n            \n        print(f\"Initialized adaptive weights for {len(self.adaptive_weights)} models\")\n    \n    def _optimize_ensemble_parameters(self, validation_data):\n        \"\"\"Optimize ensemble parameters using Optuna\"\"\"\n        print(\"Optimizing ensemble parameters...\")\n        \n        def objective(trial):\n            # Suggest parameters\n            params = {\n                'iou_threshold': trial.suggest_float('iou_threshold', 0.3, 0.7),\n                'conf_threshold': trial.suggest_float('conf_threshold', 0.0001, 0.01),\n                'sigma': trial.suggest_float('sigma', 0.3, 0.7),\n                'skip_box_threshold': trial.suggest_float('skip_box_threshold', 0.0001, 0.01)\n            }\n            \n            # Update ensemble parameters\n            self.ensemble_params.update(params)\n            \n            # Evaluate on validation subset\n            score = 0\n            for img_path, ground_truth in validation_data[:5]:  # Small subset\n                predictions = self.ensemble_predictions(img_path, strategy='adaptive')\n                # Simple scoring based on number of predictions\n                if predictions:\n                    score += len(predictions) / 100  # Normalize\n            \n            return score\n        \n        study = optuna.create_study(direction='maximize')\n        study.optimize(objective, n_trials=20, timeout=60)  # Quick optimization\n        \n        # Update with best parameters\n        self.ensemble_params.update(study.best_params)\n        print(f\"Optimized parameters: {study.best_params}\")\n    \n    def predict_with_model(self, model, image, model_info):\n        \"\"\"Unified prediction interface with performance tracking\"\"\"\n        predictions = []\n        start_time = time.time()\n        \n        try:\n            # Update attempt counter\n            model_name = model_info['name']\n            self.model_performance[model_name]['predictions'] += 1\n            \n            # Get predictions based on model type\n            if model_info['type'] in ['yolo', 'yolo-nas'] or (\n                model_info['type'] in ['specialized', 'local', 'public'] and hasattr(model, 'predict')\n            ):\n                # YOLO-style prediction\n                if model_info['type'] == 'yolo-nas':\n                    # YOLO-NAS specific prediction\n                    results = model.predict(image, conf=self.ensemble_params['conf_threshold'])\n                    predictions = self._extract_yolo_nas_boxes(results)\n                else:\n                    # Standard YOLO prediction\n                    results = model.predict(\n                        image, \n                        conf=self.ensemble_params['conf_threshold'], \n                        device=self.device, \n                        verbose=False\n                    )\n                    if results and len(results) > 0:\n                        predictions = self._extract_yolo_boxes(results[0])\n                        \n            elif model_info['type'] == 'transformer' or isinstance(model, dict):\n                # Transformer model prediction\n                predictions = self._predict_with_transformer(model, image, model_info)\n                \n            elif model_info['type'] == 'efficientdet':\n                # EfficientDet prediction\n                predictions = self._predict_with_efficientdet(model, image, model_info)\n                \n            elif hasattr(model, '__call__'):\n                # Generic callable model\n                pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))\n                results = model(pil_image)\n                predictions = self._extract_transformer_boxes(results, image.shape)\n            \n            # Update success counter\n            if predictions:\n                self.model_performance[model_name]['successes'] += 1\n                \n            # Update speed metric\n            elapsed_time = time.time() - start_time\n            self.model_performance[model_name]['speed'] = elapsed_time\n            \n        except Exception as e:\n            # Update failure counter\n            self.model_performance[model_info['name']]['failures'] = \\\n                self.model_performance[model_info['name']].get('failures', 0) + 1\n                \n            if self.model_performance[model_info['name']]['failures'] % 10 == 0:\n                print(f\"Model {model_info['name']} has failed {self.model_performance[model_info['name']]['failures']} times\")\n        \n        return predictions\n    \n    def _predict_with_transformer(self, model, image, model_info):\n        \"\"\"Handle transformer model predictions\"\"\"\n        predictions = []\n        \n        if isinstance(model, dict) and 'pipeline' in model:\n            # Pipeline interface\n            pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))\n            results = model['pipeline'](pil_image)\n            predictions = self._extract_transformer_boxes(results, image.shape)\n            \n        elif isinstance(model, dict) and 'model' in model:\n            # Model + processor interface\n            pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))\n            inputs = model['processor'](images=pil_image, return_tensors=\"pt\")\n            \n            # Move inputs to device\n            inputs = {k: v.to(self.device) if isinstance(v, torch.Tensor) else v \n                     for k, v in inputs.items()}\n            \n            with torch.no_grad():\n                outputs = model['model'](**inputs)\n                \n            # Post-process\n            target_sizes = torch.tensor([image.shape[:2]]).to(self.device)\n            results = model['processor'].post_process_object_detection(\n                outputs, \n                threshold=self.ensemble_params['conf_threshold'], \n                target_sizes=target_sizes\n            )[0]\n            \n            predictions = self._extract_transformer_boxes_raw(results, image.shape)\n            \n        return predictions\n    \n    def _predict_with_efficientdet(self, model, image, model_info):\n        \"\"\"Handle EfficientDet predictions\"\"\"\n        # This is a placeholder - actual implementation would depend on the specific model\n        # For now, return empty predictions\n        return []\n    \n    def _extract_yolo_boxes(self, results):\n        \"\"\"Extract boxes from YOLO results\"\"\"\n        boxes = []\n        if results.boxes is None or len(results.boxes) == 0:\n            return boxes\n            \n        width, height = results.orig_shape[1], results.orig_shape[0]\n        \n        for box in results.boxes:\n            try:\n                cls = int(box.cls.cpu().numpy().item())\n                conf = float(box.conf.cpu().numpy().item())\n                x_center_abs, y_center_abs, w_abs, h_abs = box.xywh[0].cpu().numpy()\n                \n                # Normalize coordinates\n                x_center = x_center_abs / width\n                y_center = y_center_abs / height\n                box_width = w_abs / width\n                box_height = h_abs / height\n                \n                # Validate box\n                if all(0 <= v <= 1 for v in [x_center, y_center, box_width, box_height]):\n                    boxes.append([cls, conf, x_center, y_center, box_width, box_height])\n            except:\n                continue\n                \n        return boxes\n    \n    def _extract_yolo_nas_boxes(self, results):\n        \"\"\"Extract boxes from YOLO-NAS results\"\"\"\n        boxes = []\n        \n        try:\n            # YOLO-NAS returns a different format\n            if hasattr(results, 'prediction'):\n                pred = results.prediction\n                for i in range(len(pred.bboxes_xyxy)):\n                    bbox = pred.bboxes_xyxy[i]\n                    conf = pred.confidence[i]\n                    cls = pred.labels[i]\n                    \n                    # Convert to center format and normalize\n                    x_center = (bbox[0] + bbox[2]) / 2 / pred.image_shape[1]\n                    y_center = (bbox[1] + bbox[3]) / 2 / pred.image_shape[0]\n                    box_width = (bbox[2] - bbox[0]) / pred.image_shape[1]\n                    box_height = (bbox[3] - bbox[1]) / pred.image_shape[0]\n                    \n                    boxes.append([int(cls), float(conf), x_center, y_center, box_width, box_height])\n        except:\n            pass\n            \n        return boxes\n    \n    def _extract_transformer_boxes(self, results, image_shape):\n        \"\"\"Extract boxes from transformer pipeline results\"\"\"\n        boxes = []\n        height, width = image_shape[:2]\n        \n        for detection in results:\n            try:\n                # Get bounding box\n                bbox = detection.get('box', {})\n                x_min = bbox.get('xmin', 0)\n                y_min = bbox.get('ymin', 0)\n                x_max = bbox.get('xmax', width)\n                y_max = bbox.get('ymax', height)\n                \n                # Convert to center format and normalize\n                x_center = (x_min + x_max) / 2 / width\n                y_center = (y_min + y_max) / 2 / height\n                box_width = (x_max - x_min) / width\n                box_height = (y_max - y_min) / height\n                \n                # Get class and confidence\n                label = detection.get('label', 'object')\n                score = detection.get('score', 0.5)\n                \n                # Map label to class id\n                cls_id = self._label_to_class_id(label)\n                \n                if all(0 <= v <= 1 for v in [x_center, y_center, box_width, box_height]):\n                    boxes.append([cls_id, score, x_center, y_center, box_width, box_height])\n            except:\n                continue\n                \n        return boxes\n    \n    def _extract_transformer_boxes_raw(self, results, image_shape):\n        \"\"\"Extract boxes from raw transformer outputs\"\"\"\n        boxes = []\n        height, width = image_shape[:2]\n        \n        try:\n            # Handle both CPU and GPU tensors\n            scores = results['scores'].cpu().numpy() if hasattr(results['scores'], 'cpu') else results['scores']\n            labels = results['labels'].cpu().numpy() if hasattr(results['labels'], 'cpu') else results['labels']\n            boxes_data = results['boxes'].cpu().numpy() if hasattr(results['boxes'], 'cpu') else results['boxes']\n            \n            for score, label, box in zip(scores, labels, boxes_data):\n                if score > self.ensemble_params['conf_threshold']:\n                    x_min, y_min, x_max, y_max = box\n                    \n                    # Convert to center format and normalize\n                    x_center = (x_min + x_max) / 2 / width\n                    y_center = (y_min + y_max) / 2 / height\n                    box_width = (x_max - x_min) / width\n                    box_height = (y_max - y_min) / height\n                    \n                    if all(0 <= v <= 1 for v in [x_center, y_center, box_width, box_height]):\n                        boxes.append([int(label), float(score), x_center, y_center, box_width, box_height])\n        except Exception as e:\n            print(f\"Error extracting transformer boxes: {e}\")\n            \n        return boxes\n    \n    def _label_to_class_id(self, label):\n        \"\"\"Convert string label to class ID\"\"\"\n        # This is a simplified mapping - in practice, you'd want a proper COCO class mapping\n        if isinstance(label, str):\n            # Use hash for consistent mapping\n            return hash(label) % 80  # Assuming 80 COCO classes\n        return int(label)\n    \n    def apply_test_time_augmentation(self, image, model, model_info):\n        \"\"\"Apply Test Time Augmentation\"\"\"\n        all_predictions = []\n        \n        # Original prediction\n        original_pred = self.predict_with_model(model, image, model_info)\n        all_predictions.extend(original_pred)\n        \n        if not ALBUMENTATIONS_AVAILABLE or not self.tta_transforms:\n            return all_predictions\n        \n        # TTA predictions\n        augmentations = [\n            # Horizontal flip\n            lambda img: cv2.flip(img, 1),\n            # Vertical flip  \n            lambda img: cv2.flip(img, 0),\n            # Rotate 90\n            lambda img: cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE),\n            # Rotate 270\n            lambda img: cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE),\n            # Brightness adjustment\n            lambda img: cv2.convertScaleAbs(img, alpha=1.2, beta=10),\n            lambda img: cv2.convertScaleAbs(img, alpha=0.8, beta=-10),\n        ]\n        \n        for aug_func in augmentations[:3]:  # Limit TTA for speed\n            try:\n                aug_image = aug_func(image)\n                aug_pred = self.predict_with_model(model, aug_image, model_info)\n                \n                # Reverse transform boxes if needed\n                if aug_func == augmentations[0]:  # Horizontal flip\n                    for pred in aug_pred:\n                        pred[2] = 1 - pred[2]  # Flip x coordinate\n                elif aug_func == augmentations[1]:  # Vertical flip\n                    for pred in aug_pred:\n                        pred[3] = 1 - pred[3]  # Flip y coordinate\n                \n                all_predictions.extend(aug_pred)\n            except:\n                continue\n        \n        return all_predictions\n    \n    # Advanced Ensemble Strategies\n    \n    def _weighted_boxes_fusion(self, predictions_by_class):\n        \"\"\"Standard Weighted Boxes Fusion\"\"\"\n        final_predictions = []\n        \n        for cls, class_preds in predictions_by_class.items():\n            if not class_preds:\n                continue\n                \n            boxes, scores, labels = self._prepare_boxes_for_ensemble(class_preds, cls)\n            \n            try:\n                boxes_out, scores_out, labels_out = weighted_boxes_fusion(\n                    [boxes], [scores], [labels],\n                    weights=None, \n                    iou_thr=self.ensemble_params['iou_threshold'],\n                    skip_box_thr=self.ensemble_params['skip_box_threshold']\n                )\n                \n                final_predictions.extend(\n                    self._convert_from_corner_format(boxes_out, scores_out, labels_out)\n                )\n            except:\n                final_predictions.extend(class_preds)\n                \n        return final_predictions\n    \n    def _soft_nms(self, predictions_by_class):\n        \"\"\"Soft-NMS ensemble\"\"\"\n        final_predictions = []\n        \n        for cls, class_preds in predictions_by_class.items():\n            if not class_preds:\n                continue\n                \n            boxes, scores, labels = self._prepare_boxes_for_ensemble(class_preds, cls)\n            \n            try:\n                boxes_out, scores_out, labels_out = soft_nms(\n                    [boxes], [scores], [labels],\n                    weights=None,\n                    iou_thr=self.ensemble_params['iou_threshold'],\n                    sigma=self.ensemble_params['sigma'],\n                    thresh=self.ensemble_params['skip_box_threshold']\n                )\n                \n                final_predictions.extend(\n                    self._convert_from_corner_format(boxes_out, scores_out, labels_out)\n                )\n            except:\n                final_predictions.extend(class_preds)\n                \n        return final_predictions\n    \n    def _standard_nms(self, predictions_by_class):\n        \"\"\"Standard NMS\"\"\"\n        final_predictions = []\n        \n        for cls, class_preds in predictions_by_class.items():\n            if not class_preds:\n                continue\n                \n            boxes, scores, labels = self._prepare_boxes_for_ensemble(class_preds, cls)\n            \n            try:\n                boxes_out, scores_out, labels_out = nms(\n                    [boxes], [scores], [labels],\n                    weights=None,\n                    iou_thr=self.ensemble_params['iou_threshold']\n                )\n                \n                final_predictions.extend(\n                    self._convert_from_corner_format(boxes_out, scores_out, labels_out)\n                )\n            except:\n                final_predictions.extend(class_preds)\n                \n        return final_predictions\n    \n    def _non_maximum_weighted(self, predictions_by_class):\n        \"\"\"Non-Maximum Weighted ensemble\"\"\"\n        final_predictions = []\n        \n        for cls, class_preds in predictions_by_class.items():\n            if not class_preds:\n                continue\n                \n            boxes, scores, labels = self._prepare_boxes_for_ensemble(class_preds, cls)\n            \n            try:\n                boxes_out, scores_out, labels_out = non_maximum_weighted(\n                    [boxes], [scores], [labels],\n                    weights=None,\n                    iou_thr=self.ensemble_params['iou_threshold'],\n                    skip_box_thr=self.ensemble_params['skip_box_threshold']\n                )\n                \n                final_predictions.extend(\n                    self._convert_from_corner_format(boxes_out, scores_out, labels_out)\n                )\n            except:\n                final_predictions.extend(class_preds)\n                \n        return final_predictions\n    \n    def _adaptive_ensemble(self, predictions_by_class):\n        \"\"\"Adaptive ensemble using model performance\"\"\"\n        final_predictions = []\n        \n        for cls, class_preds in predictions_by_class.items():\n            if not class_preds:\n                continue\n            \n            # Group predictions by model\n            model_predictions = defaultdict(list)\n            for pred in class_preds:\n                # Identify model (simplified - you'd track this properly)\n                model_id = pred[1] % len(self.models)  # Hack to identify model\n                model_predictions[model_id].append(pred)\n            \n            # Apply adaptive weights\n            weighted_preds = []\n            for model_id, preds in model_predictions.items():\n                model_name = self.model_info[model_id]['name'] if model_id < len(self.model_info) else 'unknown'\n                adaptive_weight = self.adaptive_weights.get(model_name, 1.0)\n                \n                for pred in preds:\n                    weighted_pred = pred.copy()\n                    weighted_pred[1] *= adaptive_weight  # Apply adaptive weight\n                    weighted_preds.append(weighted_pred)\n            \n            # Use WBF on weighted predictions\n            boxes, scores, labels = self._prepare_boxes_for_ensemble(weighted_preds, cls)\n            \n            try:\n                boxes_out, scores_out, labels_out = weighted_boxes_fusion(\n                    [boxes], [scores], [labels],\n                    weights=None,\n                    iou_thr=self.ensemble_params['iou_threshold'],\n                    skip_box_thr=self.ensemble_params['skip_box_threshold']\n                )\n                \n                final_predictions.extend(\n                    self._convert_from_corner_format(boxes_out, scores_out, labels_out)\n                )\n            except:\n                final_predictions.extend(weighted_preds)\n                \n        return final_predictions\n    \n    def _cascade_ensemble(self, predictions_by_class):\n        \"\"\"Cascade ensemble - process high confidence first\"\"\"\n        final_predictions = []\n        confidence_stages = [0.9, 0.7, 0.5, 0.3, 0.1]\n        \n        for cls, class_preds in predictions_by_class.items():\n            if not class_preds:\n                continue\n            \n            remaining_preds = class_preds.copy()\n            stage_results = []\n            \n            for conf_threshold in confidence_stages:\n                # Extract high confidence predictions\n                high_conf = [p for p in remaining_preds if p[1] >= conf_threshold]\n                low_conf = [p for p in remaining_preds if p[1] < conf_threshold]\n                \n                if high_conf:\n                    # Process high confidence predictions\n                    boxes, scores, labels = self._prepare_boxes_for_ensemble(high_conf, cls)\n                    \n                    try:\n                        boxes_out, scores_out, labels_out = weighted_boxes_fusion(\n                            [boxes], [scores], [labels],\n                            weights=None,\n                            iou_thr=self.ensemble_params['iou_threshold'] * (1 + 0.2 * (1 - conf_threshold)),\n                            skip_box_thr=conf_threshold * 0.5\n                        )\n                        \n                        stage_results.extend(\n                            self._convert_from_corner_format(boxes_out, scores_out, labels_out)\n                        )\n                    except:\n                        stage_results.extend(high_conf)\n                \n                remaining_preds = low_conf\n                if not remaining_preds:\n                    break\n            \n            final_predictions.extend(stage_results)\n            \n        return final_predictions\n    \n    def _class_specific_ensemble(self, predictions_by_class):\n        \"\"\"Use different strategies for different classes\"\"\"\n        final_predictions = []\n        \n        # Define class-specific strategies (customize based on your classes)\n        class_strategies = {\n            0: 'wbf',      # person\n            1: 'soft_nms',  # bicycle\n            2: 'wbf',      # car\n            3: 'soft_nms',  # motorcycle\n            # ... add more as needed\n        }\n        \n        for cls, class_preds in predictions_by_class.items():\n            if not class_preds:\n                continue\n            \n            # Get strategy for this class\n            strategy = class_strategies.get(cls, 'wbf')\n            \n            # Apply appropriate strategy\n            if strategy == 'wbf':\n                preds = self._weighted_boxes_fusion({cls: class_preds})\n            elif strategy == 'soft_nms':\n                preds = self._soft_nms({cls: class_preds})\n            else:\n                preds = self._standard_nms({cls: class_preds})\n            \n            final_predictions.extend(preds)\n            \n        return final_predictions\n    \n    def _size_aware_ensemble(self, predictions_by_class):\n        \"\"\"Different handling for small/medium/large objects\"\"\"\n        final_predictions = []\n        \n        for cls, class_preds in predictions_by_class.items():\n            if not class_preds:\n                continue\n            \n            # Categorize by size\n            small_preds = []\n            medium_preds = []\n            large_preds = []\n            \n            for pred in class_preds:\n                area = pred[4] * pred[5]  # width * height\n                if area < 0.01:  # Small objects\n                    small_preds.append(pred)\n                elif area < 0.1:  # Medium objects\n                    medium_preds.append(pred)\n                else:  # Large objects\n                    large_preds.append(pred)\n            \n            # Process each size category with appropriate parameters\n            for preds, size_factor in [(small_preds, 0.7), (medium_preds, 1.0), (large_preds, 1.3)]:\n                if not preds:\n                    continue\n                \n                boxes, scores, labels = self._prepare_boxes_for_ensemble(preds, cls)\n                \n                try:\n                    boxes_out, scores_out, labels_out = weighted_boxes_fusion(\n                        [boxes], [scores], [labels],\n                        weights=None,\n                        iou_thr=self.ensemble_params['iou_threshold'] * size_factor,\n                        skip_box_thr=self.ensemble_params['skip_box_threshold']\n                    )\n                    \n                    final_predictions.extend(\n                        self._convert_from_corner_format(boxes_out, scores_out, labels_out)\n                    )\n                except:\n                    final_predictions.extend(preds)\n                    \n        return final_predictions\n    \n    def _hybrid_ensemble(self, predictions_by_class):\n        \"\"\"Hybrid approach combining multiple strategies\"\"\"\n        # Get predictions from multiple strategies\n        wbf_preds = self._weighted_boxes_fusion(predictions_by_class)\n        soft_nms_preds = self._soft_nms(predictions_by_class)\n        adaptive_preds = self._adaptive_ensemble(predictions_by_class)\n        \n        # Combine all predictions\n        all_predictions = wbf_preds + soft_nms_preds + adaptive_preds\n        \n        # Group by class again\n        combined_by_class = defaultdict(list)\n        for pred in all_predictions:\n            combined_by_class[int(pred[0])].append(pred)\n        \n        # Final fusion\n        return self._weighted_boxes_fusion(combined_by_class)\n    \n    def _prepare_boxes_for_ensemble(self, predictions, cls):\n        \"\"\"Prepare boxes for ensemble methods\"\"\"\n        boxes = []\n        scores = []\n        labels = []\n        \n        for pred in predictions:\n            _, score, x_center, y_center, width, height = pred\n            \n            # Convert to corner format\n            x1 = max(0, x_center - width/2)\n            y1 = max(0, y_center - height/2)\n            x2 = min(1, x_center + width/2)\n            y2 = min(1, y_center + height/2)\n            \n            boxes.append([x1, y1, x2, y2])\n            scores.append(score)\n            labels.append(cls)\n            \n        return boxes, scores, labels\n    \n    def _convert_from_corner_format(self, boxes, scores, labels):\n        \"\"\"Convert from corner format back to center format\"\"\"\n        predictions = []\n        \n        for box, score, label in zip(boxes, scores, labels):\n            x1, y1, x2, y2 = box\n            x_center = (x1 + x2) / 2\n            y_center = (y1 + y2) / 2\n            width = x2 - x1\n            height = y2 - y1\n            \n            predictions.append([int(label), score, x_center, y_center, width, height])\n            \n        return predictions\n    \n    def ensemble_predictions(self, image_path, strategy='adaptive', use_tta=False):\n        \"\"\"Enhanced ensemble prediction with multiple strategies\"\"\"\n        # Read image\n        if isinstance(image_path, str):\n            image = cv2.imread(image_path)\n        else:\n            image = image_path\n            \n        if image is None:\n            return []\n        \n        all_predictions = []\n        \n        # Collect predictions from all models\n        for model, info in zip(self.models, self.model_info):\n            try:\n                # Check if model should be skipped based on performance\n                if self.enable_self_tuning:\n                    model_name = info['name']\n                    perf = self.model_performance.get(model_name, {})\n                    failure_rate = perf.get('failures', 0) / max(perf.get('predictions', 1), 1)\n                    \n                    # Skip models with high failure rate\n                    if failure_rate > 0.8 and perf.get('predictions', 0) > 10:\n                        continue\n                \n                # Get predictions (with or without TTA)\n                if use_tta:\n                    predictions = self.apply_test_time_augmentation(image, model, info)\n                else:\n                    predictions = self.predict_with_model(model, image, info)\n                \n                # Apply model-specific adjustments\n                class_offset = info.get('class_offset', 0)\n                weight = self.adaptive_weights.get(info['name'], info['weight'])\n                \n                for pred in predictions:\n                    pred[0] += class_offset\n                    pred[1] *= weight\n                    \n                all_predictions.extend(predictions)\n                \n            except Exception as e:\n                continue\n        \n        if not all_predictions:\n            return []\n        \n        # Apply ensemble strategy\n        predictions_by_class = defaultdict(list)\n        for pred in all_predictions:\n            cls = int(pred[0])\n            predictions_by_class[cls].append(pred)\n        \n        # Get ensemble function\n        ensemble_func = self.ensemble_strategies.get(strategy, self._adaptive_ensemble)\n        \n        # Apply ensemble\n        final_predictions = ensemble_func(predictions_by_class)\n        \n        # Post-process with class-specific thresholds\n        if self.enable_self_tuning:\n            filtered_predictions = []\n            for pred in final_predictions:\n                cls = int(pred[0])\n                conf = pred[1]\n                threshold = self.class_specific_thresholds.get(cls, self.ensemble_params['conf_threshold'])\n                if conf >= threshold:\n                    filtered_predictions.append(pred)\n            final_predictions = filtered_predictions\n        \n        return final_predictions\n    \n    def create_enhanced_submission(self, test_images_path, output_csv=\"submission.csv\", \n                                 strategy='adaptive', use_tta=False):\n        \"\"\"Create submission with enhanced features\"\"\"\n        test_images_dir = Path(test_images_path)\n        image_files = sorted([f for f in test_images_dir.glob(\"*\") \n                             if f.suffix.lower() in ['.png', '.jpg', '.jpeg']])\n        \n        print(f\"\\n{'='*60}\")\n        print(f\"Creating Enhanced Submission\")\n        print(f\"Active Models: {len(self.models)}\")\n        print(f\"Failed Models: {len(self.failed_models)}\")\n        print(f\"Images: {len(image_files)}\")\n        print(f\"Strategy: {strategy}\")\n        print(f\"TTA: {'Enabled' if use_tta else 'Disabled'}\")\n        print(f\"Self-Tuning: {'Enabled' if self.enable_self_tuning else 'Disabled'}\")\n        print(f\"{'='*60}\")\n        \n        # Show top performing models\n        if self.enable_self_tuning:\n            print(\"\\nTop Performing Models:\")\n            sorted_models = sorted(\n                [(name, perf['mAP']) for name, perf in self.model_performance.items()],\n                key=lambda x: x[1],\n                reverse=True\n            )\n            for name, mAP in sorted_models[:10]:\n                print(f\"- {name}: mAP={mAP:.3f}\")\n        \n        predictions_data = []\n        \n        # Process images with progress bar\n        for img_path in tqdm(image_files, desc=\"Processing images\"):\n            try:\n                predictions = self.ensemble_predictions(\n                    str(img_path), \n                    strategy=strategy, \n                    use_tta=use_tta\n                )\n                \n                # Format predictions\n                if predictions:\n                    pred_strings = []\n                    for pred in predictions:\n                        cls_id, conf, x_center, y_center, width, height = pred\n                        # Ensure valid values\n                        if all(0 <= v <= 1 for v in [x_center, y_center, width, height]):\n                            pred_strings.append(\n                                f\"{int(cls_id)} {conf:.6f} {x_center:.6f} {y_center:.6f} \"\n                                f\"{width:.6f} {height:.6f}\"\n                            )\n                    prediction_string = \" \".join(pred_strings) if pred_strings else \"no detections\"\n                else:\n                    prediction_string = \"no detections\"\n                \n            except Exception as e:\n                print(f\"\\nError processing {img_path.name}: {str(e)}\")\n                prediction_string = \"no detections\"\n            \n            predictions_data.append({\n                \"image_id\": img_path.stem,\n                \"prediction_string\": prediction_string\n            })\n            \n            # Periodic garbage collection\n            if len(predictions_data) % 100 == 0:\n                gc.collect()\n                if torch.cuda.is_available():\n                    torch.cuda.empty_cache()\n        \n        # Create submission DataFrame\n        submission_df = pd.DataFrame(predictions_data)\n        submission_df.to_csv(output_csv, index=False)\n        \n        print(f\"\\n✓ Submission saved to {output_csv}\")\n        print(f\"Shape: {submission_df.shape}\")\n        \n        # Statistics\n        with_detections = (submission_df['prediction_string'] != 'no detections').sum()\n        without_detections = (submission_df['prediction_string'] == 'no detections').sum()\n        \n        print(f\"\\nDetection Statistics:\")\n        print(f\"- Images with detections: {with_detections} ({with_detections/len(submission_df)*100:.1f}%)\")\n        print(f\"- Images without detections: {without_detections}\")\n        \n        # Performance summary\n        if self.enable_self_tuning:\n            print(\"\\nModel Performance Summary:\")\n            for name, perf in list(self.model_performance.items())[:10]:\n                success_rate = perf['successes'] / max(perf['predictions'], 1)\n                avg_speed = perf.get('speed', 0)\n                print(f\"- {name}: Success={success_rate:.2%}, Speed={avg_speed:.3f}s\")\n        \n        return submission_df\n    \n    def load_all_models(self, local_models=None):\n        \"\"\"Load all available models\"\"\"\n        print(\"=\"*60)\n        print(\"ENHANCED MULTI-SOURCE OBJECT DETECTION ENSEMBLE\")\n        print(\"=\"*60)\n        \n        # 1. Enhanced YOLO models\n        self.download_enhanced_yolo_models()\n        \n        # 2. Advanced Transformer models\n        self.download_advanced_transformer_models()\n        \n        # 3. YOLOv9 models\n        self.download_yolov9_models()\n        \n        # 4. YOLO-NAS models\n        self.download_yolo_nas_models()\n        \n        # 5. Specialized HuggingFace models\n        self.download_specialized_hf_models()\n        \n        # 6. EfficientDet models\n        self.download_efficientdet_models()\n        \n        # 7. Auto-discover and rank models\n        self.auto_discover_and_rank_models(limit=10)\n        \n        # 8. Local models (if any)\n        if local_models:\n            self.load_local_models(local_models)\n        \n        # Summary\n        print(f\"\\n{'='*60}\")\n        print(f\"MODEL LOADING COMPLETE\")\n        print(f\"Successfully loaded: {len(self.models)} models\")\n        print(f\"Failed to load: {len(self.failed_models)} models\")\n        print(f\"{'='*60}\")\n        \n        # Setup self-tuning if enabled\n        if self.enable_self_tuning:\n            self.setup_self_tuning()\n    \n    def load_local_models(self, model_paths):\n        \"\"\"Load local pre-trained models\"\"\"\n        print(\"\\n\" + \"=\"*60)\n        print(\"LOADING LOCAL MODELS\")\n        print(\"=\"*60)\n        \n        for path_config in model_paths:\n            path = path_config.get('path', '')\n            \n            if not os.path.exists(path):\n                print(f\"✗ Path not found: {path}\")\n                continue\n            \n            def load_local():\n                try:\n                    # Try YOLO first\n                    if YOLO_AVAILABLE and path.endswith('.pt'):\n                        return YOLO(path)\n                    # Try generic PyTorch\n                    else:\n                        return torch.load(path, map_location=self.device)\n                except:\n                    return None\n            \n            model = self.safe_model_load(load_local, path)\n            if model:\n                self.models.append(model)\n                self.model_info.append({\n                    'name': os.path.basename(path),\n                    'type': 'local',\n                    'weight': path_config.get('weight', 1.0),\n                    'architecture': 'Unknown',\n                    'source': 'local',\n                    'class_offset': path_config.get('class_offset', 0)\n                })\n                print(f\"✓ Loaded {path}\")\n\n\ndef main():\n    \"\"\"Main execution with enhanced features\"\"\"\n    # Initialize enhanced ensemble\n    ensemble = EnhancedObjectDetectionEnsemble(enable_self_tuning=True)\n    \n    # Local models (if available)\n    local_models = [\n        {'path': '/kaggle/input/2-top-models/pytorch/default/1/habijabii.pt', 'weight': 1.2, 'class_offset': 1},\n        {'path': '/kaggle/input/2-top-models/pytorch/default/1/nadiatriki.pt', 'weight': 1.2, 'class_offset': 0}\n    ]\n    \n    # Load all models\n    ensemble.load_all_models(local_models)\n    \n    # Test images path\n    test_images_path = '/kaggle/input/multi-class-object-detection-challenge/testImages/images'\n    \n    # Create submissions with different strategies\n    strategies = [\n        ('adaptive', True),   # Adaptive with TTA\n        ('hybrid', True),     # Hybrid with TTA\n        ('cascade', False),   # Cascade without TTA\n        ('wbf', False),      # Standard WBF\n    ]\n    \n    for strategy, use_tta in strategies:\n        print(f\"\\n{'='*60}\")\n        print(f\"Creating submission with {strategy.upper()} strategy (TTA: {use_tta})\")\n        print(f\"{'='*60}\")\n        \n        output_name = f\"submission_{strategy}_{'tta' if use_tta else 'no_tta'}.csv\"\n        \n        submission_df = ensemble.create_enhanced_submission(\n            test_images_path=test_images_path,\n            output_csv=output_name,\n            strategy=strategy,\n            use_tta=use_tta\n        )\n    \n    print(\"\\n\" + \"=\"*60)\n    print(\"ENHANCED ENSEMBLE PROCESSING COMPLETE!\")\n    print(\"=\"*60)\n    \n    # Final recommendations\n    print(\"\\nRecommendations for best results:\")\n    print(\"1. Use 'adaptive' or 'hybrid' strategy with TTA enabled\")\n    print(\"2. Ensure GPU is available for faster processing\")\n    print(\"3. Consider running cross-validation to optimize parameters\")\n    print(\"4. Monitor model performance and remove consistently failing models\")\n    print(\"5. Fine-tune class-specific thresholds based on validation data\")\n\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T02:21:56.285794Z","iopub.execute_input":"2025-08-01T02:21:56.285974Z","iopub.status.idle":"2025-08-01T02:28:38.519767Z","shell.execute_reply.started":"2025-08-01T02:21:56.285957Z","shell.execute_reply":"2025-08-01T02:28:38.518898Z"}},"outputs":[],"execution_count":null}]}