Hey fix my code it inst detecting anymore here is the old one and the new one pleas fix the new one and all this is in one python file and keep all features as other threads hevely rely on this:

New one:
class YOLOEnsemble:
    _instance = None
    _lock = threading.Lock()

    def __init__(self, models_config, class_names=None):
        """
        models_config: List of tuples (model_path, imgsz)
        class_names: dict mapping class indices to names, e.g., {0: 'person'}
        """
        self.models = []  # [{'session':..., 'input_name':..., 'imgsz':...}, ...]
        self.names = class_names if class_names else {0: 'person'}
        self.cache_lock = threading.Lock()
        self._load_models(models_config)

    def _load_models(self, models_config):
        """Load ONNX models with DirectML execution provider."""
        for model_path, imgsz in models_config:
            try:
                onnx_path = model_path if model_path.endswith(".onnx") else model_path.replace('.pt', '.onnx')

                providers = ['DmlExecutionProvider']
                sess_options = ort.SessionOptions()
                sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL

                session = ort.InferenceSession(onnx_path, sess_options, providers=providers)
                input_name = session.get_inputs()[0].name

                self.models.append({
                    'session': session,
                    'input_name': input_name,
                    'imgsz': imgsz
                })

                logging.info(f"Loaded ONNX model with DirectML: {onnx_path} (imgsz={imgsz})")

            except Exception as e:
                logging.error(f"Failed to load ONNX model {model_path}: {e}")

    def preprocess(self, frame, imgsz):
        """
        Preprocess input image:
        - Resize to (imgsz, imgsz)
        - Normalize to [0,1]
        - Convert HWC to CHW
        - Add batch dimension
        """
        resized = cv2.resize(frame, (imgsz, imgsz), interpolation=cv2.INTER_LINEAR)
        img = resized.astype(np.float32) / 255.0
        img = np.transpose(img, (2, 0, 1))
        img = np.expand_dims(img, axis=0)
        img = np.ascontiguousarray(img)
        return img

    def fast_nms(self, boxes, scores, iou_threshold=0.25):
        """
        Performs non-maximum suppression (NMS) on boxes given scores,
        returns indices to keep.
        """
        if len(boxes) == 0:
            return []

        boxes = np.array(boxes, dtype=np.float32)
        scores = np.array(scores, dtype=np.float32)

        indices = np.argsort(scores)[::-1]
        keep = []

        while indices.size > 0:
            current = indices[0]
            keep.append(current)
            if indices.size == 1:
                break

            remaining = indices[1:]
            current_box = boxes[current]
            remaining_boxes = boxes[remaining]

            iou = self._fast_iou(current_box, remaining_boxes)
            indices = remaining[iou <= iou_threshold]

        return keep

    def _fast_iou(self, box, boxes):
        """
        Vectorized IoU calculation between one box and multiple boxes.
        """
        x1 = np.maximum(box[0], boxes[:, 0])
        y1 = np.maximum(box[1], boxes[:, 1])
        x2 = np.minimum(box[2], boxes[:, 2])
        y2 = np.minimum(box[3], boxes[:, 3])

        inter_area = np.maximum(0, x2 - x1) * np.maximum(0, y2 - y1)

        box_area = (box[2] - box[0]) * (box[3] - box[1])
        boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])

        union = box_area + boxes_area - inter_area
        return inter_area / np.maximum(union, 1e-10)

    def postprocess(self, outputs, conf_thres=0.3, iou_threshold=0.25, max_det=40):
        """
        Postprocess raw ONNX outputs.

        Expected output shape:
          (batch_size=1, N, 6) [x1, y1, x2, y2, confidence, class_id]

        Returns:
          boxes: (M, 4) numpy array of bbox coordinates
          scores: (M,) numpy array of confidences
          classes: (M,) numpy array of class indices
        """
        preds = outputs[0]  # Usually outputs is a list: [output_tensor]
        preds = np.squeeze(preds, axis=0)  # (N, 6)

        if preds.shape[0] == 0:
            return np.empty((0, 4)), np.empty((0,)), np.empty((0,), dtype=np.int32)

        # Filter by confidence threshold
        conf_mask = preds[:, 4] > conf_thres
        preds = preds[conf_mask]

        if preds.shape[0] == 0:
            return np.empty((0, 4)), np.empty((0,)), np.empty((0,), dtype=np.int32)

        boxes = preds[:, :4]
        scores = preds[:, 4]
        classes = preds[:, 5].astype(np.int32)

        # Apply NMS
        keep = self.fast_nms(boxes, scores, iou_threshold)
        if len(keep) > max_det:
            keep = keep[:max_det]

        return boxes[keep], scores[keep], classes[keep]

    def predict(self, frame, conf_thres=0.3):
        """
        Perform inference on input frame with all ensemble models.

        Returns a list of DummyResults compatible with detection thread.

        Each DummyResults contains:
          - boxes.xyxy (torch.FloatTensor)
          - boxes.conf (torch.FloatTensor)
          - boxes.cls (torch.FloatTensor)
          - scale_x, scale_y, original_width, original_height
        """
        all_results = []
        original_height, original_width = frame.shape[:2]

        for model_info in self.models:
            session = model_info['session']
            input_name = model_info['input_name']
            imgsz = model_info['imgsz']

            # Prepare input tensor for ONNX
            input_tensor = self.preprocess(frame, imgsz)

            try:
                # ONNX Runtime inference
                outputs = session.run(None, {input_name: input_tensor})

                boxes, scores, classes = self.postprocess(
                    outputs,
                    conf_thres=conf_thres,
                    iou_threshold=0.25,
                    max_det=40
                )

                if boxes.shape[0] == 0:
                    continue

                # Scale boxes from [imgsz,imgsz] back to original image size
                scale_x = original_width / imgsz
                scale_y = original_height / imgsz

                boxes[:, [0, 2]] *= scale_x
                boxes[:, [1, 3]] *= scale_y

                # Convert to torch tensors for compatibility with detection thread
                boxes_torch = torch.from_numpy(boxes).float()
                scores_torch = torch.from_numpy(scores).float()
                classes_torch = torch.from_numpy(classes).float()

                class DummyBoxes:
                    def __init__(self, xyxy, conf, cls):
                        self.xyxy = xyxy
                        self.conf = conf
                        self.cls = cls

                class DummyResults:
                    def __init__(self, boxes, scale_x, scale_y, ow, oh):
                        self.boxes = boxes
                        self.scale_x = scale_x
                        self.scale_y = scale_y
                        self.original_width = ow
                        self.original_height = oh

                dummy_boxes = DummyBoxes(boxes_torch, scores_torch, classes_torch)
                dummy_results = DummyResults(dummy_boxes, scale_x, scale_y, original_width, original_height)

                all_results.append(dummy_results)

            except Exception as e:
                logging.warning(f"ONNX DirectML inference failed: {e}")
                continue

        return all_results

    @classmethod
    def get_instance(cls, models_config, class_names=None):
        with cls._lock:
            if cls._instance is None:
                cls._instance = YOLOEnsemble(models_config, class_names)
            return cls._instance

