Coverage for collectra/tasks/machine_learning/orienters.py: 0.00%
63 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 09:56 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 09:56 +0000
1from __future__ import annotations
3from pathlib import Path
4from typing import TYPE_CHECKING
6from collectra.types.images import Image, ImageCrop, Orientation
8from ..base import Task
10if TYPE_CHECKING:
11 from torchvision.models import EfficientNet
13__all__ = ["ImageOrienter"]
16class ImageOrienter(Task):
18 model: str | Path | EfficientNet
20 def __init__(self, name, model: str | Path | EfficientNet, **kwargs):
21 super().__init__(name, model=model, **kwargs)
22 self.transforms = None
24 def _init_model(self):
25 """Initialisation code here is emulated from https://github.com/duartebarbosadev/deep-image-orientation-detection"""
26 import torch
27 import torchvision.transforms as transforms
28 from torchvision.models import EfficientNet
30 image_size = 384
31 self.transforms = transforms.Compose(
32 [
33 transforms.Resize((image_size + 32, image_size + 32)),
34 transforms.CenterCrop(image_size),
35 transforms.ToTensor(),
36 transforms.Normalize(
37 mean=[0.485, 0.456, 0.406],
38 std=[0.229, 0.224, 0.225],
39 ),
40 ]
41 )
43 device = torch.device(
44 "mps"
45 if torch.backends.mps.is_available()
46 else "cuda" if torch.cuda.is_available() else "cpu"
47 )
49 if isinstance(self.model, EfficientNet):
50 self.model.to(device)
51 self.device = device
52 return
54 import torchvision.models as models
56 model = models.efficientnet_v2_s(weights=None)
58 num_ftrs = model.classifier[1].in_features
60 model.classifier = torch.nn.Sequential(
61 torch.nn.Dropout(p=0.2, inplace=True),
62 torch.nn.Linear(num_ftrs, 4),
63 )
65 state_dict = torch.load(self.model, map_location=device)
66 model.load_state_dict(state_dict)
67 model.to(device)
68 model.eval()
70 self.model = model
71 self.device = device
73 def _processed_image(self, img: Image):
74 from PIL import Image as PILImage
76 image = img.pil()
78 if image.mode in ("RGB", "L"):
79 return image.convert("RGB")
81 converted_image = image.convert("RGBA")
82 background = PILImage.new("RGBA", converted_image.size, (255, 255, 255))
83 background.paste(converted_image, mask=converted_image)
84 return background
86 def run(self, *args: Image) -> Image:
87 import torch
88 from torchvision.models import EfficientNet
90 if len(args) != 1:
91 raise ValueError("This task only supports a single Image input.")
92 self._init_model()
93 img = args[0]
94 image = self._processed_image(img)
95 input_tensor = self.transforms(image).unsqueeze(0).to(self.device)
97 if not isinstance(self.model, EfficientNet):
98 raise ValueError("Model must be an instance of EfficientNet.")
100 with torch.no_grad():
101 output = self.model(input_tensor)
102 _, predicted_idx = torch.max(output, 1)
103 predicted_class = predicted_idx.item()
104 orientation = Orientation(predicted_class)
106 name = self.get_output_name()
108 if isinstance(img, ImageCrop):
109 new_image = ImageCrop(
110 name=name,
111 data=img.data,
112 x_center=float(img.x_center),
113 y_center=float(img.y_center),
114 width_relative=float(img.width_relative),
115 height_relative=float(img.height_relative),
116 orientation=orientation,
117 )
118 return new_image
120 new_image = Image(
121 name=name,
122 data=img.data,
123 orientation=orientation,
124 )
126 return new_image