Skip to content

Commit 2a3099b

Browse files
Merge branch 'main' into cf-e2e
2 parents 1360c2b + eb8b8a0 commit 2a3099b

File tree

6 files changed

+121
-6
lines changed

6 files changed

+121
-6
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
supervision[desktop]==0.13.0
2-
requests==2.32.0
2+
requests==2.32.4
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
matplotlib==3.7.1
22
opencv-python>=4.8.1.78,<=4.10.0.84
33
pandas==2.0.2
4-
Requests==2.31.0
4+
Requests==2.32.4

inference/core/registries/roboflow.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ def get_model(self, model_id: ModelID, api_key: str) -> Model:
8585
logger.debug(f"Model type: {model_type}")
8686

8787
if model_type not in self.registry_dict:
88-
raise ModelNotRecognisedError(f"Model type not supported: {model_type}")
88+
raise ModelNotRecognisedError(
89+
f"Model type not supported, you may want to try a different inference server configuration or endpoint: {model_type}"
90+
)
8991
return self.registry_dict[model_type]
9092

9193

inference/core/workflows/core_steps/fusion/detections_classes_replacement/v1.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Dict, List, Literal, Optional, Tuple, Type
1+
import sys
2+
from typing import Dict, List, Literal, Optional, Tuple, Type, Union
23
from uuid import uuid4
34

45
import numpy as np
@@ -17,8 +18,10 @@
1718
from inference.core.workflows.execution_engine.entities.types import (
1819
CLASSIFICATION_PREDICTION_KIND,
1920
INSTANCE_SEGMENTATION_PREDICTION_KIND,
21+
INTEGER_KIND,
2022
KEYPOINT_DETECTION_PREDICTION_KIND,
2123
OBJECT_DETECTION_PREDICTION_KIND,
24+
STRING_KIND,
2225
Selector,
2326
)
2427
from inference.core.workflows.prototypes.block import (
@@ -75,6 +78,19 @@ class BlockManifest(WorkflowBlockManifest):
7578
description="The output of classification model for crops taken based on RoIs pointed as the other parameter",
7679
examples=["$steps.my_classification_model.predictions"],
7780
)
81+
fallback_class_name: Union[Optional[str], Selector(kind=[STRING_KIND])] = Field(
82+
default=None,
83+
title="Fallback class name",
84+
description="The class name to be used as a fallback if no class is predicted for a bounding box",
85+
examples=["unknown"],
86+
)
87+
fallback_class_id: Union[Optional[int], Selector(kind=[INTEGER_KIND])] = Field(
88+
default=None,
89+
title="Fallback class id",
90+
description="The class id to be used as a fallback if no class is predicted for a bounding box;"
91+
f"if not specified or negative, the class id will be set to {sys.maxsize}",
92+
examples=[77],
93+
)
7894