class DetectionThread(QtCore.QThread):
    detection_signal = QtCore.pyqtSignal(list, list, list, list, list)

    def __init__(self, settings):
        super().__init__()
        self.settings = settings
        self.running = False
        self.frame_lock = threading.Lock()

        # BitBlt setup variables
        self.user32 = ctypes.windll.user32
        self.gdi32 = ctypes.windll.gdi32
        self.dc = None
        self.memdc = None
        self.bitmap = None
        self.bitmapinfo = None

        # Tracker
        self.tracker = Sort(max_age=10, min_hits=1, iou_threshold=0.2)
        self.frame_skip = 0
        self.last_detection_time = 0

        # Screen info
        self.screen_width = 0
        self.screen_height = 0
        self.center_x = 0
        self.center_y = 0

        model_paths = [
            ("models/yolo11n.onnx", 256),
        ]

        # ONNX + DirectML model ensemble singleton instance
        # from YOLOEnsemble import YOLOEnsemble  # adjust import as needed
        self.yolo_ensemble = YOLOEnsemble.get_instance(model_paths)

    def setup_bitblt(self):
        try:
            hwnd = self.user32.GetDesktopWindow()
            self.dc = self.user32.GetDC(hwnd)
            self.memdc = self.gdi32.CreateCompatibleDC(self.dc)

            class BITMAPINFOHEADER(ctypes.Structure):
                _fields_ = [
                    ("biSize", ctypes.c_uint32),
                    ("biWidth", ctypes.c_int),
                    ("biHeight", ctypes.c_int),
                    ("biPlanes", ctypes.c_short),
                    ("biBitCount", ctypes.c_short),
                    ("biCompression", ctypes.c_uint32),
                    ("biSizeImage", ctypes.c_uint32),
                    ("biXPelsPerMeter", ctypes.c_int),
                    ("biYPelsPerMeter", ctypes.c_int),
                    ("biClrUsed", ctypes.c_uint32),
                    ("biClrImportant", ctypes.c_uint32)
                ]
            self.bitmapinfo = BITMAPINFOHEADER()
            self.bitmapinfo.biSize = ctypes.sizeof(BITMAPINFOHEADER)
            self.bitmapinfo.biPlanes = 1
            self.bitmapinfo.biBitCount = 32
            self.bitmapinfo.biCompression = 0

            return True
        except Exception as e:
            logging.error(f"BitBlt setup failed: {e}")
            return False

    def capture_screen_region(self, x, y, width, height):
        try:
            self.bitmapinfo.biWidth = width
            self.bitmapinfo.biHeight = -height  # top-down bitmap
            self.bitmapinfo.biSizeImage = width * height * 4

            self.bitmap = self.gdi32.CreateCompatibleBitmap(self.dc, width, height)
            self.gdi32.SelectObject(self.memdc, self.bitmap)

            if not self.gdi32.BitBlt(self.memdc, 0, 0, width, height,
                                     self.dc, x, y, 0x00CC0020):
                raise Exception("BitBlt failed")

            buf_size = width * height * 4
            buffer = (ctypes.c_byte * buf_size)()

            self.gdi32.GetDIBits(self.memdc, self.bitmap, 0, height, buffer,
                                 ctypes.byref(self.bitmapinfo), 0)

            array = np.frombuffer(buffer, dtype=np.uint8)
            array = array.reshape(height, width, 4)

            self.gdi32.DeleteObject(self.bitmap)
            self.bitmap = None

            return array

        except Exception as e:
            logging.error(f"BitBlt capture failed: {e}")
            return None

    def cleanup_bitblt(self):
        try:
            if self.bitmap:
                self.gdi32.DeleteObject(self.bitmap)
                self.bitmap = None
            if self.memdc:
                self.gdi32.DeleteDC(self.memdc)
                self.memdc = None
            if self.dc:
                self.user32.ReleaseDC(None, self.dc)
                self.dc = None
        except Exception as e:
            logging.error(f"BitBlt cleanup failed: {e}")

    def fast_nms(self, boxes, scores, iou_threshold=0.25):
        if len(boxes) == 0:
            return []
        boxes = np.asarray(boxes, dtype=np.float32)
        scores = np.asarray(scores, dtype=np.float32)
        indices = np.argsort(scores)[::-1]

        keep = []
        while indices.size > 0:
            current = indices[0]
            keep.append(current)
            if indices.size == 1:
                break
            remaining = indices[1:]
            current_box = boxes[current]
            remaining_boxes = boxes[remaining]

            x1 = np.maximum(current_box[0], remaining_boxes[:, 0])
            y1 = np.maximum(current_box[1], remaining_boxes[:, 1])
            x2 = np.minimum(current_box[2], remaining_boxes[:, 2])
            y2 = np.minimum(current_box[3], remaining_boxes[:, 3])

            inter_area = np.maximum(0, x2 - x1) * np.maximum(0, y2 - y1)
            current_area = (current_box[2] - current_box[0]) * (current_box[3] - current_box[1])
            rem_area = (remaining_boxes[:, 2] - remaining_boxes[:, 0]) * (remaining_boxes[:, 3] - remaining_boxes[:, 1])
            union = current_area + rem_area - inter_area
            iou = inter_area / np.maximum(union, 1e-10)

            indices = remaining[iou <= iou_threshold]
        return keep

    def transform_coordinates(self, boxes, crop_x, crop_y, scale_x, scale_y):
        transformed = []
        for box in boxes:
            x1, y1, x2, y2 = box
            x1_screen = int(x1 * scale_x + crop_x)
            y1_screen = int(y1 * scale_y + crop_y)
            x2_screen = int(x2 * scale_x + crop_x)
            y2_screen = int(y2 * scale_y + crop_y)
            transformed.append((x1_screen, y1_screen, x2_screen, y2_screen))
        return transformed

    def run(self):
        self.running = True

        if not self.yolo_ensemble.models:
            logging.error("No models loaded")
            return

        if not self.setup_bitblt():
            logging.error("BitBlt setup failed")
            return

        self.screen_width = self.user32.GetSystemMetrics(0)
        self.screen_height = self.user32.GetSystemMetrics(1)
        self.center_x = self.screen_width >> 1
        self.center_y = self.screen_height >> 1

        logging.info(f"BitBlt detection started: {self.screen_width}x{self.screen_height}")

        last_time = time.perf_counter()

        try:
            while self.running:
                current_time = time.perf_counter()

                fov_radius = clamp(self.settings.get('fov_radius', 150), 50,
                                   min(self.screen_width, self.screen_height) >> 1)

                crop_x = max(self.center_x - fov_radius, 0)
                crop_y = max(self.center_y - fov_radius, 0)
                crop_width = min(fov_radius * 2, self.screen_width - crop_x)
                crop_height = min(fov_radius * 2, self.screen_height - crop_y)

                if crop_width < 100 or crop_height < 100:
                    continue

                frame = self.capture_screen_region(crop_x, crop_y, crop_width, crop_height)
                if frame is None or frame.size == 0:
                    continue

                frame = np.ascontiguousarray(frame)
                frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2RGB)

                conf_thres = clamp(self.settings.get('confidence', 0.5), 0.2, 0.95)

                all_results = self.yolo_ensemble.predict(frame, conf_thres=conf_thres)

                detections = []
                for results in all_results:
                    # Access torch tensors as numpy for processing
                    if not hasattr(results, 'boxes') or not results.boxes.xyxy.shape[0]:
                        continue

                    boxes = results.boxes.xyxy.cpu().numpy()
                    confs = results.boxes.conf.cpu().numpy()
                    classes = results.boxes.cls.cpu().numpy()

                    scale_x = getattr(results, 'scale_x', 1.0)
                    scale_y = getattr(results, 'scale_y', 1.0)

                    screen_boxes = self.transform_coordinates(boxes, crop_x, crop_y, scale_x, scale_y)

                    for i, (box, conf, cls) in enumerate(zip(screen_boxes, confs, classes)):
                        if conf < conf_thres:
                            continue
                        x1_s, y1_s, x2_s, y2_s = box
                        if x1_s >= x2_s or y1_s >= y2_s or x1_s < 0 or y1_s < 0 or x2_s > self.screen_width or y2_s > self.screen_height:
                            continue
                        class_id = int(cls)
                        class_name = self.yolo_ensemble.names.get(class_id, "person")

                        # Distance check from center for FOV radius
                        det_center_x = (x1_s + x2_s) >> 1
                        det_center_y = (y1_s + y2_s) >> 1
                        dx = det_center_x - self.center_x
                        dy = det_center_y - self.center_y
                        dist_sq = dx*dx + dy*dy

                        if dist_sq <= fov_radius * fov_radius:
                            detections.append((x1_s, y1_s, x2_s, y2_s, float(conf), class_name))

                # Sort detections by confidence
                detections.sort(key=lambda x: x[4], reverse=True)

                boxes = [(d[0], d[1], d[2], d[3]) for d in detections]
                confidences = [d[4] for d in detections]
                labels = [d[5] for d in detections]

                # Apply NMS with your fast_nms (optional if you trust YOLO output NMS)
                if boxes:
                    selected_indices = self.fast_nms(boxes, confidences, iou_threshold=0.25)
                    boxes = [boxes[i] for i in selected_indices]
                    labels = [labels[i] for i in selected_indices]
                    confidences = [confidences[i] for i in selected_indices]

                dets_for_sort = np.array([[*box, conf] for box, conf in zip(boxes, confidences)]) if boxes else np.empty((0,5))

                tracked_objects = self.tracker.update(dets_for_sort)

                tracked_boxes = []
                track_ids = []

                for obj in tracked_objects:
                    x1t, y1t, x2t, y2t, track_id = obj
                    x1t, y1t, x2t, y2t = map(int, (x1t, y1t, x2t, y2t))
                    if x1t < x2t and y1t < y2t and 0 <= x1t < self.screen_width and 0 <= y1t < self.screen_height and x2t <= self.screen_width and y2t <= self.screen_height:
                        tracked_boxes.append((x1t, y1t, x2t, y2t))
                        track_ids.append(int(track_id))

                self.detection_signal.emit(tracked_boxes, labels, confidences, labels, track_ids)

                self.frame_skip += 1
                if self.frame_skip >= 200:
                    fps = 1.0 / max(0.001, current_time - last_time)
                    logging.info(f"BitBlt Performance - FPS: {fps:.1f}, Detections: {len(tracked_boxes)}")
                    self.frame_skip = 0

                last_time = current_time

        except Exception as e:
            logging.error(f"Detection error: {e}")

        finally:
            self.cleanup_bitblt()

    def stop(self):
        self.running = False
        self.cleanup_bitblt()
        logging.info("BitBlt detection stopped")


