diff --git a/CHANGELOG.md b/CHANGELOG.md index 044503b8ef0..50181c62685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## \[unreleased\] +## \[v1.6.0\] + ### New features - Add zero-shot visual prompting (, , ) @@ -13,6 +15,18 @@ All notable changes to this project will be documented in this file. - Upgrade OpenVINO to 2023.3 () - Automate performance benchmark () +- Bump ONNX version to 1.16.0 to resolve CVE-2022-25882 () + +## \[v1.5.2\] + +> **NOTES** +> +> OpenVINO™ Training Extension v1.5.2 does not include the latest functional and security updates. OpenVINO™ Training Extension v2.0.0 is targeted to be released in April 2024 and will include additional functional and security updates. Customers should update to the latest version as it becomes available. + +### Bug fixes + +- Remove polygon clipping code () +- Hotfix default memcache size to 100MB () ## \[v1.5.0\] @@ -49,6 +63,17 @@ All notable changes to this project will be documented in this file. - OpenVINO(==2023.0) IR inference is not working well on 2-stage models (e.g. Mask-RCNN) exported from torch>=1.13.1 - NNCF QAT optimization is disabled for MaskRCNN models due to CUDA runtime error in ROIAlign kernel on torch==2.0.1 +## \[v1.4.5\] + +### Bug fixes + +- Filter invalid polygon shapes () +- 🐞 Bugfix: Set reverse*input_channels to True in OpenVINO models (* + +### Misc + +- Remove dependency of protobuf to omit vulnerability issue () + ## \[v1.4.4\] ### Enhancements @@ -324,7 +349,7 @@ All notable changes to this project will be documented in this file. ## \[v1.0.0\] -> _**NOTES**_ +> **NOTES** > > OpenVINO™ Training Extensions which version 1.0.0 has been updated to include functional and security updates. Users should update to the latest version. @@ -352,7 +377,7 @@ All notable changes to this project will be documented in this file. ## \[v0.5.0\] -> _**NOTES**_ +> **NOTES** > > OpenVINO Training Extension which version is equal or older then v0.5.0 does not include the latest functional and security updates. OTE Version 1.0.0 is targeted to be released in February 2023 and will include additional functional and security updates. Customers should update to the latest version as it becomes available. diff --git a/docs/source/guide/release_notes/index.rst b/docs/source/guide/release_notes/index.rst index 133b7350c9e..08abb0d55a6 100644 --- a/docs/source/guide/release_notes/index.rst +++ b/docs/source/guide/release_notes/index.rst @@ -4,6 +4,13 @@ Releases .. toctree:: :maxdepth: 1 +v1.5.2 (1Q24) +------------- + +- Remove polygon clipping code +- Hotfix default memcache size to 100MB + + v1.5.0 (4Q23) ------------- @@ -25,6 +32,12 @@ v1.5.0 (4Q23) - Support torch==2.0.1 - Set "Auto" as default input size mode +v1.4.5 (1Q24) +------------- + +- Filter invalid polygon shapes +- Fix a bug to set reverse_input_channels for OpenVINO models +- Remove unreferenced dependency of protobuf v1.4.4 (4Q23) ------------- diff --git a/requirements/base.txt b/requirements/base.txt index 9a4099a6162..0fdf3c0eb8e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -2,7 +2,6 @@ # Base Algo Requirements. # natsort==8.1.* prettytable==3.9.* -protobuf==3.20.* pyyaml datumaro==1.6.0rc1 psutil==5.9.* diff --git a/src/otx/algorithms/action/configs/classification/configuration.yaml b/src/otx/algorithms/action/configs/classification/configuration.yaml index 86dcd72a68a..b4e3282b058 100644 --- a/src/otx/algorithms/action/configs/classification/configuration.yaml +++ b/src/otx/algorithms/action/configs/classification/configuration.yaml @@ -290,7 +290,7 @@ algo_backend: description: Size of memory pool for caching decoded data to load data faster (bytes). editable: true header: Size of memory pool - max_value: 9223372036854775807 + max_value: 10000000000 min_value: 0 type: INTEGER ui_rules: diff --git a/src/otx/algorithms/action/configs/detection/configuration.yaml b/src/otx/algorithms/action/configs/detection/configuration.yaml index 86dcd72a68a..b4e3282b058 100644 --- a/src/otx/algorithms/action/configs/detection/configuration.yaml +++ b/src/otx/algorithms/action/configs/detection/configuration.yaml @@ -290,7 +290,7 @@ algo_backend: description: Size of memory pool for caching decoded data to load data faster (bytes). editable: true header: Size of memory pool - max_value: 9223372036854775807 + max_value: 10000000000 min_value: 0 type: INTEGER ui_rules: diff --git a/src/otx/algorithms/anomaly/tasks/inference.py b/src/otx/algorithms/anomaly/tasks/inference.py index bf430a5dbea..c75534dfafb 100644 --- a/src/otx/algorithms/anomaly/tasks/inference.py +++ b/src/otx/algorithms/anomaly/tasks/inference.py @@ -357,7 +357,7 @@ def _add_metadata_to_ir(self, model_file: str, export_type: ExportType) -> None: if "min" in metadata and "max" in metadata: extra_model_data[("model_info", "normalization_scale")] = metadata["max"] - metadata["min"] - extra_model_data[("model_info", "reverse_input_channels")] = False + extra_model_data[("model_info", "reverse_input_channels")] = True # convert BGR to RGB extra_model_data[("model_info", "model_type")] = "AnomalyDetection" labels = [] diff --git a/src/otx/algorithms/anomaly/tasks/openvino.py b/src/otx/algorithms/anomaly/tasks/openvino.py index 922ff2b3fd9..c235ef99c01 100644 --- a/src/otx/algorithms/anomaly/tasks/openvino.py +++ b/src/otx/algorithms/anomaly/tasks/openvino.py @@ -453,7 +453,7 @@ def _metadata_in_ir_format(self) -> Dict[Tuple[str, str], Union[str, int, float, if "min" in metadata and "max" in metadata: extra_model_data[("model_info", "normalization_scale")] = metadata["max"] - metadata["min"] - extra_model_data[("model_info", "reverse_input_channels")] = False + extra_model_data[("model_info", "reverse_input_channels")] = True # convert BGR to RGB extra_model_data[("model_info", "model_type")] = "AnomalyDetection" extra_model_data[("model_info", "labels")] = "Normal Anomaly" return extra_model_data diff --git a/src/otx/algorithms/classification/adapters/openvino/task.py b/src/otx/algorithms/classification/adapters/openvino/task.py index 135f8b20d98..dd65f460dc9 100644 --- a/src/otx/algorithms/classification/adapters/openvino/task.py +++ b/src/otx/algorithms/classification/adapters/openvino/task.py @@ -127,7 +127,11 @@ def __init__( self.model = Model.create_model(model_adapter, "Classification", self.configuration, preload=True) - self.converter = ClassificationToAnnotationConverter(self.label_schema) + if self.model.hierarchical: + hierarchical_cls_heads_info = self.model.hierarchical_info["cls_heads_info"] + else: + hierarchical_cls_heads_info = None + self.converter = ClassificationToAnnotationConverter(self.label_schema, hierarchical_cls_heads_info) self.callback_exceptions: List[Exception] = [] self.model.inference_adapter.set_callback(self._async_callback) diff --git a/src/otx/algorithms/classification/configs/configuration.yaml b/src/otx/algorithms/classification/configs/configuration.yaml index 18de282bf68..0aaf20c07d1 100644 --- a/src/otx/algorithms/classification/configs/configuration.yaml +++ b/src/otx/algorithms/classification/configs/configuration.yaml @@ -441,11 +441,11 @@ algo_backend: warning: null mem_cache_size: affects_outcome_of: TRAINING - default_value: 1000000000 + default_value: 100000000 description: Size of memory pool for caching decoded data to load data faster (bytes). editable: true header: Size of memory pool - max_value: 9223372036854775807 + max_value: 10000000000 min_value: 0 type: INTEGER ui_rules: diff --git a/src/otx/algorithms/classification/utils/cls_utils.py b/src/otx/algorithms/classification/utils/cls_utils.py index 67e3b1c5601..f1bdd13e209 100644 --- a/src/otx/algorithms/classification/utils/cls_utils.py +++ b/src/otx/algorithms/classification/utils/cls_utils.py @@ -18,8 +18,9 @@ import json from operator import itemgetter -from typing import Any, Dict +from typing import Any, Dict, List +from otx.api.entities.label import LabelEntity from otx.api.entities.label_schema import LabelSchemaEntity from otx.api.serialization.label_mapper import LabelSchemaMapper @@ -51,8 +52,8 @@ def get_multihead_class_info(label_schema: LabelSchemaEntity): # pylint: disabl for j, group in enumerate(single_label_groups): class_to_idx[group[0]] = (len(exclusive_groups), j) - all_labels = label_schema.get_labels(include_empty=False) - label_to_idx = {lbl.name: i for i, lbl in enumerate(all_labels)} + # Idx of label corresponds to model output + label_to_idx = {lbl: i for i, lbl in enumerate(class_to_idx.keys())} mixed_cls_heads_info = { "num_multiclass_heads": len(exclusive_groups), @@ -104,9 +105,13 @@ def get_cls_model_api_configuration(label_schema: LabelSchemaEntity, inference_c mapi_config[("model_info", "hierarchical")] = str(inference_config["hierarchical"]) mapi_config[("model_info", "output_raw_scores")] = str(True) + label_entities = label_schema.get_labels(include_empty=False) + if inference_config["hierarchical"]: + label_entities = get_hierarchical_label_list(inference_config["multihead_class_info"], label_entities) + all_labels = "" all_label_ids = "" - for lbl in label_schema.get_labels(include_empty=False): + for lbl in label_entities: all_labels += lbl.name.replace(" ", "_") + " " all_label_ids += f"{lbl.id_} " @@ -123,22 +128,16 @@ def get_cls_model_api_configuration(label_schema: LabelSchemaEntity, inference_c return mapi_config -def get_hierarchical_label_list(hierarchical_info, labels): +def get_hierarchical_label_list(hierarchical_cls_heads_info: Dict, labels: List) -> List[LabelEntity]: """Return hierarchical labels list which is adjusted to model outputs classes.""" + + # Create the list of Label Entities (took from "labels") + # corresponding to names and order in "label_to_idx" + label_to_idx = hierarchical_cls_heads_info["label_to_idx"] hierarchical_labels = [] - for head_idx in range(hierarchical_info["num_multiclass_heads"]): - logits_begin, logits_end = hierarchical_info["head_idx_to_logits_range"][str(head_idx)] - for logit in range(0, logits_end - logits_begin): - label_str = hierarchical_info["all_groups"][head_idx][logit] - label_idx = hierarchical_info["label_to_idx"][label_str] - hierarchical_labels.append(labels[label_idx]) - - if hierarchical_info["num_multilabel_classes"]: - logits_begin = hierarchical_info["num_single_label_classes"] - logits_end = len(labels) - for logit_idx, logit in enumerate(range(0, logits_end - logits_begin)): - label_str_idx = hierarchical_info["num_multiclass_heads"] + logit_idx - label_str = hierarchical_info["all_groups"][label_str_idx][0] - label_idx = hierarchical_info["label_to_idx"][label_str] - hierarchical_labels.append(labels[label_idx]) + for label_str, _ in label_to_idx.items(): + for label_entity in labels: + if label_entity.name == label_str: + hierarchical_labels.append(label_entity) + break return hierarchical_labels diff --git a/src/otx/algorithms/common/adapters/mmcv/pipelines/load_image_from_otx_dataset.py b/src/otx/algorithms/common/adapters/mmcv/pipelines/load_image_from_otx_dataset.py index b5df29dcd96..fa0c94518d9 100644 --- a/src/otx/algorithms/common/adapters/mmcv/pipelines/load_image_from_otx_dataset.py +++ b/src/otx/algorithms/common/adapters/mmcv/pipelines/load_image_from_otx_dataset.py @@ -204,6 +204,8 @@ def __call__(self, results: Dict[str, Any]) -> Dict[str, Any]: return cached_results results = self._load_img(results) results = self._load_ann_if_any(results) + if results is None: + return None results.pop("dataset_item", None) # Prevent deepcopy or caching results = self._resize_img_ann_if_any(results) self._save_cache(results) diff --git a/src/otx/algorithms/detection/configs/detection/configuration.yaml b/src/otx/algorithms/detection/configs/detection/configuration.yaml index a332cad8711..76153480be3 100644 --- a/src/otx/algorithms/detection/configs/detection/configuration.yaml +++ b/src/otx/algorithms/detection/configs/detection/configuration.yaml @@ -371,11 +371,11 @@ algo_backend: warning: null mem_cache_size: affects_outcome_of: TRAINING - default_value: 1000000000 + default_value: 100000000 description: Size of memory pool for caching decoded data to load data faster (bytes). editable: true header: Size of memory pool - max_value: 9223372036854775807 + max_value: 10000000000 min_value: 0 type: INTEGER ui_rules: diff --git a/src/otx/algorithms/detection/configs/instance_segmentation/configuration.yaml b/src/otx/algorithms/detection/configs/instance_segmentation/configuration.yaml index 59cef3af6c5..a41cf8ac9fb 100644 --- a/src/otx/algorithms/detection/configs/instance_segmentation/configuration.yaml +++ b/src/otx/algorithms/detection/configs/instance_segmentation/configuration.yaml @@ -371,11 +371,11 @@ algo_backend: warning: null mem_cache_size: affects_outcome_of: TRAINING - default_value: 1000000000 + default_value: 100000000 description: Size of memory pool for caching decoded data to load data faster (bytes). editable: true header: Size of memory pool - max_value: 9223372036854775807 + max_value: 10000000000 min_value: 0 type: INTEGER ui_rules: diff --git a/src/otx/algorithms/detection/configs/rotated_detection/configuration.yaml b/src/otx/algorithms/detection/configs/rotated_detection/configuration.yaml index 824093460ac..56e1e98885b 100644 --- a/src/otx/algorithms/detection/configs/rotated_detection/configuration.yaml +++ b/src/otx/algorithms/detection/configs/rotated_detection/configuration.yaml @@ -356,11 +356,11 @@ algo_backend: warning: null mem_cache_size: affects_outcome_of: TRAINING - default_value: 1000000000 + default_value: 100000000 description: Size of memory pool for caching decoded data to load data faster (bytes). editable: true header: Size of memory pool - max_value: 9223372036854775807 + max_value: 10000000000 min_value: 0 type: INTEGER ui_rules: diff --git a/src/otx/algorithms/detection/configs/rotated_detection/efficientnetb2b_maskrcnn/tile_pipeline.py b/src/otx/algorithms/detection/configs/rotated_detection/efficientnetb2b_maskrcnn/tile_pipeline.py index fb25aab8478..7b44ca1d30a 100644 --- a/src/otx/algorithms/detection/configs/rotated_detection/efficientnetb2b_maskrcnn/tile_pipeline.py +++ b/src/otx/algorithms/detection/configs/rotated_detection/efficientnetb2b_maskrcnn/tile_pipeline.py @@ -1,7 +1,95 @@ -"""Tiling Pipeline of EfficientNetB2B model for Instance-Seg Task.""" +"""Tiling Pipeline of EfficientNetB2B model.""" # Copyright (C) 2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +# pylint: disable=invalid-name -_base_ = ["../../base/data/tiling/efficientnet_iseg_tile_pipeline.py"] +img_size = (512, 512) + +tile_cfg = dict( + tile_size=400, min_area_ratio=0.9, overlap_ratio=0.2, iou_threshold=0.45, max_per_img=1500, filter_empty_gt=True +) + +img_norm_cfg = dict(mean=(103.53, 116.28, 123.675), std=(1.0, 1.0, 1.0), to_rgb=True) + +train_pipeline = [ + dict(type="Resize", img_scale=img_size, keep_ratio=False), + dict(type="RandomFlip", flip_ratio=0.5), + dict(type="Normalize", **img_norm_cfg), + dict(type="Pad", size_divisor=32), + dict(type="DefaultFormatBundle"), + dict( + type="Collect", + keys=["img", "gt_bboxes", "gt_labels", "gt_masks"], + meta_keys=[ + "filename", + "ori_filename", + "ori_shape", + "img_shape", + "pad_shape", + "scale_factor", + "flip", + "flip_direction", + "img_norm_cfg", + ], + ), +] + +test_pipeline = [ + dict( + type="MultiScaleFlipAug", + img_scale=img_size, + flip=False, + transforms=[ + dict(type="Resize", keep_ratio=False), + dict(type="RandomFlip"), + dict(type="Normalize", **img_norm_cfg), + dict(type="Pad", size_divisor=32), + dict(type="ImageToTensor", keys=["img"]), + dict(type="Collect", keys=["img"]), + ], + ) +] + +__dataset_type = "OTXDetDataset" + +train_dataset = dict( + type="ImageTilingDataset", + dataset=dict( + type=__dataset_type, + pipeline=[ + dict(type="LoadImageFromOTXDataset", enable_memcache=True), + dict(type="LoadAnnotationFromOTXDataset", domain="rotated_detection", with_bbox=True, with_mask=True), + ], + ), + pipeline=train_pipeline, + **tile_cfg +) + +val_dataset = dict( + type="ImageTilingDataset", + dataset=dict( + type=__dataset_type, + pipeline=[ + dict(type="LoadImageFromOTXDataset", enable_memcache=True), + dict(type="LoadAnnotationFromOTXDataset", domain="rotated_detection", with_bbox=True, with_mask=True), + ], + ), + pipeline=test_pipeline, + **tile_cfg +) + +test_dataset = dict( + type="ImageTilingDataset", + dataset=dict( + type=__dataset_type, + test_mode=True, + pipeline=[dict(type="LoadImageFromOTXDataset")], + ), + pipeline=test_pipeline, + **tile_cfg +) + + +data = dict(train=train_dataset, val=val_dataset, test=test_dataset) diff --git a/src/otx/algorithms/detection/configs/rotated_detection/resnet50_maskrcnn/tile_pipeline.py b/src/otx/algorithms/detection/configs/rotated_detection/resnet50_maskrcnn/tile_pipeline.py index ad2ebc72f9d..af77d77b5c3 100644 --- a/src/otx/algorithms/detection/configs/rotated_detection/resnet50_maskrcnn/tile_pipeline.py +++ b/src/otx/algorithms/detection/configs/rotated_detection/resnet50_maskrcnn/tile_pipeline.py @@ -1,7 +1,95 @@ -"""Tiling Pipeline of Resnet model for Instance-Seg Task.""" +"""Tiling Pipeline for Rotated-Detection Task.""" -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +# pylint: disable=invalid-name -_base_ = ["../../base/data/tiling/base_iseg_tile_pipeline.py"] +img_size = (512, 512) + +tile_cfg = dict( + tile_size=400, min_area_ratio=0.9, overlap_ratio=0.2, iou_threshold=0.45, max_per_img=1500, filter_empty_gt=True +) + +img_norm_cfg = dict(mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) + +train_pipeline = [ + dict(type="Resize", img_scale=img_size, keep_ratio=True), + dict(type="RandomFlip", flip_ratio=0.5), + dict(type="Normalize", **img_norm_cfg), + dict(type="Pad", size=img_size), + dict(type="DefaultFormatBundle"), + dict( + type="Collect", + keys=["img", "gt_bboxes", "gt_labels", "gt_masks"], + meta_keys=[ + "filename", + "ori_filename", + "ori_shape", + "img_shape", + "pad_shape", + "scale_factor", + "flip", + "flip_direction", + "img_norm_cfg", + ], + ), +] + +test_pipeline = [ + dict( + type="MultiScaleFlipAug", + img_scale=img_size, + flip=False, + transforms=[ + dict(type="Resize", keep_ratio=True), + dict(type="RandomFlip"), + dict(type="Normalize", **img_norm_cfg), + dict(type="Pad", size=img_size), + dict(type="ImageToTensor", keys=["img"]), + dict(type="Collect", keys=["img"]), + ], + ) +] + +__dataset_type = "OTXDetDataset" + +train_dataset = dict( + type="ImageTilingDataset", + dataset=dict( + type=__dataset_type, + pipeline=[ + dict(type="LoadImageFromOTXDataset", enable_memcache=True), + dict(type="LoadAnnotationFromOTXDataset", domain="rotated_detection", with_bbox=True, with_mask=True), + ], + ), + pipeline=train_pipeline, + **tile_cfg +) + +val_dataset = dict( + type="ImageTilingDataset", + dataset=dict( + type=__dataset_type, + pipeline=[ + dict(type="LoadImageFromOTXDataset", enable_memcache=True), + dict(type="LoadAnnotationFromOTXDataset", domain="rotated_detection", with_bbox=True, with_mask=True), + ], + ), + pipeline=test_pipeline, + **tile_cfg +) + +test_dataset = dict( + type="ImageTilingDataset", + dataset=dict( + type=__dataset_type, + test_mode=True, + pipeline=[dict(type="LoadImageFromOTXDataset")], + ), + pipeline=test_pipeline, + **tile_cfg +) + + +data = dict(train=train_dataset, val=val_dataset, test=test_dataset) diff --git a/src/otx/algorithms/segmentation/configs/configuration.yaml b/src/otx/algorithms/segmentation/configs/configuration.yaml index fa835827add..6f5ccd2263b 100644 --- a/src/otx/algorithms/segmentation/configs/configuration.yaml +++ b/src/otx/algorithms/segmentation/configs/configuration.yaml @@ -323,11 +323,11 @@ algo_backend: warning: null mem_cache_size: affects_outcome_of: TRAINING - default_value: 1000000000 + default_value: 100000000 description: Size of memory pool for caching decoded data to load data faster (bytes). editable: true header: Size of memory pool - max_value: 9223372036854775807 + max_value: 10000000000 min_value: 0 type: INTEGER ui_rules: diff --git a/src/otx/api/entities/shapes/polygon.py b/src/otx/api/entities/shapes/polygon.py index f510a8eeaab..6176a0c3b79 100644 --- a/src/otx/api/entities/shapes/polygon.py +++ b/src/otx/api/entities/shapes/polygon.py @@ -29,8 +29,8 @@ class Point: __slots__ = ["x", "y"] def __init__(self, x: float, y: float): - self.x = np.clip(x, a_min=0.0, a_max=1.0) - self.y = np.clip(y, a_min=0.0, a_max=1.0) + self.x = x + self.y = y def __repr__(self): """String representation of the point.""" diff --git a/src/otx/api/usecases/exportable_code/prediction_to_annotation_converter.py b/src/otx/api/usecases/exportable_code/prediction_to_annotation_converter.py index f84c2b0facf..72cf458e24a 100644 --- a/src/otx/api/usecases/exportable_code/prediction_to_annotation_converter.py +++ b/src/otx/api/usecases/exportable_code/prediction_to_annotation_converter.py @@ -18,6 +18,7 @@ InstanceSegmentationResult, ) +from otx.algorithms.classification.utils import get_hierarchical_label_list from otx.api.entities.annotation import ( Annotation, AnnotationSceneEntity, @@ -182,7 +183,11 @@ def create_converter( elif converter_type == Domain.SEGMENTATION: converter = SegmentationToAnnotationConverter(labels) elif converter_type == Domain.CLASSIFICATION: - converter = ClassificationToAnnotationConverter(labels) + if configuration is not None and configuration.get("hierarchical", False): + hierarchical_cls_heads_info = configuration["multihead_class_info"] + else: + hierarchical_cls_heads_info = None + converter = ClassificationToAnnotationConverter(labels, hierarchical_cls_heads_info) elif converter_type == Domain.ANOMALY_CLASSIFICATION: converter = AnomalyClassificationToAnnotationConverter(labels) elif converter_type == Domain.ANOMALY_DETECTION: @@ -280,9 +285,10 @@ class ClassificationToAnnotationConverter(IPredictionToAnnotationConverter): Args: labels (LabelSchemaEntity): Label Schema containing the label info of the task + hierarchical_cls_heads_info (Dict): Info from model.hierarchical_info["cls_heads_info"] """ - def __init__(self, label_schema: LabelSchemaEntity): + def __init__(self, label_schema: LabelSchemaEntity, hierarchical_cls_heads_info: Optional[Dict] = None): if len(label_schema.get_labels(False)) == 1: self.labels = label_schema.get_labels(include_empty=True) else: @@ -296,6 +302,9 @@ def __init__(self, label_schema: LabelSchemaEntity): self.label_schema = label_schema + if self.hierarchical: + self.labels = get_hierarchical_label_list(hierarchical_cls_heads_info, self.labels) + def convert_to_annotation( self, predictions: ClassificationResult, metadata: Optional[Dict] = None ) -> AnnotationSceneEntity: diff --git a/tests/unit/algorithms/classification/tasks/test_classification_openvino_task.py b/tests/unit/algorithms/classification/tasks/test_classification_openvino_task.py index ae1dc20f370..2565c33c7ca 100644 --- a/tests/unit/algorithms/classification/tasks/test_classification_openvino_task.py +++ b/tests/unit/algorithms/classification/tasks/test_classification_openvino_task.py @@ -101,8 +101,8 @@ def setup(self, mocker, otx_model) -> None: "cls_heads_info": { "num_multiclass_heads": 2, "head_idx_to_logits_range": {"0": (0, 1), "1": (1, 2)}, - "all_groups": [["a"], ["b"]], - "label_to_idx": {"a": 0, "b": 1}, + "all_groups": [["b"], ["g"]], + "label_to_idx": {"b": 0, "g": 1}, "num_multilabel_classes": 0, } } @@ -134,6 +134,7 @@ def test_infer_w_features_hierarhicallabel(self, mocker): ), ) mocker.patch.object(ShapeFactory, "shape_produces_valid_crop", return_value=True) + self.cls_ov_task.inferencer.model.hierarchical = True self.cls_ov_task.inferencer.model.hierarchical_info = self.fake_hierarchical_info updated_dataset = self.cls_ov_task.infer( self.dataset, InferenceParameters(enable_async_inference=False, is_evaluation=False) @@ -188,6 +189,7 @@ def test_explain_hierarhicallabel(self, mocker): ), ) self.cls_ov_task.inferencer.model.hierarchical_info = self.fake_hierarchical_info + self.cls_ov_task.inferencer.model.hierarchical = True updpated_dataset = self.cls_ov_task.explain(self.dataset) assert updpated_dataset is not None diff --git a/tests/unit/api/usecases/exportable_code/test_prediction_to_annotation_converter.py b/tests/unit/api/usecases/exportable_code/test_prediction_to_annotation_converter.py index f48729b1775..66ba903d9b2 100644 --- a/tests/unit/api/usecases/exportable_code/test_prediction_to_annotation_converter.py +++ b/tests/unit/api/usecases/exportable_code/test_prediction_to_annotation_converter.py @@ -758,7 +758,10 @@ def test_classification_to_annotation_init(self): labels=other_non_empty_labels, ) label_schema = LabelSchemaEntity(label_groups=[label_group, other_label_group]) - converter = ClassificationToAnnotationConverter(label_schema=label_schema) + hierarchical_cls_heads_info = {"label_to_idx": {label_0_1.name: 0, label_0_1_1.name: 1, label_0_2.name: 2}} + converter = ClassificationToAnnotationConverter( + label_schema=label_schema, hierarchical_cls_heads_info=hierarchical_cls_heads_info + ) assert not converter.empty_label assert converter.label_schema == label_schema assert converter.hierarchical @@ -849,7 +852,10 @@ def check_annotation(actual_annotation: Annotation, expected_labels: list): label_schema = LabelSchemaEntity(label_groups=[label_group, other_label_group]) label_schema.add_child(parent=label_0_1, child=label_0_1_1) - converter = ClassificationToAnnotationConverter(label_schema=label_schema) + hierarchical_cls_heads_info = {"label_to_idx": {label_0_1.name: 0, label_0_1_1.name: 1, label_0_2.name: 2}} + converter = ClassificationToAnnotationConverter( + label_schema=label_schema, hierarchical_cls_heads_info=hierarchical_cls_heads_info + ) predictions = ClassificationResult([(2, 0.9), (1, 0.8)], None, None, None) predictions_to_annotations = converter.convert_to_annotation(predictions) check_annotation_scene(annotation_scene=predictions_to_annotations, expected_length=1)