k-lauren commited on
Commit
f0e6dfd
·
1 Parent(s): d7f0382

Add handler, requirements, and update preprocessor config for Inference Endpoints

Browse files

- Add requirements.txt with timm dependency (fixes ImportError on endpoint startup)
- Add custom handler.py for object detection inference
- Update preprocessor_config.json to use modern DetrImageProcessor format

Co-Authored-By: Claude Opus 4.6

Files changed (3) hide show
  1. handler.py +61 -0
  2. preprocessor_config.json +5 -3
  3. requirements.txt +1 -0
handler.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import io
3
+ from typing import Any, Dict, List
4
+
5
+ import torch
6
+ from PIL import Image
7
+ from transformers import AutoImageProcessor, AutoModelForObjectDetection
8
+
9
+
10
+ class EndpointHandler:
11
+ def __init__(self, path: str = ""):
12
+ self.processor = AutoImageProcessor.from_pretrained(path)
13
+ self.model = AutoModelForObjectDetection.from_pretrained(path)
14
+ self.model.eval()
15
+
16
+ def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
17
+ inputs = data.get("inputs", data)
18
+
19
+ # Handle base64-encoded image
20
+ if isinstance(inputs, str):
21
+ image_bytes = base64.b64decode(inputs)
22
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
23
+ elif isinstance(inputs, bytes):
24
+ image = Image.open(io.BytesIO(inputs)).convert("RGB")
25
+ elif isinstance(inputs, Image.Image):
26
+ image = inputs.convert("RGB")
27
+ else:
28
+ raise ValueError(
29
+ "Unsupported input type. Provide a base64-encoded image string or raw bytes."
30
+ )
31
+
32
+ # Run inference
33
+ with torch.no_grad():
34
+ encoded = self.processor(images=image, return_tensors="pt")
35
+ outputs = self.model(**encoded)
36
+
37
+ # Post-process: convert to bounding boxes
38
+ target_size = torch.tensor([image.size[::-1]]) # (height, width)
39
+ results = self.processor.post_process_object_detection(
40
+ outputs, threshold=0.5, target_sizes=target_size
41
+ )[0]
42
+
43
+ detections = []
44
+ for score, label, box in zip(
45
+ results["scores"], results["labels"], results["boxes"]
46
+ ):
47
+ xmin, ymin, xmax, ymax = box.tolist()
48
+ detections.append(
49
+ {
50
+ "score": round(score.item(), 4),
51
+ "label": self.model.config.id2label[label.item()],
52
+ "box": {
53
+ "xmin": round(xmin, 2),
54
+ "ymin": round(ymin, 2),
55
+ "xmax": round(xmax, 2),
56
+ "ymax": round(ymax, 2),
57
+ },
58
+ }
59
+ )
60
+
61
+ return detections
preprocessor_config.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "do_normalize": true,
3
  "do_resize": true,
4
- "feature_extractor_type": "DetrFeatureExtractor",
5
  "format": "coco_detection",
6
  "image_mean": [
7
  0.485,
@@ -13,6 +13,8 @@
13
  0.224,
14
  0.225
15
  ],
16
- "max_size": 800,
17
- "size": 800
 
 
18
  }
 
1
  {
2
  "do_normalize": true,
3
  "do_resize": true,
4
+ "image_processor_type": "DetrImageProcessor",
5
  "format": "coco_detection",
6
  "image_mean": [
7
  0.485,
 
13
  0.224,
14
  0.225
15
  ],
16
+ "size": {
17
+ "shortest_edge": 800,
18
+ "longest_edge": 800
19
+ }
20
  }
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ timm