File size: 4,079 Bytes
0345feb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
from PIL import Image, ImageDraw, ImageFont
import io
import base64
from typing import List, Dict, Any, Optional, Tuple
import numpy as np
import cv2

class ImageProcessor:
    """Utility class for image processing operations"""
    
    @staticmethod
    def validate_image(image_bytes: bytes) -> bool:
        """Validate if the provided bytes represent a valid image"""
        try:
            image = Image.open(io.BytesIO(image_bytes))
            image.verify()
            return True
        except (IOError, SyntaxError):
            return False
    
    @staticmethod
    def convert_to_rgb(image_bytes: bytes) -> Image.Image:
        """Convert image bytes to RGB PIL Image"""
        image = Image.open(io.BytesIO(image_bytes))
        if image.mode != 'RGB':
            image = image.convert('RGB')
        return image
    
    @staticmethod
    def resize_image(image: Image.Image, max_size: Tuple[int, int] = (1024, 1024)) -> Image.Image:
        """Resize image while maintaining aspect ratio"""
        image.thumbnail(max_size, Image.Resampling.LANCZOS)
        return image
    
    @staticmethod
    def draw_bounding_boxes(
        image: Image.Image, 
        detections: List[Dict[str, Any]],
        confidence_threshold: float = 0.3
    ) -> Image.Image:
        """Draw bounding boxes and labels on the image"""
        draw = ImageDraw.Draw(image)
        
        # Try to load a font, fall back to default if not available
        try:
            font = ImageFont.truetype("arial.ttf", 16)
        except IOError:
            font = ImageFont.load_default()
        
        for detection in detections:
            if detection['score'] < confidence_threshold:
                continue
                
            bbox = detection['bounding_box']
            label = f"{detection['label']}: {detection['score']:.2f}"
            
            # Draw bounding box
            draw.rectangle(
                [(bbox['xmin'], bbox['ymin']), (bbox['xmax'], bbox['ymax'])],
                outline="red",
                width=3
            )
            
            # Draw label background
            text_bbox = draw.textbbox((0, 0), label, font=font)
            text_width = text_bbox[2] - text_bbox[0]
            text_height = text_bbox[3] - text_bbox[1]
            
            draw.rectangle(
                [(bbox['xmin'], bbox['ymin'] - text_height - 5), 
                 (bbox['xmin'] + text_width + 10, bbox['ymin'])],
                fill="red"
            )
            
            # Draw label text
            draw.text(
                (bbox['xmin'] + 5, bbox['ymin'] - text_height - 2),
                label,
                fill="white",
                font=font
            )
        
        return image
    
    @staticmethod
    def image_to_base64(image: Image.Image, format: str = "JPEG") -> str:
        """Convert PIL Image to base64 string"""
        buffered = io.BytesIO()
        image.save(buffered, format=format)
        img_str = base64.b64encode(buffered.getvalue()).decode()
        return f"data:image/{format.lower()};base64,{img_str}"
    
    @staticmethod
    def base64_to_image(base64_string: str) -> Image.Image:
        """Convert base64 string to PIL Image"""
        if ',' in base64_string:
            base64_string = base64_string.split(',')[1]
        
        image_data = base64.b64decode(base64_string)
        return Image.open(io.BytesIO(image_data))
    
    @staticmethod
    def get_image_info(image: Image.Image) -> Dict[str, Any]:
        """Get information about the image"""
        return {
            "format": image.format,
            "mode": image.mode,
            "size": image.size,
            "width": image.width,
            "height": image.height
        }
    
    @staticmethod
    def create_thumbnail(image: Image.Image, size: Tuple[int, int] = (200, 200)) -> Image.Image:
        """Create a thumbnail of the image"""
        return image.copy().thumbnail(size, Image.Resampling.LANCZOS)

# Global image processor instance
image_processor = ImageProcessor()