File size: 6,995 Bytes
0bcf514 8640453 0bcf514 ebdf21a 0bcf514 067cfd6 ebdf21a 067cfd6 ebdf21a 0bcf514 8640453 0bcf514 067cfd6 0bcf514 ef7a0dc 0bcf514 ef7a0dc 067cfd6 8640453 0bcf514 8640453 0bcf514 8640453 0bcf514 ebdf21a 0bcf514 | 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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | """Minimal inference example for microsoft/resnet-50 INT8 using ExecuTorch.
Loads a quantized .pte model and runs image classification on a single image.
Outputs top-5 predictions with class labels and probabilities.
"""
import json
from pathlib import Path
import torch
from executorch.runtime import Runtime
from PIL import Image, ImageDraw, ImageFont
from torchvision import transforms
# ββ Configuration ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
MODEL_PATH = "resnet-50_raspberry_executorch_optimized.pte"
IMAGE_PATH = "sample_input.jpg"
INPUT_SIZE = (224, 224)
TOP_K = 5
# ImageNet normalization constants
IMAGENET_MEAN = [0.485, 0.456, 0.406]
IMAGENET_STD = [0.229, 0.224, 0.225]
SCRIPT_DIR = Path(__file__).resolve().parent
# ImageNet class labels (1000 classes)
with (SCRIPT_DIR / "imagenet_classes.json").open(encoding="utf-8") as file:
IMAGENET_CLASSES = json.load(file)
# ββ Model Loading ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def load_model(pte_path: str):
"""Load ExecuTorch .pte model and return the forward method."""
runtime = Runtime.get()
program = runtime.load_program(pte_path)
return program.load_method("forward")
# ββ Preprocessing ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def preprocess(image_path: str) -> torch.Tensor:
"""Load and preprocess image for model input.
Resizes to 256, center-crops to 224x224, converts to float32 tensor in
[0, 1], then applies ImageNet mean/std normalization.
"""
image = Image.open(image_path).convert("RGB")
transform = transforms.Compose(
[
transforms.Resize(256),
transforms.CenterCrop(INPUT_SIZE),
transforms.ToTensor(),
transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),
]
)
return transform(image).unsqueeze(0) # [1, 3, 224, 224]
# ββ Inference ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def run_inference(method, input_tensor: torch.Tensor) -> torch.Tensor:
"""Run forward pass and return raw logits tensor."""
outputs = method.execute([input_tensor])
return outputs[0] # [1, 1000]
# ββ Postprocessing βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def postprocess(raw_output: torch.Tensor, labels: dict[str, str], top_k: int = TOP_K) -> list:
"""Decode raw logits into top-k class predictions with probabilities.
Applies softmax to convert logits to probabilities, then returns the
top-k classes sorted by descending probability.
"""
# Ensure 2D: [1, 1000] β [1000]
logits = raw_output
if logits.dim() > 1:
logits = logits.squeeze(0)
probabilities = torch.softmax(logits, dim=-1)
k = min(top_k, logits.shape[-1])
top_scores, top_indices = probabilities.topk(k)
# Quantized logits land on a discrete grid, so exact ties are common and
# topk orders them arbitrarily. Break ties by class id for a stable result.
ranked = sorted(
zip(top_scores.tolist(), top_indices.tolist(), strict=False),
key=lambda item: (-item[0], item[1]),
)
results = []
for score, idx in ranked:
class_label = labels[str(idx)]
results.append(
{"class_index": idx, "class": class_label, "probability": round(score, 6)}
)
return results
# ββ Save Results βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def save_results(results: list, script_dir: Path) -> None:
"""Save top-k predictions as predictions.json in the script directory."""
output_path = script_dir / "predictions.json"
with open(output_path, "w") as f:
json.dump(results, f, indent=2)
print(f"Predictions saved to: {output_path}")
def save_output_image(image_path: str, top_prediction: dict, script_dir: Path) -> None:
"""Overlay the top prediction label on the original image and save as sample_output.jpg."""
image = Image.open(image_path).convert("RGB")
draw = ImageDraw.Draw(image)
label = f"{top_prediction['class']}: {top_prediction['probability'] * 100:.1f}%"
font_size = max(16, image.width // 20)
font: ImageFont.ImageFont | ImageFont.FreeTypeFont
try:
font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size
)
except OSError:
try:
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
except OSError:
font = ImageFont.load_default()
bbox = draw.textbbox((0, 0), label, font=font)
text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
padding = 8
x, y = padding, image.height - text_h - padding * 3
draw.rectangle(
[x - padding, y - padding, x + text_w + padding, y + text_h + padding],
fill=(0, 0, 0, 180),
)
draw.text((x, y), label, fill=(255, 255, 255), font=font)
output_path = script_dir / "sample_output.jpg"
image.save(output_path, quality=95)
print(f"Output image saved to: {output_path}")
# ββ Main βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def main() -> None:
script_dir = Path(__file__).parent
# Resolve paths relative to script location
model_path = script_dir / MODEL_PATH
image_path = script_dir / IMAGE_PATH
# Load model
method = load_model(str(model_path))
# Preprocess input image
input_tensor = preprocess(str(image_path))
# Run inference
raw_output = run_inference(method, input_tensor)
# Decode predictions
results = postprocess(raw_output, IMAGENET_CLASSES)
# Print top-k predictions
print(f"\nTop-{TOP_K} predictions for: {image_path.name}")
print("-" * 50)
for rank, pred in enumerate(results, 1):
print(f" {rank}. {pred['class']:<40} {pred['probability']*100:.2f}%")
# Save predictions.json
save_results(results, script_dir)
# Save labeled output image
save_output_image(str(image_path), results[0], script_dir)
if __name__ == "__main__":
main()
|