def clamp(value, min_val, max_val):
    return min_val if value < min_val else max_val if value > max_val else value

Old one:
class YOLOEnsemble:
    _instance = None
    _lock = threading.Lock()

    def __init__(self, models_config):
        self.models = []
        self.names = {}
        self.prediction_cache = {}
        self.cache_lock = threading.Lock()
        self.device = self._setup_directml_device()
        self._load_models(models_config)

    def _setup_directml_device(self):
        """Setup DirectML for maximum AMD GPU performance with fallbacks"""
        try:
            # Try DirectML first
            if torch_directml.is_available():
                device_count = torch_directml.device_count()
                logging.info(f"DirectML available with {device_count} device(s)")
                
                if device_count > 0:
                    # Get the best DirectML device (usually 0)
                    dml_device = torch_directml.device(0)
                    
                    # Maximum DirectML optimizations
                    torch.backends.directml.enabled = True
                    torch.backends.directml.allow_reduced_precision = True
                    
                    logging.info(f"DirectML device initialized: {dml_device}")
                    return dml_device
            
            # Fallback to CUDA if available
            if torch.cuda.is_available():
                cuda_device = torch.device('cuda:0')
                logging.info(f"CUDA device initialized: {cuda_device}")
                return cuda_device
            
            # Final fallback to CPU with warning
            logging.warning("No GPU available, falling back to CPU (performance will be reduced)")
            return torch.device('cpu')
                
        except Exception as e:
            logging.warning(f"GPU setup failed ({e}), falling back to CPU")
            return torch.device('cpu')

    def _load_models(self, models_config):
        """Load models with DirectML optimization for maximum performance"""
        for model_path, imgsz in models_config:
            try:
                # Load YOLO model
                model = YOLO(model_path)
                
                # Force model to DirectML device for 100% GPU utilization
                model.model = model.model.to(self.device)
                
                # Set model to evaluation mode and optimize
                model.model.eval()
                
                # Enable DirectML optimizations
                if hasattr(torch.backends, 'directml') and torch.backends.directml.enabled:
                    # Compile model for maximum DirectML performance
                    try:
                        model.model = torch.jit.optimize_for_inference(model.model)
                        logging.info(f"Model JIT optimized for DirectML: {model_path}")
                    except:
                        logging.info(f"JIT optimization skipped for: {model_path}")
                
                # Zero-latency warmup with DirectML - more aggressive
                dummy = torch.zeros((1, 3, imgsz, imgsz), dtype=torch.float32, device=self.device)
                
                with torch.no_grad():
                    # More aggressive warmup - 15 iterations for maximum DirectML optimization
                    for _ in range(15):
                        try:
                            _ = model.model(dummy)
                        except:
                            # Fallback warmup using predict
                            dummy_np = np.zeros((imgsz, imgsz, 3), dtype=np.uint8)
                            model.predict(
                                source=dummy_np,
                                imgsz=imgsz,
                                device=self.device,
                                verbose=False,
                                stream=False
                            )
                            break
                
                # Clear cache for maximum performance (if DirectML available)
                if hasattr(torch, 'directml') and str(self.device).startswith('privateuseone'):
                    torch.directml.empty_cache()
                elif torch.cuda.is_available() and self.device.type == 'cuda':
                    torch.cuda.empty_cache()
                
                self.models.append((model, imgsz))
                if hasattr(model, "names"):
                    self.names.update(model.names)
                    
                logging.info(f"DirectML model loaded: {model_path} -> {imgsz}px on {self.device}")
                
            except Exception as e:
                logging.error(f"Failed to load model {model_path}: {e}")

    def predict(self, frame, conf_thres=0.3):
        """Ultra-high performance DirectML prediction with zero latency"""
        all_results = []
        original_height, original_width = frame.shape[:2]
        
        # Process all models with DirectML for maximum GPU utilization
        with torch.no_grad():  # Disable gradients for inference speed
            for model, imgsz in self.models:
                try:
                    # Calculate scale factors for coordinate transformation
                    scale_x = original_width / imgsz
                    scale_y = original_height / imgsz
                    
                    # Ultra-fast resize with optimized interpolation
                    resized_frame = cv2.resize(frame, (imgsz, imgsz), interpolation=cv2.INTER_LINEAR)
                    
                    # Maximum performance prediction with DirectML
                    results = model.predict(
                        source=resized_frame,
                        save=False,
                        imgsz=imgsz,
                        conf=conf_thres,
                        iou=0.15,  # More aggressive NMS for speed
                        device=self.device,
                        half=False,  # DirectML works better with float32
                        verbose=False,
                        stream=False,
                        agnostic_nms=True,
                        max_det=40,  # Slightly higher detection count
                        classes=[0],  # Person class only
                        retina_masks=False
                    )[0]
                    
                    # Store transformation data
                    results.scale_x = scale_x
                    results.scale_y = scale_y
                    results.original_width = original_width
                    results.original_height = original_height
                    
                    all_results.append(results)
                    
                except Exception as e:
                    logging.warning(f"DirectML prediction failed: {e}")
                    continue
        
        # No cache clearing to maintain maximum GPU utilization
        # Only clear if memory is getting full
        if hasattr(torch, 'directml') and str(self.device).startswith('privateuseone'):
            pass  # Keep DirectML memory allocated
        elif torch.cuda.is_available() and self.device.type == 'cuda':
            pass  # Keep CUDA memory allocated
        return all_results

    @classmethod
    def get_instance(cls, models_config):
        with cls._lock:
            if cls._instance is None:
                cls._instance = YOLOEnsemble(models_config)
            return cls._instance

