orient_express 2.0__tar.gz → 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.
Files changed (20) hide show
  1. orient_express-2.0/README.md → orient_express-2.1/PKG-INFO +123 -0
  2. orient_express-2.0/PKG-INFO → orient_express-2.1/README.md +94 -18
  3. {orient_express-2.0 → orient_express-2.1}/orient_express/predictors/__init__.py +21 -7
  4. {orient_express-2.0 → orient_express-2.1}/orient_express/predictors/classification.py +1 -1
  5. {orient_express-2.0 → orient_express-2.1}/orient_express/predictors/instance_segmentation.py +2 -2
  6. orient_express-2.1/orient_express/predictors/multi_label_classification.py +68 -0
  7. {orient_express-2.0 → orient_express-2.1}/orient_express/predictors/predictor.py +14 -3
  8. {orient_express-2.0 → orient_express-2.1}/orient_express/vertex.py +2 -3
  9. {orient_express-2.0 → orient_express-2.1}/pyproject.toml +11 -17
  10. {orient_express-2.0 → orient_express-2.1}/orient_express/__init__.py +0 -0
  11. {orient_express-2.0 → orient_express-2.1}/orient_express/deployment.py +0 -0
  12. {orient_express-2.0 → orient_express-2.1}/orient_express/model_wrapper.py +0 -0
  13. {orient_express-2.0 → orient_express-2.1}/orient_express/predictors/object_detection.py +0 -0
  14. {orient_express-2.0 → orient_express-2.1}/orient_express/predictors/semantic_segmentation.py +0 -0
  15. {orient_express-2.0 → orient_express-2.1}/orient_express/sklearn_pipeline.py +0 -0
  16. {orient_express-2.0 → orient_express-2.1}/orient_express/utils/colors.py +0 -0
  17. {orient_express-2.0 → orient_express-2.1}/orient_express/utils/gs.py +0 -0
  18. {orient_express-2.0 → orient_express-2.1}/orient_express/utils/image_processor.py +0 -0
  19. {orient_express-2.0 → orient_express-2.1}/orient_express/utils/paths.py +0 -0
  20. {orient_express-2.0 → orient_express-2.1}/orient_express/utils/retry.py +0 -0
@@ -1,3 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: orient_express
3
+ Version: 2.1
4
+ Summary: A library to simplify model deployment to Vertex AI
5
+ Author: Alexey Zankevich
6
+ Author-email: alex.zankevich@shiftsmart.com
7
+ Requires-Python: >=3.10,<3.13
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: Pillow (==10.0.1)
13
+ Requires-Dist: gcsfs (>=2024.10.0,<2025.0.0)
14
+ Requires-Dist: google-cloud-aiplatform
15
+ Requires-Dist: google-cloud-storage
16
+ Requires-Dist: kserve (==0.16.0)
17
+ Requires-Dist: onnxruntime (>=1.20,<2.0) ; sys_platform == "darwin" or platform_machine == "aarch64" or platform_machine == "arm64" or sys_platform == "win32" and platform_machine == "ARM64"
18
+ Requires-Dist: onnxruntime-gpu (>=1.20,<2.0) ; sys_platform == "linux" and platform_machine == "x86_64" or sys_platform == "win32" and platform_machine == "AMD64"
19
+ Requires-Dist: opencv-python-headless (==4.11.0.86)
20
+ Requires-Dist: pandas
21
+ Requires-Dist: python-json-logger (==2.0.7)
22
+ Requires-Dist: pyyaml (>=6.0,<7.0)
23
+ Requires-Dist: scikit-learn (==1.5.2)
24
+ Requires-Dist: torch (>=2.5.0,<3.0.0)
25
+ Requires-Dist: torchvision (>=0.20.0,<0.21.0)
26
+ Requires-Dist: xgboost (==2.1.2)
27
+ Description-Content-Type: text/markdown
28
+
1
29
  # Orient Express
2
30
 
3
31
  A library to accelerate model deployments to Vertex AI directly from colab notebooks
@@ -18,6 +46,16 @@ Both workflows handle versioning, artifact storage in GCS, and integration with
18
46
  pip install orient_express
