orient_express 2.4.0__tar.gz → 2.4.2__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.4.0 → orient_express-2.4.2}/PKG-INFO +186 -1
- {orient_express-2.4.0 → orient_express-2.4.2}/README.md +185 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/predictor.py +1 -1
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/semantic_segmentation.py +15 -6
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/utils/image_processor.py +39 -1
- {orient_express-2.4.0 → orient_express-2.4.2}/pyproject.toml +4 -1
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/__init__.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/deployment.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/model_wrapper.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/__init__.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/classification.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/feature_extraction.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/instance_segmentation.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/multi_label_classification.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/object_detection.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/vector_index.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/sklearn_pipeline.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/utils/colors.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/utils/gs.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/utils/paths.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/utils/retry.py +0 -0
- {orient_express-2.4.0 → orient_express-2.4.2}/orient_express/vertex.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orient_express
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
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
|
|
@@ -593,6 +593,191 @@ class SearchResult:
|
|
|
593
593
|
|
|
594
594
|
---
|
|
595
595
|
|
|
596
|
+
## Deployed Endpoint APIs
|
|
597
|
+
|
|
598
|
+
When you upload a model with orient-express and deploy it to a Vertex AI endpoint, the actual HTTP API exposed by the endpoint is determined by the serving container image — not by your Python predictor code. Orient-express ships two such images:
|
|
599
|
+
|
|
600
|
+
- `image-onnx` — serves the built-in ONNX predictor types (classification, detection, segmentation).
|
|
601
|
+
- `xgboost-scikit-learn` — serves any joblib-loadable model (sklearn pipelines, xgboost, etc.).
|
|
602
|
+
|
|
603
|
+
This section documents the request/response shape each image's endpoint exposes once deployed.
|
|
604
|
+
|
|
605
|
+
### How the docker images connect to GCP endpoints
|
|
606
|
+
|
|
607
|
+
The deployment flow is:
|
|
608
|
+
|
|
609
|
+
1. **Train + export.** You build a predictor locally (e.g. `ClassificationPredictor("model.onnx", classes)`) or train a sklearn/xgboost model.
|
|
610
|
+
2. **Upload.** `upload_model` / `upload_model_joblib` pushes the artifacts to GCS under `gs://<bucket>/models/<model_name>/<version>/` and registers a Vertex AI Model with `serving_container_image_uri` pointing at one of orient-express's images.
|
|
611
|
+
3. **Deploy.** `vertex_model.deploy_to_endpoint(...)` (or the Vertex console) attaches the registered model to a Vertex AI Endpoint. Vertex starts the container with `AIP_STORAGE_URI` set to the GCS path from step 2, plus `MODEL_NAME` set to the model's display name.
|
|
612
|
+
4. **Serve.** The container downloads the artifacts on startup, instantiates the right predictor via metadata, and listens on `/v1/models/<MODEL_NAME>:predict`.
|
|
613
|
+
5. **Call.** Clients POST to `https://<region>-aiplatform.googleapis.com/v1/projects/<project>/locations/<region>/endpoints/<endpoint_id>:predict` with a Bearer token. Vertex routes the request into the container and returns the JSON response.
|
|
614
|
+
|
|
615
|
+
Every endpoint accepts the same envelope:
|
|
616
|
+
|
|
617
|
+
```json
|
|
618
|
+
{
|
|
619
|
+
"instances": [...],
|
|
620
|
+
"parameters": {...}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
What goes in `instances` / `parameters`, and what comes back in `predictions`, is per-image and per-predictor — covered below.
|
|
625
|
+
|
|
626
|
+
### ONNX Image Endpoint
|
|
627
|
+
|
|
628
|
+
Image: `us-west1-docker.pkg.dev/shiftsmart-api/orient-express/image-onnx:<tag>`
|
|
629
|
+
|
|
630
|
+
Common request shape across all ONNX predictor types:
|
|
631
|
+
|
|
632
|
+
```json
|
|
633
|
+
{
|
|
634
|
+
"instances": [
|
|
635
|
+
{"image": "<http(s) URL | gs:// URI | base64 | data URI>"}
|
|
636
|
+
],
|
|
637
|
+
"parameters": {
|
|
638
|
+
"confidence": 0.5
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Common response envelope:
|
|
644
|
+
|
|
645
|
+
```json
|
|
646
|
+
{
|
|
647
|
+
"predictions": [
|
|
648
|
+
{"status": "success", ...predictor-specific fields...}
|
|
649
|
+
]
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
`status` values: `"success"`, `"failed to download image"`, or `"failed to get debug image"`. Malformed payloads return top-level `{"error": "Failed to decode input"}` instead of `predictions`.
|
|
654
|
+
|
|
655
|
+
The predictor-specific fields differ by model type — covered in the subsections below.
|
|
656
|
+
|
|
657
|
+
#### Classification
|
|
658
|
+
|
|
659
|
+
For models uploaded as `ClassificationPredictor`. `parameters.confidence` is **not** honored (the predictor always returns the top class).
|
|
660
|
+
|
|
661
|
+
Per-image response:
|
|
662
|
+
|
|
663
|
+
```json
|
|
664
|
+
{
|
|
665
|
+
"status": "success",
|
|
666
|
+
"class": "cat",
|
|
667
|
+
"score": 0.95,
|
|
668
|
+
"class_scores": {"cat": 0.95, "dog": 0.03, "bird": 0.02}
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
No `debug_image` for classification (nothing meaningful to draw).
|
|
673
|
+
|
|
674
|
+
#### Multi-label classification
|
|
675
|
+
|
|
676
|
+
For models uploaded as `MultiLabelClassificationPredictor`. `parameters.confidence` is the per-class threshold for inclusion in `classes` (default `0.5`).
|
|
677
|
+
|
|
678
|
+
Per-image response:
|
|
679
|
+
|
|
680
|
+
```json
|
|
681
|
+
{
|
|
682
|
+
"status": "success",
|
|
683
|
+
"predictions": {
|
|
684
|
+
"classes": ["contains_cat", "contains_bird"],
|
|
685
|
+
"class_scores": {"contains_cat": 0.95, "contains_dog": 0.03, "contains_bird": 0.82}
|
|
686
|
+
},
|
|
687
|
+
"debug_image": null
|
|
688
|
+
}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
`debug_image` is always `null` for multi-label.
|
|
692
|
+
|
|
693
|
+
#### Object detection
|
|
694
|
+
|
|
695
|
+
For models uploaded as `BoundingBoxPredictor`. `parameters.confidence` filters detections below the threshold (default `0.5`).
|
|
696
|
+
|
|
697
|
+
Per-image response:
|
|
698
|
+
|
|
699
|
+
```json
|
|
700
|
+
{
|
|
701
|
+
"status": "success",
|
|
702
|
+
"predictions": [
|
|
703
|
+
{"class": "person", "score": 0.92, "bbox": {"x1": 100.5, "y1": 50.2, "x2": 300.8, "y2": 400.1}}
|
|
704
|
+
],
|
|
705
|
+
"debug_image": "<base64 JPEG with boxes overlaid>"
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
`bbox` coordinates are in pixels of the original (EXIF-corrected) image. `predictions` is an empty list when nothing clears the confidence threshold.
|
|
710
|
+
|
|
711
|
+
#### Instance segmentation
|
|
712
|
+
|
|
713
|
+
For models uploaded as `InstanceSegmentationPredictor`. `parameters.confidence` filters detections (default `0.5`).
|
|
714
|
+
|
|
715
|
+
Per-image response:
|
|
716
|
+
|
|
717
|
+
```json
|
|
718
|
+
{
|
|
719
|
+
"status": "success",
|
|
720
|
+
"predictions": [
|
|
721
|
+
{"class": "person", "score": 0.89, "bbox": {"x1": 100.5, "y1": 50.2, "x2": 300.8, "y2": 400.1}}
|
|
722
|
+
],
|
|
723
|
+
"debug_image": "<base64 JPEG with masks overlaid>"
|
|
724
|
+
}
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
Per-instance mask arrays are **not** included in the response by default (too large). The annotated mask overlay is baked into `debug_image`.
|
|
728
|
+
|
|
729
|
+
#### Semantic segmentation
|
|
730
|
+
|
|
731
|
+
For models uploaded as `SemanticSegmentationPredictor`. `parameters.confidence` is the per-pixel threshold above which a class is considered "valid" (default `0.5`).
|
|
732
|
+
|
|
733
|
+
Per-image response:
|
|
734
|
+
|
|
735
|
+
```json
|
|
736
|
+
{
|
|
737
|
+
"status": "success",
|
|
738
|
+
"predictions": {
|
|
739
|
+
"class_mask": "<base64 PNG, uint8, per-pixel class id>",
|
|
740
|
+
"valid_mask": "<base64 PNG, uint8, 0=below threshold, 1=above>"
|
|
741
|
+
},
|
|
742
|
+
"debug_image": "<base64 JPEG with color-coded overlay>"
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
`class_mask` always paints every pixel with the argmax winner. `valid_mask` tells you which pixels actually cleared the confidence threshold — AND them together client-side to get the "real" segmentation.
|
|
747
|
+
|
|
748
|
+
### XGBoost / scikit-learn Endpoint
|
|
749
|
+
|
|
750
|
+
Image: `us-west1-docker.pkg.dev/shiftsmart-api/orient-express/xgboost-scikit-learn:<tag>`
|
|
751
|
+
|
|
752
|
+
For models uploaded via `upload_model_joblib` — sklearn pipelines, xgboost models, or anything `joblib.load`-able with a `.predict(DataFrame)` method.
|
|
753
|
+
|
|
754
|
+
Request shape — `instances` is a list of dicts, one row per input:
|
|
755
|
+
|
|
756
|
+
```json
|
|
757
|
+
{
|
|
758
|
+
"instances": [
|
|
759
|
+
{"pclass": 1, "sex": "female", "age": 29, "fare": 100.0, "embarked": "S"},
|
|
760
|
+
{"pclass": 3, "sex": "male", "age": 35, "fare": 8.05, "embarked": "S"}
|
|
761
|
+
]
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
The server constructs `pd.DataFrame(instances)` and calls `model.predict(df)` on it. The columns your pipeline expects must be present in each instance dict.
|
|
766
|
+
|
|
767
|
+
Response shape — one prediction per input row:
|
|
768
|
+
|
|
769
|
+
```json
|
|
770
|
+
{
|
|
771
|
+
"predictions": [0, 1]
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
Each element is whatever your `.predict()` returns — a class label for classifiers, a numeric value for regressors, an array for multi-output models.
|
|
776
|
+
|
|
777
|
+
`parameters` is ignored — there's no per-request configuration for this image.
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
596
781
|
## Color Schemes
|
|
597
782
|
|
|
598
783
|
For predictors that support annotation (`BoundingBoxPredictor`, `InstanceSegmentationPredictor`, `SemanticSegmentationPredictor`), you can set a custom color scheme:
|
|
@@ -566,6 +566,191 @@ class SearchResult:
|
|
|
566
566
|
|
|
567
567
|
---
|
|
568
568
|
|
|
569
|
+
## Deployed Endpoint APIs
|
|
570
|
+
|
|
571
|
+
When you upload a model with orient-express and deploy it to a Vertex AI endpoint, the actual HTTP API exposed by the endpoint is determined by the serving container image — not by your Python predictor code. Orient-express ships two such images:
|
|
572
|
+
|
|
573
|
+
- `image-onnx` — serves the built-in ONNX predictor types (classification, detection, segmentation).
|
|
574
|
+
- `xgboost-scikit-learn` — serves any joblib-loadable model (sklearn pipelines, xgboost, etc.).
|
|
575
|
+
|
|
576
|
+
This section documents the request/response shape each image's endpoint exposes once deployed.
|
|
577
|
+
|
|
578
|
+
### How the docker images connect to GCP endpoints
|
|
579
|
+
|
|
580
|
+
The deployment flow is:
|
|
581
|
+
|
|
582
|
+
1. **Train + export.** You build a predictor locally (e.g. `ClassificationPredictor("model.onnx", classes)`) or train a sklearn/xgboost model.
|
|
583
|
+
2. **Upload.** `upload_model` / `upload_model_joblib` pushes the artifacts to GCS under `gs://<bucket>/models/<model_name>/<version>/` and registers a Vertex AI Model with `serving_container_image_uri` pointing at one of orient-express's images.
|
|
584
|
+
3. **Deploy.** `vertex_model.deploy_to_endpoint(...)` (or the Vertex console) attaches the registered model to a Vertex AI Endpoint. Vertex starts the container with `AIP_STORAGE_URI` set to the GCS path from step 2, plus `MODEL_NAME` set to the model's display name.
|
|
585
|
+
4. **Serve.** The container downloads the artifacts on startup, instantiates the right predictor via metadata, and listens on `/v1/models/<MODEL_NAME>:predict`.
|
|
586
|
+
5. **Call.** Clients POST to `https://<region>-aiplatform.googleapis.com/v1/projects/<project>/locations/<region>/endpoints/<endpoint_id>:predict` with a Bearer token. Vertex routes the request into the container and returns the JSON response.
|
|
587
|
+
|
|
588
|
+
Every endpoint accepts the same envelope:
|
|
589
|
+
|
|
590
|
+
```json
|
|
591
|
+
{
|
|
592
|
+
"instances": [...],
|
|
593
|
+
"parameters": {...}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
What goes in `instances` / `parameters`, and what comes back in `predictions`, is per-image and per-predictor — covered below.
|
|
598
|
+
|
|
599
|
+
### ONNX Image Endpoint
|
|
600
|
+
|
|
601
|
+
Image: `us-west1-docker.pkg.dev/shiftsmart-api/orient-express/image-onnx:<tag>`
|
|
602
|
+
|
|
603
|
+
Common request shape across all ONNX predictor types:
|
|
604
|
+
|
|
605
|
+
```json
|
|
606
|
+
{
|
|
607
|
+
"instances": [
|
|
608
|
+
{"image": "<http(s) URL | gs:// URI | base64 | data URI>"}
|
|
609
|
+
],
|
|
610
|
+
"parameters": {
|
|
611
|
+
"confidence": 0.5
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
Common response envelope:
|
|
617
|
+
|
|
618
|
+
```json
|
|
619
|
+
{
|
|
620
|
+
"predictions": [
|
|
621
|
+
{"status": "success", ...predictor-specific fields...}
|
|
622
|
+
]
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
`status` values: `"success"`, `"failed to download image"`, or `"failed to get debug image"`. Malformed payloads return top-level `{"error": "Failed to decode input"}` instead of `predictions`.
|
|
627
|
+
|
|
628
|
+
The predictor-specific fields differ by model type — covered in the subsections below.
|
|
629
|
+
|
|
630
|
+
#### Classification
|
|
631
|
+
|
|
632
|
+
For models uploaded as `ClassificationPredictor`. `parameters.confidence` is **not** honored (the predictor always returns the top class).
|
|
633
|
+
|
|
634
|
+
Per-image response:
|
|
635
|
+
|
|
636
|
+
```json
|
|
637
|
+
{
|
|
638
|
+
"status": "success",
|
|
639
|
+
"class": "cat",
|
|
640
|
+
"score": 0.95,
|
|
641
|
+
"class_scores": {"cat": 0.95, "dog": 0.03, "bird": 0.02}
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
No `debug_image` for classification (nothing meaningful to draw).
|
|
646
|
+
|
|
647
|
+
#### Multi-label classification
|
|
648
|
+
|
|
649
|
+
For models uploaded as `MultiLabelClassificationPredictor`. `parameters.confidence` is the per-class threshold for inclusion in `classes` (default `0.5`).
|
|
650
|
+
|
|
651
|
+
Per-image response:
|
|
652
|
+
|
|
653
|
+
```json
|
|
654
|
+
{
|
|
655
|
+
"status": "success",
|
|
656
|
+
"predictions": {
|
|
657
|
+
"classes": ["contains_cat", "contains_bird"],
|
|
658
|
+
"class_scores": {"contains_cat": 0.95, "contains_dog": 0.03, "contains_bird": 0.82}
|
|
659
|
+
},
|
|
660
|
+
"debug_image": null
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
`debug_image` is always `null` for multi-label.
|
|
665
|
+
|
|
666
|
+
#### Object detection
|
|
667
|
+
|
|
668
|
+
For models uploaded as `BoundingBoxPredictor`. `parameters.confidence` filters detections below the threshold (default `0.5`).
|
|
669
|
+
|
|
670
|
+
Per-image response:
|
|
671
|
+
|
|
672
|
+
```json
|
|
673
|
+
{
|
|
674
|
+
"status": "success",
|
|
675
|
+
"predictions": [
|
|
676
|
+
{"class": "person", "score": 0.92, "bbox": {"x1": 100.5, "y1": 50.2, "x2": 300.8, "y2": 400.1}}
|
|
677
|
+
],
|
|
678
|
+
"debug_image": "<base64 JPEG with boxes overlaid>"
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
`bbox` coordinates are in pixels of the original (EXIF-corrected) image. `predictions` is an empty list when nothing clears the confidence threshold.
|
|
683
|
+
|
|
684
|
+
#### Instance segmentation
|
|
685
|
+
|
|
686
|
+
For models uploaded as `InstanceSegmentationPredictor`. `parameters.confidence` filters detections (default `0.5`).
|
|
687
|
+
|
|
688
|
+
Per-image response:
|
|
689
|
+
|
|
690
|
+
```json
|
|
691
|
+
{
|
|
692
|
+
"status": "success",
|
|
693
|
+
"predictions": [
|
|
694
|
+
{"class": "person", "score": 0.89, "bbox": {"x1": 100.5, "y1": 50.2, "x2": 300.8, "y2": 400.1}}
|
|
695
|
+
],
|
|
696
|
+
"debug_image": "<base64 JPEG with masks overlaid>"
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
Per-instance mask arrays are **not** included in the response by default (too large). The annotated mask overlay is baked into `debug_image`.
|
|
701
|
+
|
|
702
|
+
#### Semantic segmentation
|
|
703
|
+
|
|
704
|
+
For models uploaded as `SemanticSegmentationPredictor`. `parameters.confidence` is the per-pixel threshold above which a class is considered "valid" (default `0.5`).
|
|
705
|
+
|
|
706
|
+
Per-image response:
|
|
707
|
+
|
|
708
|
+
```json
|
|
709
|
+
{
|
|
710
|
+
"status": "success",
|
|
711
|
+
"predictions": {
|
|
712
|
+
"class_mask": "<base64 PNG, uint8, per-pixel class id>",
|
|
713
|
+
"valid_mask": "<base64 PNG, uint8, 0=below threshold, 1=above>"
|
|
714
|
+
},
|
|
715
|
+
"debug_image": "<base64 JPEG with color-coded overlay>"
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
`class_mask` always paints every pixel with the argmax winner. `valid_mask` tells you which pixels actually cleared the confidence threshold — AND them together client-side to get the "real" segmentation.
|
|
720
|
+
|
|
721
|
+
### XGBoost / scikit-learn Endpoint
|
|
722
|
+
|
|
723
|
+
Image: `us-west1-docker.pkg.dev/shiftsmart-api/orient-express/xgboost-scikit-learn:<tag>`
|
|
724
|
+
|
|
725
|
+
For models uploaded via `upload_model_joblib` — sklearn pipelines, xgboost models, or anything `joblib.load`-able with a `.predict(DataFrame)` method.
|
|
726
|
+
|
|
727
|
+
Request shape — `instances` is a list of dicts, one row per input:
|
|
728
|
+
|
|
729
|
+
```json
|
|
730
|
+
{
|
|
731
|
+
"instances": [
|
|
732
|
+
{"pclass": 1, "sex": "female", "age": 29, "fare": 100.0, "embarked": "S"},
|
|
733
|
+
{"pclass": 3, "sex": "male", "age": 35, "fare": 8.05, "embarked": "S"}
|
|
734
|
+
]
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
The server constructs `pd.DataFrame(instances)` and calls `model.predict(df)` on it. The columns your pipeline expects must be present in each instance dict.
|
|
739
|
+
|
|
740
|
+
Response shape — one prediction per input row:
|
|
741
|
+
|
|
742
|
+
```json
|
|
743
|
+
{
|
|
744
|
+
"predictions": [0, 1]
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
Each element is whatever your `.predict()` returns — a class label for classifiers, a numeric value for regressors, an array for multi-output models.
|
|
749
|
+
|
|
750
|
+
`parameters` is ignored — there's no per-request configuration for this image.
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
569
754
|
## Color Schemes
|
|
570
755
|
|
|
571
756
|
For predictors that support annotation (`BoundingBoxPredictor`, `InstanceSegmentationPredictor`, `SemanticSegmentationPredictor`), you can set a custom color scheme:
|
|
@@ -46,7 +46,7 @@ class ImagePredictor(Predictor):
|
|
|
46
46
|
self.model_path = model_path
|
|
47
47
|
|
|
48
48
|
def get_serving_container_image_uri(self):
|
|
49
|
-
return "us-west1-docker.pkg.dev/shiftsmart-api/orient-express/image-onnx:v2.4.
|
|
49
|
+
return "us-west1-docker.pkg.dev/shiftsmart-api/orient-express/image-onnx:v2.4.2"
|
|
50
50
|
|
|
51
51
|
def get_serving_container_health_route(self, model_name):
|
|
52
52
|
return f"/v1/models/{model_name}"
|
{orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/semantic_segmentation.py
RENAMED
|
@@ -7,17 +7,19 @@ import torch
|
|
|
7
7
|
import torch.nn.functional as F
|
|
8
8
|
|
|
9
9
|
from .predictor import OnnxSessionWrapper, ImagePredictor
|
|
10
|
-
from ..utils.image_processor import pil_to_opencv, opencv_to_pil
|
|
10
|
+
from ..utils.image_processor import mask_to_base64, pil_to_opencv, opencv_to_pil
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@dataclass
|
|
14
14
|
class SemanticSegmentationPrediction:
|
|
15
15
|
class_mask: np.ndarray
|
|
16
|
+
valid_mask: np.ndarray
|
|
16
17
|
conf_masks: np.ndarray
|
|
17
18
|
|
|
18
19
|
def to_dict(self, include_conf_masks: bool = False):
|
|
19
20
|
dict_repr = {
|
|
20
|
-
"class_mask": self.class_mask,
|
|
21
|
+
"class_mask": mask_to_base64(self.class_mask.astype(np.uint8)),
|
|
22
|
+
"valid_mask": mask_to_base64(self.valid_mask.astype(np.uint8)),
|
|
21
23
|
}
|
|
22
24
|
if include_conf_masks:
|
|
23
25
|
dict_repr["conf_masks"] = self.conf_masks.tolist()
|
|
@@ -60,31 +62,38 @@ class SemanticSegmentationPredictor(ImagePredictor):
|
|
|
60
62
|
backend_model = OnnxSemanticSegmentation
|
|
61
63
|
|
|
62
64
|
def predict(
|
|
63
|
-
self, images: list[Image.Image]
|
|
65
|
+
self, images: list[Image.Image], confidence: float = 0.5
|
|
64
66
|
) -> list[SemanticSegmentationPrediction]:
|
|
65
67
|
if not images:
|
|
66
68
|
return []
|
|
67
69
|
raw_outputs = self.model(images)
|
|
68
70
|
outputs = []
|
|
69
71
|
for masks in raw_outputs:
|
|
70
|
-
class_mask = np.argmax(masks, axis=0)
|
|
72
|
+
class_mask = np.argmax(masks, axis=0).astype(np.uint8)
|
|
73
|
+
valid_mask = np.max(masks, axis=0) >= confidence
|
|
71
74
|
outputs.append(
|
|
72
75
|
SemanticSegmentationPrediction(
|
|
73
76
|
class_mask=class_mask,
|
|
77
|
+
valid_mask=valid_mask,
|
|
74
78
|
conf_masks=masks,
|
|
75
79
|
)
|
|
76
80
|
)
|
|
77
81
|
return outputs
|
|
78
82
|
|
|
79
83
|
def get_annotated_image(
|
|
80
|
-
self,
|
|
84
|
+
self,
|
|
85
|
+
image: Image.Image,
|
|
86
|
+
prediction: SemanticSegmentationPrediction,
|
|
87
|
+
mask_opacity: float = 0.3,
|
|
81
88
|
) -> Image.Image:
|
|
82
89
|
opencv_image = pil_to_opencv(image)
|
|
83
90
|
mask_overlay = opencv_image.copy()
|
|
84
91
|
|
|
85
92
|
for class_id, class_name in self.classes.items():
|
|
86
93
|
fill_color = self.color_scheme.get(class_name, (120, 120, 120))
|
|
87
|
-
mask_overlay[
|
|
94
|
+
mask_overlay[
|
|
95
|
+
(prediction.class_mask == class_id) & prediction.valid_mask
|
|
96
|
+
] = fill_color[:3]
|
|
88
97
|
|
|
89
98
|
annotated_image = cv2.addWeighted(
|
|
90
99
|
mask_overlay, mask_opacity, opencv_image, 1 - mask_opacity, 0
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import ipaddress
|
|
2
3
|
import logging
|
|
4
|
+
import socket
|
|
3
5
|
from io import BytesIO
|
|
6
|
+
from urllib.parse import urlparse
|
|
4
7
|
|
|
5
8
|
import cv2
|
|
6
9
|
import numpy as np
|
|
@@ -9,6 +12,34 @@ import requests
|
|
|
9
12
|
|
|
10
13
|
from .gs import get_gcs_from_http_url, read_file_bytes
|
|
11
14
|
|
|
15
|
+
DOWNLOAD_TIMEOUT_SECONDS = 30
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class UnsafeUrlError(ValueError):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_url(http_url):
|
|
23
|
+
# Reject URLs that could be used for SSRF: the request runs server-side
|
|
24
|
+
# with the service account's network access, so only allow http(s) URLs
|
|
25
|
+
# that resolve to public IPs (blocks the GCE metadata server, localhost,
|
|
26
|
+
# and private/internal ranges).
|
|
27
|
+
parsed = urlparse(http_url)
|
|
28
|
+
if parsed.scheme not in ("http", "https"):
|
|
29
|
+
raise UnsafeUrlError(f"unsupported URL scheme: {parsed.scheme}")
|
|
30
|
+
if not parsed.hostname:
|
|
31
|
+
raise UnsafeUrlError("URL has no hostname")
|
|
32
|
+
try:
|
|
33
|
+
addr_infos = socket.getaddrinfo(parsed.hostname, None)
|
|
34
|
+
except socket.gaierror as e:
|
|
35
|
+
raise UnsafeUrlError(f"could not resolve host: {parsed.hostname}") from e
|
|
36
|
+
for addr_info in addr_infos:
|
|
37
|
+
ip = ipaddress.ip_address(addr_info[4][0])
|
|
38
|
+
if not ip.is_global:
|
|
39
|
+
raise UnsafeUrlError(
|
|
40
|
+
f"URL host {parsed.hostname} resolves to non-public address {ip}"
|
|
41
|
+
)
|
|
42
|
+
|
|
12
43
|
|
|
13
44
|
def read_image_from_url(http_url, http_as_gcs=False) -> Image.Image:
|
|
14
45
|
# Extract GSC URI from http link and download the file directly.
|
|
@@ -19,7 +50,8 @@ def read_image_from_url(http_url, http_as_gcs=False) -> Image.Image:
|
|
|
19
50
|
if gs_uri:
|
|
20
51
|
return read_image_from_gs(gs_uri)
|
|
21
52
|
|
|
22
|
-
|
|
53
|
+
validate_url(http_url)
|
|
54
|
+
response = requests.get(http_url, timeout=DOWNLOAD_TIMEOUT_SECONDS)
|
|
23
55
|
response.raise_for_status()
|
|
24
56
|
image = Image.open(BytesIO(response.content))
|
|
25
57
|
return image
|
|
@@ -74,6 +106,12 @@ def image_to_base64(image):
|
|
|
74
106
|
return base64.b64encode(bytes_content).decode("utf-8")
|
|
75
107
|
|
|
76
108
|
|
|
109
|
+
def mask_to_base64(mask: np.ndarray) -> str:
|
|
110
|
+
buffered = BytesIO()
|
|
111
|
+
Image.fromarray(mask).save(buffered, format="PNG")
|
|
112
|
+
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
113
|
+
|
|
114
|
+
|
|
77
115
|
def image_to_bytes(image):
|
|
78
116
|
buffered = BytesIO()
|
|
79
117
|
if image.mode == "RGBA":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "orient_express"
|
|
3
|
-
version = "2.4.
|
|
3
|
+
version = "2.4.2"
|
|
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"
|
|
@@ -25,6 +25,9 @@ onnxruntime = {version = "^1.20", markers = "sys_platform == 'darwin' or platfor
|
|
|
25
25
|
[tool.poetry.group.dev.dependencies]
|
|
26
26
|
black = "24.10.0"
|
|
27
27
|
pytest = "8.3.3"
|
|
28
|
+
ipykernel = "^7.2.0"
|
|
29
|
+
|
|
30
|
+
[tool.poetry.group.server.dependencies]
|
|
28
31
|
kserve = "^0.16.0"
|
|
29
32
|
python-json-logger = "^4.0.0"
|
|
30
33
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/feature_extraction.py
RENAMED
|
File without changes
|
{orient_express-2.4.0 → orient_express-2.4.2}/orient_express/predictors/instance_segmentation.py
RENAMED
|
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
|