Skip to content

Commit 32e8a5a

Browse files
authored
Replace unnamed tuple in cls result with a label object (#237)
1 parent 6e3bdb9 commit 32e8a5a

File tree

5 files changed

+62
-19
lines changed

5 files changed

+62
-19
lines changed

model_api/python/model_api/models/action_classification.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import numpy as np
1111

1212
from model_api.adapters.utils import RESIZE_TYPES, InputTransform
13+
from model_api.models.result_types import Label
1314

1415
from .model import Model
1516
from .result_types import ClassificationResult
@@ -223,7 +224,7 @@ def postprocess(
223224
logits = next(iter(outputs.values())).squeeze()
224225
index = np.argmax(logits)
225226
return ClassificationResult(
226-
[(index, self.labels[index], logits[index])],
227+
[Label(int(index), self.labels[index], logits[index])],
227228
np.ndarray(0),
228229
np.ndarray(0),
229230
np.ndarray(0),

model_api/python/model_api/models/classification.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88
import copy
99
import json
1010
from pathlib import Path
11-
from typing import TYPE_CHECKING, List, Tuple
11+
from typing import TYPE_CHECKING
1212

1313
import numpy as np
14-
from numpy import float32
1514
from openvino.preprocess import PrePostProcessor
1615
from openvino.runtime import Model, Type
1716
from openvino.runtime import opset10 as opset
1817

1918
from model_api.models.image_model import ImageModel
20-
from model_api.models.result_types import ClassificationResult
19+
from model_api.models.result_types import ClassificationResult, Label
2120
from model_api.models.types import BooleanValue, ListValue, NumericalValue, StringValue
2221
from model_api.models.utils import softmax
2322

@@ -267,7 +266,7 @@ def get_all_probs(self, logits: np.ndarray) -> np.ndarray:
267266
probs = softmax(logits.reshape(-1))
268267
return probs
269268

270-
def get_hierarchical_predictions(self, logits: np.ndarray):
269+
def get_hierarchical_predictions(self, logits: np.ndarray) -> list[Label]:
271270
predicted_labels = []
272271
predicted_scores = []
273272
cls_heads_info = self.hierarchical_info["cls_heads_info"]
@@ -294,7 +293,7 @@ def get_hierarchical_predictions(self, logits: np.ndarray):
294293
predictions = list(zip(predicted_labels, predicted_scores))
295294
return self.labels_resolver.resolve_labels(predictions)
296295

297-
def get_multilabel_predictions(self, logits: np.ndarray) -> List[Tuple[int, str, float32]]:
296+
def get_multilabel_predictions(self, logits: np.ndarray) -> list[Label]:
298297
logits = sigmoid_numpy(logits)
299298
scores = []
300299
indices = []
@@ -304,18 +303,18 @@ def get_multilabel_predictions(self, logits: np.ndarray) -> List[Tuple[int, str,
304303
scores.append(logits[i])
305304
labels = [self.labels[i] if self.labels else "" for i in indices]
306305

307-
return list(zip(indices, labels, scores))
306+
return [Label(*data) for data in zip(indices, labels, scores)]
308307

309-
def get_multiclass_predictions(self, outputs: dict) -> list[tuple[int, str, float]]:
308+
def get_multiclass_predictions(self, outputs: dict) -> list[Label]:
310309
if self.embedded_topk:
311310
indicesTensor = outputs[self.out_layer_names[0]][0]
312311
scoresTensor = outputs[self.out_layer_names[1]][0]
313312
labels = [self.labels[i] if self.labels else "" for i in indicesTensor]
314313
else:
315314
scoresTensor = softmax(outputs[self.out_layer_names[0]][0])
316-
indicesTensor = [np.argmax(scoresTensor)]
315+
indicesTensor = [int(np.argmax(scoresTensor))]
317316
labels = [self.labels[i] if self.labels else "" for i in indicesTensor]
318-
return list(zip(indicesTensor, labels, scoresTensor))
317+
return [Label(*data) for data in zip(indicesTensor, labels, scoresTensor)]
319318

320319

321320
def addOrFindSoftmaxAndTopkOutputs(inference_adapter: InferenceAdapter, topk: int, output_raw_scores: bool) -> None:
@@ -384,7 +383,7 @@ def __init__(self, hierarchical_config: dict) -> None:
384383
for child, parent in self.label_relations:
385384
self.label_tree.add_edge(parent, child)
386385

387-
def resolve_labels(self, predictions: list[tuple]) -> list:
386+
def resolve_labels(self, predictions: list[tuple]) -> list[Label]:
388387
"""Resolves hierarchical labels and exclusivity based on a list of ScoredLabels (labels with probability).
389388
The following two steps are taken:
390389
- select the most likely label from each label group
@@ -438,7 +437,7 @@ def get_predecessors(lbl: str, candidates: list[str]) -> list:
438437
if new_lbl not in output_labels:
439438
output_labels.append(new_lbl)
440439

441-
return [(self.label_to_idx[lbl], lbl, label_to_prob[lbl]) for lbl in sorted(output_labels)]
440+
return [Label(self.label_to_idx[lbl], lbl, label_to_prob[lbl]) for lbl in sorted(output_labels)]
442441

443442

444443
class ProbabilisticLabelsResolver(GreedyLabelsResolver):
@@ -447,7 +446,7 @@ def __init__(self, hierarchical_config: dict, warmup_cache: bool = True) -> None
447446
if warmup_cache:
448447
self.label_tree.get_labels_in_topological_order()
449448

450-
def resolve_labels(self, predictions: list[tuple[str, float]]) -> list[tuple[int, str, float]]:
449+
def resolve_labels(self, predictions: list[tuple[str, float]]) -> list[Label]:
451450
"""Resolves hierarchical labels and exclusivity based on a list of ScoredLabels (labels with probability).
452451
453452
The following two steps are taken:
@@ -467,7 +466,7 @@ def resolve_labels(self, predictions: list[tuple[str, float]]) -> list[tuple[int
467466
def __resolve_labels_probabilistic(
468467
self,
469468
label_to_probability: dict[str, float],
470-
) -> list[tuple[int, str, float]]:
469+
) -> list[Label]:
471470
"""Resolves hierarchical labels and exclusivity based on a probabilistic label output.
472471
473472
- selects the most likely (max) label from an exclusive group
@@ -495,7 +494,7 @@ def __resolve_labels_probabilistic(
495494
for lbl, probability in sorted(resolved.items()):
496495
if probability > 0: # only return labels with non-zero probability
497496
result.append(
498-
(
497+
Label(
499498
self.label_to_idx[lbl],
500499
lbl,
501500
# retain the original probability in the output

model_api/python/model_api/models/result_types/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# SPDX-License-Identifier: Apache-2.0
55

66
from .anomaly import AnomalyResult
7-
from .classification import ClassificationResult
7+
from .classification import ClassificationResult, Label
88
from .detection import Detection, DetectionResult
99
from .keypoint import DetectedKeypoints
1010
from .segmentation import (
@@ -23,6 +23,7 @@
2323
"Detection",
2424
"DetectionResult",
2525
"DetectedKeypoints",
26+
"Label",
2627
"SegmentedObject",
2728
"SegmentedObjectWithRects",
2829
"ImageResultWithSoftPrediction",

model_api/python/model_api/models/result_types/classification.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,42 @@
55

66
from __future__ import annotations
77

8-
from typing import TYPE_CHECKING
8+
from typing import TYPE_CHECKING, Generator
99

1010
from .utils import array_shape_to_str
1111

1212
if TYPE_CHECKING:
1313
import numpy as np
1414

1515

16+
class Label:
17+
"""Entity representing a predicted label."""
18+
19+
def __init__(
20+
self,
21+
id: int | None = None,
22+
name: str | None = None,
23+
confidence: float | None = None,
24+
) -> None:
25+
self.name = name
26+
self.confidence = confidence
27+
self.id = id
28+
29+
def __iter__(self) -> Generator:
30+
output = (self.id, self.name, self.confidence)
31+
for i in output:
32+
yield i
33+
34+
def __str__(self) -> str:
35+
return f"{self.id} ({self.name}): {self.confidence:.3f}"
36+
37+
1638
class ClassificationResult:
1739
"""Results for classification models."""
1840

1941
def __init__(
2042
self,
21-
top_labels: list[tuple[int, str, float]] | None = None,
43+
top_labels: list[Label] | None = None,
2244
saliency_map: np.ndarray | None = None,
2345
feature_vector: np.ndarray | None = None,
2446
raw_scores: np.ndarray | None = None,
@@ -30,7 +52,7 @@ def __init__(
3052

3153
def __str__(self) -> str:
3254
assert self.top_labels is not None
33-
labels = ", ".join(f"{idx} ({label}): {confidence:.3f}" for idx, label, confidence in self.top_labels)
55+
labels = ", ".join(str(label) for label in self.top_labels)
3456
return (
3557
f"{labels}, {array_shape_to_str(self.saliency_map)}, {array_shape_to_str(self.feature_vector)}, "
3658
f"{array_shape_to_str(self.raw_scores)}"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#
2+
# Copyright (C) 2020-2024 Intel Corporation
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
6+
import numpy as np
7+
from model_api.models.result_types import ClassificationResult, Label
8+
9+
10+
def test_cls_result():
11+
label = Label(1, "label", 0.5)
12+
tst_vector = np.array([1, 2, 3])
13+
cls_result = ClassificationResult([label], tst_vector, tst_vector, tst_vector)
14+
15+
assert cls_result.top_labels[0].id == 1
16+
assert cls_result.top_labels[0].name == "label"
17+
assert cls_result.top_labels[0].confidence == 0.5
18+
assert str(cls_result) == "1 (label): 0.500, [3], [3], [3]"
19+
assert cls_result.top_labels[0].__str__() == "1 (label): 0.500"
20+
assert tuple(cls_result.top_labels[0].__iter__()) == (1, "label", 0.5)

0 commit comments

Comments
 (0)