19
47
  ```
20
48
 
49
+ For local development:
50
+ ```bash
51
+ pip install -e .
52
+ ```
53
+
54
+ Or with Poetry:
55
+ ```bash
56
+ poetry install
57
+ ```
58
+
21
59
  ## Workflows
22
60
 
23
61
  ### ONNX Image Model Workflow
@@ -137,6 +175,43 @@ local_predictor = vertex_model.get_local_predictor()
137
175
  predictions = local_predictor.predict(X_test)
138
176
  ```
139
177
 
178
+ ## ONNX Runtime and Device Support
179
+
180
+ ### Platform Support Matrix
181
+
182
+ | Platform | Architecture | ONNX Runtime Package | CUDA Available |
183
+ |----------|--------------|---------------------|----------------|
184
+ | Linux | x86_64 | onnxruntime-gpu | Yes |
185
+ | Linux | aarch64 | onnxruntime | No |
186
+ | Windows | x64 (AMD64) | onnxruntime-gpu | Yes |
187
+ | Windows | ARM64 | onnxruntime | No |
188
+ | macOS | x86_64 | onnxruntime | No |
189
+ | macOS | arm64 | onnxruntime | No |
190
+
191
+ The appropriate package is installed automatically based on your platform.
192
+
193
+ ### Selecting CPU vs CUDA Execution
194
+
195
+ When loading a predictor, use the `device` parameter to specify the execution provider:
196
+ ```python
197
+ from orient_express.predictors import ObjectDetectionPredictor
198
+
199
+ # CPU inference (works on all platforms)
200
+ predictor = ObjectDetectionPredictor("/path/to/model", classes, device="cpu")
201
+
202
+ # CUDA inference (requires Linux x64 or Windows x64 with CUDA drivers)
203
+ predictor = ObjectDetectionPredictor("/path/to/model", classes, device="cuda")
204
+ ```
205
+
206
+ When using a Vertex AI model:
207
+ ```python
208
+ # CPU inference
209
+ predictor = model.get_local_predictor(device="cpu")
210
+
211
+ # CUDA inference
212
+ predictor = model.get_local_predictor(device="cuda")
213
+ ```
214
+
140
215
  ### Pinning Model Versions
141
216
 
142
217
  By default, `get_vertex_model` returns the most recently updated version. To pin to a specific version:
@@ -213,6 +288,53 @@ class ClassificationPrediction:
213
288
 
214
289
  </details>
215
290
 
291
+ ### MultiLabelClassificationPredictor
292
+
293
+ <details>
294
+ <summary>Click to expand</summary>
295
+
296
+ For image multi-label classification models that output a set of binary class probabilities.
297
+
298
+ #### ONNX Graph Requirements
299
+
300
+ | | |
301
+ | ----------- | ------------------------------------------------------------ |
302
+ | **Inputs** | `images`: `[batch, height, width, 3]` uint8 RGB |
303
+ | **Outputs** | `scores`: `[batch, num_classes]` float32 class probabilities |
304
+
305
+ The graph must handle normalization internally. No target_sizes input is needed.
306
+
307
+ #### Usage
308
+
309
+ ```python
310
+ from orient_express.predictors import MultiLabelClassificationPredictor
311
+
312
+ predictor = MultiLabelClassificationPredictor(
313
+ onnx_path="classifier.onnx",
314
+ classes={1: "contains_cat", 2: "contains_dog", 3: "contains_bird"}
315
+ )
316
+
317
+ predictions = predictor.predict(images, confidence=0.5)
318
+ # Returns: list[MultiLabelClassificationPrediction]
319
+ ```
320
+
321
+ #### Output Structure
322
+
323
+ ```python
324
+ @dataclass
325
+ class MultiLabelClassificationPrediction:
326
+ classes: list[str] # Predicted class names based on confidence threshold
327
+ class_scores: dict[str, float] # Scores for all classes
328
+
329
+ # to_dict() output:
330
+ {
331
+ "classes": ["contains_cat", "contains_bird"],
332
+ "class_scores": {"contains_cat": 0.95, "contains_dog": 0.03, "contains_bird": 0.82}
333
+ }
334
+ ```
335
+
336
+ </details>
337
+
216
338
  ### BoundingBoxPredictor
217
339
 
218
340
  <details>