7995
@classmethod
8096
def accepts_empty_values(cls) -> bool:
@@ -119,6 +135,8 @@ def run(
119135
self,
120136
object_detection_predictions: Optional[sv.Detections],
121137
classification_predictions: Optional[Batch[Optional[dict]]],
138+
fallback_class_name: Optional[str],
139+
fallback_class_id: Optional[int],
122140
) -> BlockResult:
123141
if object_detection_predictions is None:
124142
return {"predictions": None}
@@ -131,7 +149,9 @@ def run(
131149
return {"predictions": sv.Detections.empty()}
132150
detection_id_by_class: Dict[str, Optional[Tuple[str, int]]] = {
133151
prediction[PARENT_ID_KEY]: extract_leading_class_from_prediction(
134-
prediction=prediction
152+
prediction=prediction,
153+
fallback_class_name=fallback_class_name,
154+
fallback_class_id=fallback_class_id,
135155
)
136156
for prediction in classification_predictions
137157
if prediction is not None
@@ -180,8 +200,20 @@ def run(
180200

181201
def extract_leading_class_from_prediction(
182202
prediction: dict,
203+
fallback_class_name: Optional[str] = None,
204+
fallback_class_id: Optional[int] = None,
183205
) -> Optional[Tuple[str, int, float]]:
184206
if "top" in prediction:
207+
if not prediction.get("predictions") and not fallback_class_name:
208+
return None
209+
elif not prediction.get("predictions") and fallback_class_name:
210+
try:
211+
fallback_class_id = int(fallback_class_id)
212+
except ValueError:
213+
fallback_class_id = None
214+
if fallback_class_id is None or fallback_class_id < 0:
215+
fallback_class_id = sys.maxsize
216+
return fallback_class_name, fallback_class_id, 0
185217
class_name = prediction["top"]
186218
matching_class_ids = [
187219
(p["class_id"], p["confidence"])

inference/usage_tracking/collector.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from inference.core.env import (
2929
API_KEY,
3030
DEDICATED_DEPLOYMENT_ID,
31+
DEVICE_ID,
3132
GCP_SERVERLESS,
3233
LAMBDA,
3334
REDIS_HOST,
@@ -578,6 +579,8 @@ def _extract_usage_params_from_func_kwargs(
578579
}
579580
if DEDICATED_DEPLOYMENT_ID:
580581
resource_details["dedicated_deployment_id"] = DEDICATED_DEPLOYMENT_ID
582+
if DEVICE_ID:
583+
resource_details["device_id"] = DEVICE_ID
581584
resource_id = ""
582585
# TODO: add requires_api_key, True if workflow definition comes from platform or model comes from workspace
583586
if category == "workflows":

tests/workflows/unit_tests/core_steps/fusion/test_detections_classes_replacement.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def test_classes_replacement_when_object_detection_object_is_none() -> None:
2424
result = step.run(
2525
object_detection_predictions=None,
2626
classification_predictions=None,
27+
fallback_class_name=None,
28+
fallback_class_id=None,
2729
)
2830

2931
# then
@@ -43,6 +45,8 @@ def test_classes_replacement_when_there_are_no_predictions_is_none() -> None:
4345
result = step.run(
4446
object_detection_predictions=detections,
4547
classification_predictions=None,
48+
fallback_class_name=None,
49+
fallback_class_id=None,
4650
)
4751

4852
# then
@@ -100,6 +104,8 @@ def test_classes_replacement_when_replacement_to_happen_without_filtering_for_mu
100104
result = step.run(
101105
object_detection_predictions=detections,
102106
classification_predictions=classification_predictions,
107+
fallback_class_name=None,
108+
fallback_class_id=None,
103109
)
104110

105111
# then
@@ -183,6 +189,8 @@ def test_classes_replacement_when_replacement_to_happen_without_filtering_for_mu
183189
result = step.run(
184190
object_detection_predictions=detections,
185191
classification_predictions=classification_predictions,
192+
fallback_class_name=None,
193+
fallback_class_id=None,
186194
)
187195

188196
# then
@@ -245,6 +253,8 @@ def test_classes_replacement_when_replacement_to_happen_and_one_result_to_be_fil
245253
result = step.run(
246254
object_detection_predictions=detections,
247255
classification_predictions=classification_predictions,
256+
fallback_class_name=None,
257+
fallback_class_id=None,
248258
)
249259

250260
# then
@@ -271,7 +281,7 @@ def test_classes_replacement_when_replacement_to_happen_and_one_result_to_be_fil
271281
], "Expected to generate new detection id"
272282

273283

274-
def test_classes_replacement_when_empty_classification_predictions():
284+
def test_classes_replacement_when_empty_classification_predictions_no_fallback_class():
275285
# given
276286
step = DetectionsClassesReplacementBlockV1()
277287
detections = sv.Detections(
@@ -305,6 +315,8 @@ def test_classes_replacement_when_empty_classification_predictions():
305315
result = step.run(
306316
object_detection_predictions=detections,
307317
classification_predictions=classification_predictions,
318+
fallback_class_name=None,
319+
fallback_class_id=None,
308320
)
309321

310322
# then
@@ -313,6 +325,72 @@ def test_classes_replacement_when_empty_classification_predictions():
313325
), "Expected sv.Detections.empty(), as empty classification was passed"
314326

315327

328+
def test_classes_replacement_when_empty_classification_predictions_fallback_class_provided():
329+
# given
330+
step = DetectionsClassesReplacementBlockV1()
331+
detections = sv.Detections(
332+
xyxy=np.array(
333+
[
334+
[10, 20, 30, 40],
335+
[11, 21, 31, 41],
336+
]
337+
),
338+
class_id=np.array([7, 7]),
339+
confidence=np.array([0.36, 0.91]),
340+
data={
341+
"class_name": np.array(["animal", "animal"]),
342+
"detection_id": np.array(["zero", "one"]),
343+
},
344+
)
345+
first_cls_prediction = ClassificationInferenceResponse(
346+
image=InferenceResponseImage(width=128, height=256),
347+
predictions=[
348+
ClassificationPrediction(
349+
**{"class": "cat", "class_id": 0, "confidence": 0.6}
350+
),
351+
ClassificationPrediction(
352+
**{"class": "dog", "class_id": 1, "confidence": 0.4}
353+
),
354+
],
355+
top="cat",
356+
confidence=0.6,
357+
parent_id="some",
358+
).dict(by_alias=True, exclude_none=True)
359+
first_cls_prediction["parent_id"] = "zero"
360+
second_cls_prediction = ClassificationInferenceResponse(
361+
image=InferenceResponseImage(width=128, height=256),
362+
predictions=[],
363+
top="cat",
364+
confidence=0.6,
365+
parent_id="some",
366+
).dict(by_alias=True, exclude_none=True)
367+
second_cls_prediction["parent_id"] = "one"
368+
classification_predictions = Batch(
369+
content=[
370+
first_cls_prediction,
371+
second_cls_prediction,
372+
],
373+
indices=[(0, 0), (0, 1)],
374+
)
375+
376+
# when
377+
result = step.run(
378+
object_detection_predictions=detections,
379+
classification_predictions=classification_predictions,
380+
fallback_class_name="unknown",
381+
fallback_class_id=123,
382+
)
383+
384+
# then
385+
assert (
386+
len(result["predictions"]) == 2
387+
), "Expected sv.Detections.empty(), as empty classification was passed"
388+
detections = result["predictions"]
389+
assert detections.confidence[1] == 0, "Fallback class confidence expected to be set to 0"
390+
assert detections.class_id[1] == 123, "class id expected to be set to value passed with fallback_class_id parameter"
391+
assert detections.data["class_name"][1] == "unknown", "class name expected to be set to value passed with fallback_class_name parameter"
392+
393+
316394
def test_extract_leading_class_from_prediction_when_prediction_is_multi_label() -> None:
317395
# given
318396
prediction = ClassificationInferenceResponse(

0 commit comments

Comments
 (0)