yuto090612 commited on
Commit
5f4c9f9
·
verified ·
1 Parent(s): 324867a

Upload src/predictor.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/predictor.py +114 -0
src/predictor.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Singleton predictor for SAM segmentation."""
2
+
3
+ import logging
4
+
5
+ import numpy as np
6
+ import torch
7
+ from PIL import Image
8
+ from transformers import SamModel, SamProcessor
9
+
10
+ from .model import load_model
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ MASK_COLOR = np.array([30, 100, 255, 100], dtype=np.uint8)
15
+ """Blue semi-transparent overlay color (RGBA)."""
16
+
17
+
18
+ class Predictor:
19
+ """Singleton wrapper around the SAM model for interactive segmentation.
20
+
21
+ Usage::
22
+
23
+ Predictor.initialize(device="cuda")
24
+ result = Predictor.predict(image, points=[[100, 200]], labels=[1])
25
+ """
26
+
27
+ _model: SamModel | None = None
28
+ _processor: SamProcessor | None = None
29
+ _device: str = "cpu"
30
+
31
+ @classmethod
32
+ def initialize(cls, device: str = "cuda") -> None:
33
+ """Load the SAM model onto the specified device.
34
+
35
+ This is idempotent; calling it twice with the same device is a no-op.
36
+
37
+ Args:
38
+ device: Target device (e.g., "cuda" or "cpu").
39
+ """
40
+ if cls._model is not None:
41
+ logger.info("Predictor already initialized; skipping.")
42
+ return
43
+
44
+ cls._device = device
45
+ cls._model, cls._processor = load_model(device)
46
+ logger.info("Predictor initialized on '%s'.", device)
47
+
48
+ @classmethod
49
+ def predict(
50
+ cls,
51
+ image: Image.Image,
52
+ points: list[list[int]],
53
+ labels: list[int],
54
+ ) -> Image.Image:
55
+ """Run SAM segmentation and return a mask-overlaid image.
56
+
57
+ Args:
58
+ image: Input PIL image.
59
+ points: Click coordinates as ``[[x, y], ...]``.
60
+ labels: Per-point labels (1 = foreground, 0 = background).
61
+
62
+ Returns:
63
+ A copy of the input image with a semi-transparent blue mask overlay.
64
+
65
+ Raises:
66
+ RuntimeError: If :meth:`initialize` has not been called.
67
+ """
68
+ if cls._model is None or cls._processor is None:
69
+ raise RuntimeError(
70
+ "Predictor is not initialized. Call Predictor.initialize() first."
71
+ )
72
+
73
+ # Default to center point if no points are provided
74
+ if not points or not labels:
75
+ cx = image.width // 2
76
+ cy = image.height // 2
77
+ points = [[cx, cy]]
78
+ labels = [1]
79
+ logger.info("No points provided; using image center (%d, %d).", cx, cy)
80
+
81
+ # Prepare inputs for the model
82
+ inputs = cls._processor(
83
+ image,
84
+ input_points=[points],
85
+ input_labels=[labels],
86
+ return_tensors="pt",
87
+ )
88
+ inputs = {k: v.to(cls._device) for k, v in inputs.items()}
89
+
90
+ # Run inference
91
+ with torch.no_grad():
92
+ outputs = cls._model(**inputs)
93
+
94
+ # Post-process masks
95
+ masks = cls._processor.image_processor.post_process_masks(
96
+ outputs.pred_masks.cpu(),
97
+ inputs["original_sizes"].cpu(),
98
+ inputs["reshaped_input_sizes"].cpu(),
99
+ )
100
+
101
+ # Pick the mask with the highest IoU score
102
+ iou_scores = outputs.iou_scores.cpu()
103
+ best_idx = int(iou_scores[0, 0].argmax())
104
+ mask = masks[0][0, best_idx].numpy().astype(bool)
105
+
106
+ # Create overlay
107
+ result = image.convert("RGBA")
108
+ overlay = Image.new("RGBA", result.size, (0, 0, 0, 0))
109
+ overlay_array = np.array(overlay)
110
+ overlay_array[mask] = MASK_COLOR
111
+ overlay = Image.fromarray(overlay_array)
112
+
113
+ composited = Image.alpha_composite(result, overlay)
114
+ return composited.convert("RGB")