@@ -650,3 +772,4 @@ python deploy.py --run-type scheduled
650
772
  ```
651
773
 
652
774
  </details>
775
+
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: orient_express
3
- Version: 2.0
4
- Summary: A library to simplify model deployment to Vertex AI
5
- Author: Alexey Zankevich
6
- Author-email: alex.zankevich@shiftsmart.com
7
- Requires-Python: >=3.10,<3.13
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.10
10
- Classifier: Programming Language :: Python :: 3.11
11
- Classifier: Programming Language :: Python :: 3.12
12
- Requires-Dist: google-cloud-aiplatform
13
- Requires-Dist: google-cloud-storage
14
- Requires-Dist: pandas
15
- Requires-Dist: pyyaml (>=6.0,<7.0)
16
- Description-Content-Type: text/markdown
17
-
18
1
  # Orient Express
19
2
 
20
3
  A library to accelerate model deployments to Vertex AI directly from colab notebooks
@@ -35,6 +18,16 @@ Both workflows handle versioning, artifact storage in GCS, and integration with
35
18
  pip install orient_express
36
19
  ```
37
20
 
21
+ For local development:
22
+ ```bash
23
+ pip install -e .
24
+ ```
25
+
26
+ Or with Poetry:
27
+ ```bash
28
+ poetry install
29
+ ```
30
+
38
31
  ## Workflows
39
32
 
40
33
  ### ONNX Image Model Workflow
@@ -154,6 +147,43 @@ local_predictor = vertex_model.get_local_predictor()
154
147
  predictions = local_predictor.predict(X_test)
155
148
  ```
156
149
 
150
+ ## ONNX Runtime and Device Support
151
+
152
+ ### Platform Support Matrix
153
+
154
+ | Platform | Architecture | ONNX Runtime Package | CUDA Available |
155
+ |----------|--------------|---------------------|----------------|
156
+ | Linux | x86_64 | onnxruntime-gpu | Yes |
157
+ | Linux | aarch64 | onnxruntime | No |
158
+ | Windows | x64 (AMD64) | onnxruntime-gpu | Yes |
159
+ | Windows | ARM64 | onnxruntime | No |
160
+ | macOS | x86_64 | onnxruntime | No |
161
+ | macOS | arm64 | onnxruntime | No |
162
+
163
+ The appropriate package is installed automatically based on your platform.
164
+
165
+ ### Selecting CPU vs CUDA Execution
166
+
167
+ When loading a predictor, use the `device` parameter to specify the execution provider:
168
+ ```python
169
+ from orient_express.predictors import ObjectDetectionPredictor
170
+
171
+ # CPU inference (works on all platforms)
172
+ predictor = ObjectDetectionPredictor("/path/to/model", classes, device="cpu")
173
+
174
+ # CUDA inference (requires Linux x64 or Windows x64 with CUDA drivers)
175
+ predictor = ObjectDetectionPredictor("/path/to/model", classes, device="cuda")
176
+ ```
177
+
178
+ When using a Vertex AI model:
179
+ ```python
180
+ # CPU inference
181
+ predictor = model.get_local_predictor(device="cpu")
182
+
183
+ # CUDA inference
184
+ predictor = model.get_local_predictor(device="cuda")
185
+ ```
186
+
157
187
  ### Pinning Model Versions
158
188
 
159
189
  By default, `get_vertex_model` returns the most recently updated version. To pin to a specific version:
@@ -230,6 +260,53 @@ class ClassificationPrediction:
230
260
 
231
261
  </details>
232
262
 
263
+ ### MultiLabelClassificationPredictor
264
+
265
+ <details>
266
+ <summary>Click to expand</summary>
267
+
268
+ For image multi-label classification models that output a set of binary class probabilities.
269
+
270
+ #### ONNX Graph Requirements
271
+
272
+ | | |
273
+ | ----------- | ------------------------------------------------------------ |
274
+ | **Inputs** | `images`: `[batch, height, width, 3]` uint8 RGB |
275
+ | **Outputs** | `scores`: `[batch, num_classes]` float32 class probabilities |
276
+
277
+ The graph must handle normalization internally. No target_sizes input is needed.
278
+
279
+ #### Usage
280
+
281
+ ```python
282
+ from orient_express.predictors import MultiLabelClassificationPredictor
283
+
284
+ predictor = MultiLabelClassificationPredictor(
285
+ onnx_path="classifier.onnx",
286
+ classes={1: "contains_cat", 2: "contains_dog", 3: "contains_bird"}
287
+ )
288
+
289
+ predictions = predictor.predict(images, confidence=0.5)
290
+ # Returns: list[MultiLabelClassificationPrediction]
291
+ ```
292
+
293
+ #### Output Structure
294
+
295
+ ```python
296
+ @dataclass
297
+ class MultiLabelClassificationPrediction:
298
+ classes: list[str] # Predicted class names based on confidence threshold
299
+ class_scores: dict[str, float] # Scores for all classes
300
+
301
+ # to_dict() output:
302
+ {
303
+ "classes": ["contains_cat", "contains_bird"],
304
+ "class_scores": {"contains_cat": 0.95, "contains_dog": 0.03, "contains_bird": 0.82}
305
+ }
306
+ ```
307
+
308
+ </details>
309
+
233
310
  ### BoundingBoxPredictor
234
311
 
235
312
  <details>
@@ -667,4 +744,3 @@ python deploy.py --run-type scheduled
667
744
  ```