def clamp(value, min_val, max_val):
    """Zero-overhead clamp function"""
    return min_val if value < min_val else max_val if value > max_val else value

class DetectionThread(QtCore.QThread):
    detection_signal = QtCore.pyqtSignal(list, list, list, list, list)

    def __init__(self, settings):
        super().__init__()
        self.settings = settings
        self.running = False
        self.frame_lock = threading.Lock()
        
        # BitBlt setup
        self.user32 = ctypes.windll.user32
        self.gdi32 = ctypes.windll.gdi32
        self.dc = None
        self.memdc = None
        self.bitmap = None
        self.bitmapinfo = None
        
        # Ultra-fast tracker with minimal overhead
        self.tracker = Sort(max_age=10, min_hits=1, iou_threshold=0.2)
        self.frame_skip = 0
        self.last_detection_time = 0
        
        # Screen cache
        self.screen_width = 0
        self.screen_height = 0
        self.center_x = 0
        self.center_y = 0        # Load DirectML optimized models
        model_paths = [
            ("models/BestMain.pt", 96),
            ("models/yolo11n.pt", 128)
        ]
        

        self.yolo_ensemble = YOLOEnsemble.get_instance(model_paths)

    def setup_bitblt(self):
        """Initialize BitBlt capture resources"""
        try:
            # Get desktop window handle
            hwnd = self.user32.GetDesktopWindow()
            
            # Get DC and create compatible DC
            self.dc = self.user32.GetDC(hwnd)
            self.memdc = self.gdi32.CreateCompatibleDC(self.dc)
            
            # Create bitmap info structure
            class BITMAPINFOHEADER(ctypes.Structure):
                _fields_ = [
                    ("biSize", ctypes.c_uint32),
                    ("biWidth", ctypes.c_int),
                    ("biHeight", ctypes.c_int),
                    ("biPlanes", ctypes.c_short),
                    ("biBitCount", ctypes.c_short),
                    ("biCompression", ctypes.c_uint32),
                    ("biSizeImage", ctypes.c_uint32),
                    ("biXPelsPerMeter", ctypes.c_int),
                    ("biYPelsPerMeter", ctypes.c_int),
                    ("biClrUsed", ctypes.c_uint32),
                    ("biClrImportant", ctypes.c_uint32)
                ]

            self.bitmapinfo = BITMAPINFOHEADER()
            self.bitmapinfo.biSize = ctypes.sizeof(BITMAPINFOHEADER)
            self.bitmapinfo.biPlanes = 1
            self.bitmapinfo.biBitCount = 32
            self.bitmapinfo.biCompression = 0
            
            return True
            
        except Exception as e:
            logging.error(f"BitBlt setup failed: {e}")
            return False

    def capture_screen_region(self, x, y, width, height):
        """Capture screen region using BitBlt"""
        try:
            # Update bitmap info
            self.bitmapinfo.biWidth = width
            self.bitmapinfo.biHeight = -height  # Negative for top-down
            self.bitmapinfo.biSizeImage = width * height * 4
            
            # Create compatible bitmap
            self.bitmap = self.gdi32.CreateCompatibleBitmap(self.dc, width, height)
            self.gdi32.SelectObject(self.memdc, self.bitmap)
            
            # Perform BitBlt operation
            result = self.gdi32.BitBlt(
                self.memdc, 0, 0, width, height,
                self.dc, x, y,
                0x00CC0020  # SRCCOPY
            )
            
            if not result:
                raise Exception("BitBlt failed")
            
            # Get bitmap bits
            buffer_size = width * height * 4
            buffer = (ctypes.c_byte * buffer_size)()
            
            self.gdi32.GetDIBits(
                self.memdc, self.bitmap, 0, height,
                buffer, ctypes.byref(self.bitmapinfo), 0
            )
            
            # Convert to numpy array
            array = np.frombuffer(buffer, dtype=np.uint8)
            array = array.reshape(height, width, 4)
            
            # Cleanup current bitmap
            self.gdi32.DeleteObject(self.bitmap)
            
            return array
            
        except Exception as e:
            logging.error(f"BitBlt capture failed: {e}")
            return None

    def cleanup_bitblt(self):
        """Clean up BitBlt resources"""
        try:
            if self.bitmap:
                self.gdi32.DeleteObject(self.bitmap)
            if self.memdc:
                self.gdi32.DeleteDC(self.memdc)
            if self.dc:
                self.user32.ReleaseDC(None, self.dc)
        except Exception as e:
            logging.error(f"BitBlt cleanup failed: {e}")

    def fast_nms(self, boxes, scores, iou_threshold=0.25):
        """Ultra-fast NMS implementation with minimal overhead"""
        if len(boxes) == 0:
            return []

        # Convert to numpy arrays for vectorized operations
        boxes = np.asarray(boxes, dtype=np.float32)
        scores = np.asarray(scores, dtype=np.float32)

        # Get sorted indices by score (descending)
        indices = np.argsort(scores)[::-1]
        
        keep = []
        while indices.size > 0:
            # Take the highest scoring box
            current = indices[0]
            keep.append(current)
            
            if indices.size == 1:
                break
            
            # Calculate IoU with vectorized operations
            remaining_indices = indices[1:]
            current_box = boxes[current]
            remaining_boxes = boxes[remaining_indices]
            
            # Fast IoU calculation
            ious = self._fast_iou(current_box, remaining_boxes)
            
            # Keep only boxes with IoU below threshold
            indices = remaining_indices[ious <= iou_threshold]
        
        return keep
    
    def _fast_iou(self, box, boxes):
        """Vectorized IoU calculation with minimal overhead"""
        # Intersection coordinates
        x1 = np.maximum(box[0], boxes[:, 0])
        y1 = np.maximum(box[1], boxes[:, 1])
        x2 = np.minimum(box[2], boxes[:, 2])
        y2 = np.minimum(box[3], boxes[:, 3])
        
        # Intersection area
        intersection = np.maximum(0, x2 - x1) * np.maximum(0, y2 - y1)
        
        # Box areas
        box_area = (box[2] - box[0]) * (box[3] - box[1])
        boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
        
        # Union and IoU
        union = box_area + boxes_area - intersection
        return intersection / np.maximum(union, 1e-10)

    def transform_coordinates(self, boxes, crop_x, crop_y, scale_x, scale_y):
        """Zero-latency coordinate transformation"""
        transformed_boxes = []
        
        for box in boxes:
            x1, y1, x2, y2 = box
            
            # Scale and transform in one operation
            x1_screen = int(x1 * scale_x + crop_x)
            y1_screen = int(y1 * scale_y + crop_y)
            x2_screen = int(x2 * scale_x + crop_x)
            y2_screen = int(y2 * scale_y + crop_y)
            
            transformed_boxes.append((x1_screen, y1_screen, x2_screen, y2_screen))
            
        return transformed_boxes

    def run(self):
        """Main detection loop with BitBlt capture and DirectML"""
        self.running = True
        
        if not self.yolo_ensemble.models:
            logging.error("No models loaded")
            return
            
        if not self.setup_bitblt():
            logging.error("BitBlt setup failed")
            return

        # Get screen dimensions
        self.screen_width = self.user32.GetSystemMetrics(0)  # SM_CXSCREEN
        self.screen_height = self.user32.GetSystemMetrics(1)  # SM_CYSCREEN
        self.center_x = self.screen_width >> 1
        self.center_y = self.screen_height >> 1
        
        logging.info(f"BitBlt detection started: {self.screen_width}x{self.screen_height}")
        
        last_time = time.perf_counter()
        
        try:
            while self.running:
                current_time = time.perf_counter()
                
                # Calculate FOV and crop region
                fov_radius = clamp(self.settings.get('fov_radius', 150), 50,
                                 min(self.screen_width, self.screen_height) >> 1)
                
                crop_x = max(self.center_x - fov_radius, 0)
                crop_y = max(self.center_y - fov_radius, 0)
                crop_width = min(fov_radius * 2, self.screen_width - crop_x)
                crop_height = min(fov_radius * 2, self.screen_height - crop_y)
                
                if crop_width < 100 or crop_height < 100:
                    continue
                
                # Capture screen region using BitBlt
                frame = self.capture_screen_region(crop_x, crop_y, crop_width, crop_height)
                
                if frame is None or frame.size == 0:
                    continue
                
                # Convert color space
                frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2RGB)
                
                # Run detection with DirectML
                conf_thres = clamp(self.settings.get('confidence', 0.5), 0.2, 0.95)
                all_results = self.yolo_ensemble.predict(frame, conf_thres=conf_thres)
                
                # Process detections
                detections = []
                for results in all_results:
                    if hasattr(results, 'boxes') and len(results.boxes) > 0:
                        boxes = results.boxes.xyxy.cpu().numpy()
                        confs = results.boxes.conf.cpu().numpy()
                        classes = results.boxes.cls.cpu().numpy()
                        
                        scale_x = getattr(results, 'scale_x', 1.0)
                        scale_y = getattr(results, 'scale_y', 1.0)
                        
                        screen_boxes = self.transform_coordinates(
                            boxes, crop_x, crop_y, scale_x, scale_y
                        )
                        
                        for i, (screen_box, conf, cls) in enumerate(zip(screen_boxes, confs, classes)):
                            if conf < conf_thres:
                                continue
                                
                            x1_screen, y1_screen, x2_screen, y2_screen = screen_box
                            class_id = int(cls)
                            class_name = self.yolo_ensemble.names.get(class_id, "person")
                            
                            if (x1_screen >= x2_screen or y1_screen >= y2_screen or
                                x1_screen < 0 or y1_screen < 0 or
                                x2_screen > self.screen_width or y2_screen > self.screen_height):
                                continue
                                
                            det_center_x = (x1_screen + x2_screen) >> 1
                            det_center_y = (y1_screen + y2_screen) >> 1
                            
                            dx = det_center_x - self.center_x
                            dy = det_center_y - self.center_y
                            dist_squared = dx * dx + dy * dy
                            
                            if dist_squared <= fov_radius * fov_radius:
                                detections.append((
                                    x1_screen, y1_screen, x2_screen, y2_screen,
                                    float(conf), class_name
                                ))

                # Sort by confidence (highest first)
                detections.sort(key=lambda x: x[4], reverse=True)

                # Extract data for processing
                boxes = [(x1, y1, x2, y2) for (x1, y1, x2, y2, _, _) in detections]
                confidences = [conf for (_, _, _, _, conf, _) in detections]
                labels = [label for (_, _, _, _, _, label) in detections]

                # Apply fast NMS
                if boxes:
                    selected_indices = self.fast_nms(boxes, confidences, iou_threshold=0.25)
                    boxes = [boxes[i] for i in selected_indices]
                    labels = [labels[i] for i in selected_indices]
                    confidences = [confidences[i] for i in selected_indices]

                # Prepare tracking data
                dets_for_sort = np.array([
                    [*box, conf] for box, conf in zip(boxes, confidences)
                ]) if boxes else np.empty((0, 5))
                
                # Tracking
                tracked_objects = self.tracker.update(dets_for_sort)

                # Extract tracking results
                tracked_boxes = []
                track_ids = []
                
                for obj in tracked_objects:
                    x1t, y1t, x2t, y2t, track_id = obj
                    
                    x1t, y1t, x2t, y2t = int(x1t), int(y1t), int(x2t), int(y2t)
                    
                    if (x1t < x2t and y1t < y2t and 
                        x1t >= 0 and y1t >= 0 and 
                        x2t <= self.screen_width and y2t <= self.screen_height):
                        
                        tracked_boxes.append((x1t, y1t, x2t, y2t))
                        track_ids.append(int(track_id))

                # Emit detection signal
                self.detection_signal.emit(
                    tracked_boxes, labels, confidences, labels, track_ids
                )
                
                # Performance monitoring
                self.frame_skip += 1
                if self.frame_skip >= 200:
                    self.frame_skip = 0
                    fps = 1.0 / max(0.001, current_time - last_time)
                    logging.info(f"BitBlt Performance - FPS: {fps:.1f}, Detections: {len(tracked_boxes)}")
                
                last_time = current_time
                
        except Exception as e:
            logging.error(f"Detection error: {e}")
        
        finally:
            self.cleanup_bitblt()

    def stop(self):
        """Clean shutdown"""
        self.running = False
        self.cleanup_bitblt()
        logging.info("BitBlt detection stopped")