Skip to content

Commit

Permalink
Merge pull request #333 from openvinotoolkit/fix-save-deployment
Browse files Browse the repository at this point in the history
Add compatibility for anomaly model deployment for Geti v1.13 and up
  • Loading branch information
ljcornel authored Feb 15, 2024
2 parents 950fdeb + 2572dd4 commit 4bccbf9
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 14 deletions.
22 changes: 14 additions & 8 deletions geti_sdk/deployment/deployed_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,15 @@ def get_data(self, source: Union[str, os.PathLike, GetiSession]):
model_python_path = os.path.join(
os.path.dirname(source), PYTHON_DIR_NAME
)
python_dir_contents = os.listdir(model_python_path)
python_dir_contents = (
os.listdir(model_python_path)
if os.path.exists(model_python_path)
else []
)
if WRAPPER_DIR_NAME in python_dir_contents:
self._has_custom_model_wrappers = True
self._model_python_path = os.path.join(source, PYTHON_DIR_NAME)

self._model_python_path = os.path.join(source, PYTHON_DIR_NAME)

elif isinstance(source, GetiSession):
if self.base_url is None:
Expand Down Expand Up @@ -411,14 +416,15 @@ def save(self, path_to_folder: Union[str, os.PathLike]) -> bool:
dst=new_model_data_path,
dirs_exist_ok=True,
)
shutil.copytree(
src=self._model_python_path,
dst=new_model_python_path,
dirs_exist_ok=True,
)
if self._model_python_path is not None:
shutil.copytree(
src=self._model_python_path,
dst=new_model_python_path,
dirs_exist_ok=True,
)
self._model_python_path = new_model_python_path

self._model_data_path = new_model_data_path
self._model_python_path = new_model_python_path

config_dict = ConfigurationRESTConverter.configuration_to_minimal_dict(
self.hyper_parameters
Expand Down
37 changes: 32 additions & 5 deletions geti_sdk/deployment/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
from geti_sdk.data_models.predictions import ResultMedium
from geti_sdk.data_models.shapes import Polygon, Rectangle, RotatedRectangle
from geti_sdk.deployment.data_models import ROI, IntermediateInferenceResult
from geti_sdk.deployment.legacy_converters import (
AnomalyClassificationToAnnotationConverter,
)
from geti_sdk.rest_converters import ProjectRESTConverter

from .deployed_model import DeployedModel
Expand Down Expand Up @@ -327,9 +330,21 @@ def _infer_task(
)

if n_outputs != 0:
annotation_scene_entity = converter.convert_to_annotation(
predictions=postprocessing_results, metadata=metadata
)
try:
annotation_scene_entity = converter.convert_to_annotation(
predictions=postprocessing_results, metadata=metadata
)
except AttributeError:
# Add backwards compatibility for anomaly models created in Geti v1.8 and below
if task.type.is_anomaly:
legacy_converter = AnomalyClassificationToAnnotationConverter(
label_schema=model.ote_label_schema
)
annotation_scene_entity = legacy_converter.convert_to_annotation(
predictions=postprocessing_results, metadata=metadata
)
self._inference_converters[task.type] = legacy_converter

prediction = Prediction.from_ote(
annotation_scene_entity, image_width=width, image_height=height
)
Expand Down Expand Up @@ -465,20 +480,32 @@ def _get_model_for_task(self, task: Task) -> DeployedModel:
) from error
return self.models[task_index]

def _remove_temporary_resources(self) -> None:
def _remove_temporary_resources(self) -> bool:
"""
If necessary, clean up any temporary resources associated with the deployment.
:return: True if temp files have been deleted successfully
"""
if self._path_to_temp_resources is not None and os.path.isdir(
self._path_to_temp_resources
):
shutil.rmtree(self._path_to_temp_resources)
try:
shutil.rmtree(self._path_to_temp_resources)
except PermissionError:
logging.warning(
f"Unable to remove temporary files for deployment at path "
f"`{self._path_to_temp_resources}` because the files are in "
f"use by another process. "
)
return False
else:
logging.debug(
f"Unable to clean up temporary resources for deployment {self}, "
f"because the resources were not found on the system. Possibly "
f"they were already deleted."
)
return False
return True

def __del__(self):
"""
Expand Down
22 changes: 22 additions & 0 deletions geti_sdk/deployment/legacy_converters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (C) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions
# and limitations under the License.

"""
Prediction converters for use with inference models created in older
versions of the Intel® Geti™ platform, i.e. v1.8 and below.
"""

from .legacy_anomaly_converter import AnomalyClassificationToAnnotationConverter

__all__ = ["AnomalyClassificationToAnnotationConverter"]
65 changes: 65 additions & 0 deletions geti_sdk/deployment/legacy_converters/legacy_anomaly_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright (C) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions
# and limitations under the License.
from typing import Any, Dict

import numpy as np
from otx.api.entities.annotation import (
Annotation,
AnnotationSceneEntity,
AnnotationSceneKind,
)
from otx.api.entities.label_schema import LabelSchemaEntity
from otx.api.entities.scored_label import ScoredLabel
from otx.api.entities.shapes.rectangle import Rectangle
from otx.api.usecases.exportable_code.prediction_to_annotation_converter import (
IPredictionToAnnotationConverter,
)


class AnomalyClassificationToAnnotationConverter(IPredictionToAnnotationConverter):
"""
Convert AnomalyClassification Predictions ModelAPI to Annotations.
:param labels: Label Schema containing the label info of the task
"""

def __init__(self, label_schema: LabelSchemaEntity):
labels = label_schema.get_labels(include_empty=False)
self.normal_label = [label for label in labels if not label.is_anomalous][0]
self.anomalous_label = [label for label in labels if label.is_anomalous][0]

def convert_to_annotation(
self, predictions: np.ndarray, metadata: Dict[str, Any]
) -> AnnotationSceneEntity:
"""
Convert predictions to OTX Annotation Scene using the metadata.
:param predictions: Raw predictions from the model.
:param metadata: Variable containing metadata information.
:return: OTX annotation scene entity object.
"""
pred_label = predictions >= metadata.get("threshold", 0.5)

label = self.anomalous_label if pred_label else self.normal_label
probability = (1 - predictions) if predictions < 0.5 else predictions

annotations = [
Annotation(
Rectangle.generate_full_box(),
labels=[ScoredLabel(label=label, probability=float(probability))],
)
]
return AnnotationSceneEntity(
kind=AnnotationSceneKind.PREDICTION, annotations=annotations
)
2 changes: 1 addition & 1 deletion requirements/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Pillow==10.2.*
pathvalidate>=2.5.0
simplejson==3.19.*
ipython==8.12.*
otx==1.4.3
otx==1.4.4
openvino==2023.0.*
openvino-model-api==0.1.5
certifi>=2022.12.7
Expand Down

0 comments on commit 4bccbf9

Please sign in to comment.