668
745
 
669
746
  </details>
670
-
@@ -17,9 +17,13 @@ from .semantic_segmentation import (
17
17
  SemanticSegmentationPrediction,
18
18
  )
19
19
  from .classification import ClassificationPredictor, ClassificationPrediction
20
+ from .multi_label_classification import (
21
+ MultiLabelClassificationPredictor,
22
+ MultiLabelClassificationPrediction,
23
+ )
20
24
 
21
25
 
22
- def get_predictor(dir: str):
26
+ def get_predictor(dir: str, device: str = "cpu"):
23
27
  downloaded_files = os.listdir(dir)
24
28
  metadata_path = get_metadata_path(dir)
25
29
  if not os.path.exists(metadata_path):
@@ -43,20 +47,30 @@ def get_predictor(dir: str):
43
47
  joblib_path = os.path.join(dir, metadata["model_file"])
44
48
  return joblib.load(joblib_path)
45
49
  elif model_type == InstanceSegmentationPredictor.model_type:
46
- return load_image_predictor(InstanceSegmentationPredictor, dir, metadata)
50
+ return load_image_predictor(
51
+ InstanceSegmentationPredictor, dir, metadata, device
52
+ )
47
53
  elif model_type == BoundingBoxPredictor.model_type:
48
- return load_image_predictor(BoundingBoxPredictor, dir, metadata)
54
+ return load_image_predictor(BoundingBoxPredictor, dir, metadata, device)
49
55
  elif model_type == SemanticSegmentationPredictor.model_type:
50
- return load_image_predictor(SemanticSegmentationPredictor, dir, metadata)
56
+ return load_image_predictor(
57
+ SemanticSegmentationPredictor, dir, metadata, device
58
+ )
51
59
  elif model_type == ClassificationPredictor.model_type:
52
- return load_image_predictor(ClassificationPredictor, dir, metadata)
60
+ return load_image_predictor(ClassificationPredictor, dir, metadata, device)
61
+ elif model_type == MultiLabelClassificationPredictor.model_type:
62
+ return load_image_predictor(
63
+ MultiLabelClassificationPredictor, dir, metadata, device
64
+ )
53
65
  else:
54
66
  raise Exception(f"Unknown model_type '{model_type}'")
55
67
 
56
68
 
57
- def load_image_predictor(model_type: type[ImagePredictor], dir: str, metadata: dict):
69
+ def load_image_predictor(
70
+ model_type: type[ImagePredictor], dir: str, metadata: dict, device: str = "cpu"
71
+ ):
58
72
  onnx_path = os.path.join(dir, metadata["model_file"])
59
73
  if "classes" not in metadata:
60
74
  raise Exception("No classes defined in metadata.yaml")
61
75
  classes = metadata["classes"]
62
- return model_type(onnx_path, classes)
76
+ return model_type(onnx_path, classes, device)
@@ -31,7 +31,7 @@ class OnnxClassifier(OnnxSessionWrapper):
31
31
 
32
32
  input_dict = {self.input_names[0]: images_array}
