orient_express 2.1.3__tar.gz → 2.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {orient_express-2.1.3 → orient_express-2.2.1}/PKG-INFO +3 -3
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/__init__.py +8 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/classification.py +0 -4
- orient_express-2.2.1/orient_express/predictors/feature_extraction.py +59 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/instance_segmentation.py +40 -18
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/multi_label_classification.py +0 -4
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/object_detection.py +7 -2
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/semantic_segmentation.py +8 -5
- {orient_express-2.1.3 → orient_express-2.2.1}/pyproject.toml +3 -8
- {orient_express-2.1.3 → orient_express-2.2.1}/README.md +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/__init__.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/deployment.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/model_wrapper.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/predictor.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/sklearn_pipeline.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/utils/colors.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/utils/gs.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/utils/image_processor.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/utils/paths.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/utils/retry.py +0 -0
- {orient_express-2.1.3 → orient_express-2.2.1}/orient_express/vertex.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orient_express
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: A library to simplify model deployment to Vertex AI
|
|
5
5
|
Author: Alexey Zankevich
|
|
6
6
|
Author-email: alex.zankevich@shiftsmart.com
|
|
@@ -19,8 +19,8 @@ Requires-Dist: opencv-python-headless (>=4.11.0,<5.0.0)
|
|
|
19
19
|
Requires-Dist: pandas (>=2.0,<3.0)
|
|
20
20
|
Requires-Dist: pyyaml (>=6.0,<7.0)
|
|
21
21
|
Requires-Dist: scikit-learn (>=1.5.0,<2.0.0)
|
|
22
|
-
Requires-Dist: torch (>=2.5.0
|
|
23
|
-
Requires-Dist: torchvision (>=0.20.0
|
|
22
|
+
Requires-Dist: torch (>=2.5.0)
|
|
23
|
+
Requires-Dist: torchvision (>=0.20.0)
|
|
24
24
|
Requires-Dist: xgboost (>=2.0.0,<3.0.0)
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
@@ -21,6 +21,7 @@ from .multi_label_classification import (
|
|
|
21
21
|
MultiLabelClassificationPredictor,
|
|
22
22
|
MultiLabelClassificationPrediction,
|
|
23
23
|
)
|
|
24
|
+
from .feature_extraction import FeatureExtractionPredictor, FeaturePrediction
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def get_predictor(dir: str, device: str = "cpu"):
|
|
@@ -62,6 +63,8 @@ def get_predictor(dir: str, device: str = "cpu"):
|
|
|
62
63
|
return load_image_predictor(
|
|
63
64
|
MultiLabelClassificationPredictor, dir, metadata, device
|
|
64
65
|
)
|
|
66
|
+
elif model_type == FeatureExtractionPredictor.model_type:
|
|
67
|
+
return load_feature_extractor(dir, metadata, device)
|
|
65
68
|
else:
|
|
66
69
|
raise Exception(f"Unknown model_type '{model_type}'")
|
|
67
70
|
|
|
@@ -74,3 +77,8 @@ def load_image_predictor(
|
|
|
74
77
|
raise Exception("No classes defined in metadata.yaml")
|
|
75
78
|
classes = metadata["classes"]
|
|
76
79
|
return model_type(onnx_path, classes, device)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def load_feature_extractor(dir: str, metadata: dict, device: str = "cpu"):
|
|
83
|
+
onnx_path = os.path.join(dir, metadata["model_file"])
|
|
84
|
+
return FeatureExtractionPredictor(onnx_path, device)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
|
-
import cv2
|
|
4
|
-
import numpy as np
|
|
5
3
|
from PIL import Image
|
|
6
4
|
|
|
7
5
|
from .predictor import OnnxSessionWrapper, ImagePredictor
|
|
@@ -24,9 +22,7 @@ class ClassificationPrediction:
|
|
|
24
22
|
class OnnxClassifier(OnnxSessionWrapper):
|
|
25
23
|
def __call__(self, pil_images: list[Image.Image]):
|
|
26
24
|
images_array = self.collate_images(pil_images)
|
|
27
|
-
|
|
28
25
|
input_dict = {self.input_names[0]: images_array}
|
|
29
|
-
|
|
30
26
|
scores = self.session.run(None, input_dict)[0]
|
|
31
27
|
return scores
|
|
32
28
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
import numpy as np
|
|
6
|
+
from PIL import Image
|
|
7
|
+
|
|
8
|
+
from .predictor import OnnxSessionWrapper, ImagePredictor
|
|
9
|
+
from ..utils.paths import get_metadata_path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class FeaturePrediction:
|
|
14
|
+
feature: np.ndarray
|
|
15
|
+
|
|
16
|
+
def to_dict(self):
|
|
17
|
+
return {
|
|
18
|
+
"feature": self.feature.tolist(),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class OnnxFeatureExtractor(OnnxSessionWrapper):
|
|
23
|
+
def __call__(self, pil_images: list[Image.Image]):
|
|
24
|
+
images_array = self.collate_images(pil_images)
|
|
25
|
+
input_dict = {self.input_names[0]: images_array}
|
|
26
|
+
features = self.session.run(None, input_dict)[0]
|
|
27
|
+
return features
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FeatureExtractionPredictor(ImagePredictor):
|
|
31
|
+
model_type = "feature-extraction-onnx"
|
|
32
|
+
backend_model = OnnxFeatureExtractor
|
|
33
|
+
|
|
34
|
+
def __init__(self, model_path: str, device: str = "cpu"):
|
|
35
|
+
self.model = self.backend_model(model_path, device)
|
|
36
|
+
self.model_path = model_path
|
|
37
|
+
|
|
38
|
+
def predict(self, images: list[Image.Image]) -> list[FeaturePrediction]:
|
|
39
|
+
if not images:
|
|
40
|
+
return []
|
|
41
|
+
raw_outputs = self.model(images)
|
|
42
|
+
outputs = []
|
|
43
|
+
for feature in raw_outputs:
|
|
44
|
+
outputs.append(FeaturePrediction(feature=feature))
|
|
45
|
+
return outputs
|
|
46
|
+
|
|
47
|
+
def get_annotated_image(self, image: Image.Image, predictions: FeaturePrediction):
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def dump(self, dir: str):
|
|
51
|
+
metadata = {
|
|
52
|
+
"model_type": self.model_type,
|
|
53
|
+
"model_file": os.path.basename(self.model_path),
|
|
54
|
+
}
|
|
55
|
+
metadata_path = get_metadata_path(dir)
|
|
56
|
+
with open(metadata_path, "w") as f:
|
|
57
|
+
yaml.dump(metadata, f)
|
|
58
|
+
# model is already saved in the model_path
|
|
59
|
+
return [metadata_path, self.model_path]
|
{orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/instance_segmentation.py
RENAMED
|
@@ -10,6 +10,8 @@ import torch.nn.functional as F
|
|
|
10
10
|
from .predictor import OnnxSessionWrapper, ImagePredictor
|
|
11
11
|
from ..utils.image_processor import pil_to_opencv, opencv_to_pil
|
|
12
12
|
|
|
13
|
+
FONT = cv2.FONT_HERSHEY_SIMPLEX
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
@dataclass
|
|
15
17
|
class InstanceSegmentationPrediction:
|
|
@@ -128,7 +130,12 @@ class InstanceSegmentationPredictor(ImagePredictor):
|
|
|
128
130
|
return outputs
|
|
129
131
|
|
|
130
132
|
def get_annotated_image(
|
|
131
|
-
self,
|
|
133
|
+
self,
|
|
134
|
+
image: Image.Image,
|
|
135
|
+
predictions: list[InstanceSegmentationPrediction],
|
|
136
|
+
mask_opacity: float = 0.3,
|
|
137
|
+
draw_indices: bool = True,
|
|
138
|
+
font_scale: float | None = None,
|
|
132
139
|
) -> Image.Image:
|
|
133
140
|
opencv_image = pil_to_opencv(image)
|
|
134
141
|
mask_overlay = opencv_image.copy()
|
|
@@ -137,15 +144,20 @@ class InstanceSegmentationPredictor(ImagePredictor):
|
|
|
137
144
|
fill_color = self.color_scheme.get(pred.clss, (120, 120, 120))
|
|
138
145
|
mask_overlay[pred.mask] = fill_color[:3]
|
|
139
146
|
|
|
140
|
-
alpha = 0.5
|
|
141
147
|
annotated_image = cv2.addWeighted(
|
|
142
|
-
mask_overlay,
|
|
148
|
+
mask_overlay, mask_opacity, opencv_image, 1 - mask_opacity, 0
|
|
143
149
|
)
|
|
144
150
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
if draw_indices:
|
|
152
|
+
class_counts = defaultdict(int)
|
|
153
|
+
for pred in predictions:
|
|
154
|
+
class_counts[pred.clss] += 1
|
|
155
|
+
self.draw_mask_index(
|
|
156
|
+
annotated_image,
|
|
157
|
+
pred,
|
|
158
|
+
class_counts[pred.clss],
|
|
159
|
+
font_scale,
|
|
160
|
+
)
|
|
149
161
|
|
|
150
162
|
return opencv_to_pil(annotated_image)
|
|
151
163
|
|
|
@@ -154,25 +166,35 @@ class InstanceSegmentationPredictor(ImagePredictor):
|
|
|
154
166
|
opencv_image: np.ndarray,
|
|
155
167
|
prediction: InstanceSegmentationPrediction,
|
|
156
168
|
index: int,
|
|
169
|
+
font_scale: float | None = None,
|
|
157
170
|
):
|
|
158
|
-
# Calculate the centroid of the bbox
|
|
159
|
-
centroid_x = int((prediction.bbox[0] + prediction.bbox[2]) / 2)
|
|
160
|
-
centroid_y = int((prediction.bbox[1] + prediction.bbox[3]) / 2)
|
|
161
|
-
# Put the object count in the center of the bbox
|
|
162
|
-
font_scale = 2
|
|
163
|
-
font_thickness = 4
|
|
164
171
|
text = str(index)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
|
|
173
|
+
bbox = prediction.bbox
|
|
174
|
+
bbox_width = bbox[2] - bbox[0]
|
|
175
|
+
bbox_height = bbox[3] - bbox[1]
|
|
176
|
+
|
|
177
|
+
if font_scale is None:
|
|
178
|
+
target_size = min(bbox_width, bbox_height) * 0.3
|
|
179
|
+
(base_w, base_h), _ = cv2.getTextSize(text, FONT, 1, 1)
|
|
180
|
+
font_scale = target_size / max(base_w, base_h)
|
|
181
|
+
|
|
182
|
+
assert font_scale is not None
|
|
183
|
+
thickness = max(int(font_scale * 2), 1)
|
|
184
|
+
|
|
185
|
+
text_size = cv2.getTextSize(text, FONT, font_scale, thickness)[0]
|
|
186
|
+
|
|
187
|
+
centroid_x = int((bbox[0] + bbox[2]) / 2)
|
|
188
|
+
centroid_y = int((bbox[1] + bbox[3]) / 2)
|
|
168
189
|
text_x = int(centroid_x - text_size[0] / 2)
|
|
169
190
|
text_y = int(centroid_y + text_size[1] / 2)
|
|
191
|
+
|
|
170
192
|
cv2.putText(
|
|
171
193
|
opencv_image,
|
|
172
194
|
text,
|
|
173
195
|
(text_x, text_y),
|
|
174
|
-
|
|
196
|
+
FONT,
|
|
175
197
|
font_scale,
|
|
176
198
|
(255, 255, 255),
|
|
177
|
-
|
|
199
|
+
thickness,
|
|
178
200
|
)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
|
-
import cv2
|
|
4
|
-
import numpy as np
|
|
5
3
|
from PIL import Image
|
|
6
4
|
|
|
7
5
|
from .predictor import OnnxSessionWrapper, ImagePredictor
|
|
@@ -22,9 +20,7 @@ class MultiLabelClassificationPrediction:
|
|
|
22
20
|
class OnnxMultiLabelClassifier(OnnxSessionWrapper):
|
|
23
21
|
def __call__(self, pil_images: list[Image.Image]):
|
|
24
22
|
images_array = self.collate_images(pil_images)
|
|
25
|
-
|
|
26
23
|
input_dict = {self.input_names[0]: images_array}
|
|
27
|
-
|
|
28
24
|
scores = self.session.run(None, input_dict)[0]
|
|
29
25
|
return scores
|
|
30
26
|
|
|
@@ -117,7 +117,10 @@ class BoundingBoxPredictor(ImagePredictor):
|
|
|
117
117
|
return outputs
|
|
118
118
|
|
|
119
119
|
def get_annotated_image(
|
|
120
|
-
self,
|
|
120
|
+
self,
|
|
121
|
+
image: Image.Image,
|
|
122
|
+
predictions: list[BoundingBoxPrediction],
|
|
123
|
+
line_width: int = 8,
|
|
121
124
|
):
|
|
122
125
|
opencv_image = pil_to_opencv(image)
|
|
123
126
|
|
|
@@ -125,6 +128,8 @@ class BoundingBoxPredictor(ImagePredictor):
|
|
|
125
128
|
color = self.color_scheme.get(pred.clss, (255, 255, 255))
|
|
126
129
|
x1, y1, x2, y2 = pred.bbox
|
|
127
130
|
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
|
128
|
-
opencv_image = cv2.rectangle(
|
|
131
|
+
opencv_image = cv2.rectangle(
|
|
132
|
+
opencv_image, (x1, y1), (x2, y2), color, line_width
|
|
133
|
+
)
|
|
129
134
|
|
|
130
135
|
return opencv_to_pil(opencv_image)
|
{orient_express-2.1.3 → orient_express-2.2.1}/orient_express/predictors/semantic_segmentation.py
RENAMED
|
@@ -76,15 +76,18 @@ class SemanticSegmentationPredictor(ImagePredictor):
|
|
|
76
76
|
)
|
|
77
77
|
return outputs
|
|
78
78
|
|
|
79
|
-
def get_annotated_image(
|
|
79
|
+
def get_annotated_image(
|
|
80
|
+
self, image: Image.Image, mask: np.array, mask_opacity: float = 0.3
|
|
81
|
+
) -> Image.Image:
|
|
80
82
|
opencv_image = pil_to_opencv(image)
|
|
81
|
-
|
|
82
|
-
mask_rgb = np.zeros((*mask.shape, 3), dtype=np.uint8)
|
|
83
|
+
mask_overlay = opencv_image.copy()
|
|
83
84
|
|
|
84
85
|
for class_id, class_name in self.classes.items():
|
|
85
86
|
fill_color = self.color_scheme.get(class_name, (120, 120, 120))
|
|
86
|
-
|
|
87
|
+
mask_overlay[mask == class_id] = fill_color[:3]
|
|
87
88
|
|
|
88
|
-
annotated_image = cv2.addWeighted(
|
|
89
|
+
annotated_image = cv2.addWeighted(
|
|
90
|
+
mask_overlay, mask_opacity, opencv_image, 1 - mask_opacity, 0
|
|
91
|
+
)
|
|
89
92
|
|
|
90
93
|
return opencv_to_pil(annotated_image)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "orient_express"
|
|
3
|
-
version = "2.1
|
|
3
|
+
version = "2.2.1"
|
|
4
4
|
description = "A library to simplify model deployment to Vertex AI"
|
|
5
5
|
authors = ["Alexey Zankevich <alex.zankevich@shiftsmart.com>", "Ian Myers <ian.myers@shiftsmart.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -11,8 +11,8 @@ google-cloud-aiplatform = "^1.0"
|
|
|
11
11
|
google-cloud-storage = "^3.0"
|
|
12
12
|
pandas = "^2.0"
|
|
13
13
|
pyyaml = "^6.0"
|
|
14
|
-
torch =
|
|
15
|
-
torchvision =
|
|
14
|
+
torch = ">=2.5.0"
|
|
15
|
+
torchvision = ">=0.20.0"
|
|
16
16
|
opencv-python-headless = "^4.11.0"
|
|
17
17
|
Pillow = ">=10.0.1"
|
|
18
18
|
gcsfs = "^2024.10.0"
|
|
@@ -30,8 +30,3 @@ python-json-logger = "^4.0.0"
|
|
|
30
30
|
[build-system]
|
|
31
31
|
requires = ["poetry-core"]
|
|
32
32
|
build-backend = "poetry.core.masonry.api"
|
|
33
|
-
|
|
34
|
-
[[tool.poetry.source]]
|
|
35
|
-
name = "pytorch-cpu"
|
|
36
|
-
url = "https://download.pytorch.org/whl/cpu"
|
|
37
|
-
priority = "explicit"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|