33
33
 
34
- scores = self.session.run(None, input_dict)
34
+ scores = self.session.run(None, input_dict)[0]
35
35
  return scores
36
36
 
37
37
 
@@ -166,8 +166,8 @@ class InstanceSegmentationPredictor(ImagePredictor):
166
166
  centroid_x = int((prediction.bbox[0] + prediction.bbox[2]) / 2)
167
167
  centroid_y = int((prediction.bbox[1] + prediction.bbox[3]) / 2)
168
168
  # Put the object count in the center of the bbox
169
- font_scale = 1.5
170
- font_thickness = 4
169
+ font_scale = 6
170
+ font_thickness = 12
171
171
  text = str(index)
172
172
  text_size = cv2.getTextSize(
173
173
  text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, font_thickness
@@ -0,0 +1,68 @@
1
+ from dataclasses import dataclass
2
+
3
+ import cv2
4
+ import numpy as np
5
+ from PIL import Image
6
+
7
+ from .predictor import OnnxSessionWrapper, ImagePredictor
8
+
9
+
10
+ @dataclass
11
+ class MultiLabelClassificationPrediction:
12
+ classes: list[str]
13
+ class_scores: dict[str, float]
14
+
15
+ def to_dict(self):
16
+ return {
17
+ "classes": self.classes,
18
+ "class_scores": self.class_scores,
19
+ }
20
+
21
+
22
+ class OnnxMultiLabelClassifier(OnnxSessionWrapper):
23
+ def __call__(self, pil_images: list[Image.Image]):
24
+ images = [
25
+ cv2.resize(np.array(pil_img), (self.resolution, self.resolution))
26
+ for pil_img in pil_images
27
+ ]
28
+ images_array = np.array(images)
29
+
30
+ input_dict = {self.input_names[0]: images_array}
31
+
32
+ scores = self.session.run(None, input_dict)[0]
33
+ return scores
34
+
35
+
36
+ class MultiLabelClassificationPredictor(ImagePredictor):
37
+ model_type = "multi-label-classification-onnx"
38
+ backend_model = OnnxMultiLabelClassifier
39
+
40
+ def predict(
41
+ self, images: list[Image.Image], confidence: float
42
+ ) -> list[MultiLabelClassificationPrediction]:
43
+ if not images:
44
+ return []
45
+ raw_outputs = self.model(images)
46
+ outputs = []
47
+ for class_scores in raw_outputs:
48
+ classes = []
49
+ # self.classes is 1-indexed
50
+ for idx, score in enumerate(class_scores):
51
+ if score > confidence:
52
+ classes.append(self.classes.get(idx + 1, "Unknown"))
53
+ outputs.append(
54
+ MultiLabelClassificationPrediction(
55
+ classes=classes,
56
+ class_scores={
57
+ # self.classes is 1-indexed
58
+ clss: float(class_scores[class_idx - 1])
59
+ for class_idx, clss in self.classes.items()
60
+ },
61
+ )
62
+ )
63
+ return outputs
64
+
65
+ def get_annotated_image(
66
+ self, image: Image.Image, predictions: MultiLabelClassificationPrediction
67
+ ):
68
+ return None
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from abc import ABC, abstractmethod
3
+ import warnings
3
4
 
4
5
  import yaml
5
6
  import onnxruntime as ort
@@ -34,8 +35,8 @@ class ImagePredictor(Predictor):
34
35
  backend_model: type
35
36
  prediction_type: type
36
37
 
37
- def __init__(self, model_path: str, classes: dict[int, str]):
38
- self.model = self.backend_model(model_path)
38
+ def __init__(self, model_path: str, classes: dict[int, str], device: str = "cpu"):
39
+ self.model = self.backend_model(model_path, device)
39
40
  self.color_scheme = generate_color_scheme(list(classes.values()))
40
41
  self.classes = classes
41
42
  self.model_path = model_path
@@ -63,7 +64,7 @@ class ImagePredictor(Predictor):
63
64
 
64
65
 
65
66
  class OnnxSessionWrapper:
66
- def __init__(self, onnx_path, providers=["CPUExecutionProvider"]):
67
+ def __init__(self, onnx_path: str, device: str = "cpu"):
67
68
  session_options = ort.SessionOptions()
68
69
  session_options.graph_optimization_level = (
69
70
  ort.GraphOptimizationLevel.ORT_ENABLE_ALL
@@ -72,6 +73,16 @@ class OnnxSessionWrapper:
72
73
  session_options.enable_cpu_mem_arena = True
73
74
  session_options.enable_mem_reuse = True
74
75
 
76
+ if device == "cpu":
77
+ providers = ["CPUExecutionProvider"]
78
+ elif device == "cuda":
79
+ providers = ["CUDAExecutionProvider"]
80
+ else:
81
+ warnings.warn(
82
+ f"Unknown device '{device}'. Defaulting to CPU. Supported devices: 'cpu', 'cuda'."
83
+ )
84
+ providers = ["CPUExecutionProvider"]
85
+
75
86
  self.session = ort.InferenceSession(
76
87
  onnx_path, providers=providers, sess_options=session_options
77
88
  )
@@ -7,7 +7,6 @@ from google.cloud import storage, aiplatform
7
7
 
8
8
  from .predictors import get_predictor, Predictor
9
9
 
10
-
11
10
  ARTIFACT_DIR = os.path.join(os.path.dirname(__file__), "artifacts")
12
11
  _vertex_initialized = False
13
12
 
@@ -77,10 +76,10 @@ class VertexModel:
77
76
  predictions = self.endpoint.predict(instances=instances, parameters=parameters)
78
77
  return predictions.predictions
79
78
 
80
- def get_local_predictor(self):
79
+ def get_local_predictor(self, device: str = "cpu"):
81
80
  dir = os.path.join(ARTIFACT_DIR, self.model_name + "-" + str(self.version))
82
81
  self.download_artifacts(dir)
83
- return get_predictor(dir)
82
+ return get_predictor(dir, device)
84
83
 
85
84
  def download_artifacts(self, dir: str):
86
85
  download_artifacts(dir, self.vertex_model.gca_resource.artifact_uri)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "orient_express"
3
- version = "2.0"
3
+ version = "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"
@@ -12,23 +12,7 @@ google-cloud-storage = "*"
12
12
  pandas = "*"
13
13
  pyyaml = "^6.0"
14
14
 
15
- [tool.poetry.group.dev.dependencies]
16
- black = "24.10.0"
17
- pytest = "8.3.3"
18
- scikit-learn = "1.5.2"
19
- xgboost = "2.1.2"
20
-
21
- [tool.poetry.group.xgboost-scikit-learn.dependencies]
22
- scikit-learn = "1.5.2"
23
15
  kserve = "0.16.0"
24
- python-json-logger = "2.0.7"
25
- gcsfs = "^2024.10.0"
26
-
27
- [tool.poetry.group.image-onnx.dependencies]
28
- onnxruntime = "^1.20" # no gpu for now
29
- kserve = "0.16.0"
30
- # torch = "^2.5.0"
31
- # torchvision = "^0.20.0"
32
16
  torch = {version = "^2.5.0", source = "pytorch-cpu"}
33
17
  torchvision = {version = "^0.20.0", source = "pytorch-cpu"}
34
18
  opencv-python-headless = "4.11.0.86"
@@ -36,6 +20,16 @@ Pillow = "10.0.1"
36
20
  python-json-logger = "2.0.7"
37
21
  gcsfs = "^2024.10.0"
38
22
 
23
+ scikit-learn = "1.5.2"
24
+ xgboost = "2.1.2"
25
+
26
+ onnxruntime-gpu = {version = "^1.20", markers = "sys_platform == 'linux' and platform_machine == 'x86_64' or sys_platform == 'win32' and platform_machine == 'AMD64'"}
27
+ onnxruntime = {version = "^1.20", markers = "sys_platform == 'darwin' or platform_machine == 'aarch64' or platform_machine == 'arm64' or sys_platform == 'win32' and platform_machine == 'ARM64'"}
28
+
29
+ [tool.poetry.group.dev.dependencies]
30
+ black = "24.10.0"
31
+ pytest = "8.3.3"
32
+
39
33
  [build-system]
40
34
  requires = ["poetry-core"]
41
35
  build-backend = "poetry.core.masonry.api"