diff --git a/src/frontends/tensorflow/docs/supported_ops.md b/src/frontends/tensorflow/docs/supported_ops.md index 4fd03c9801a..cf9f624d460 100644 --- a/src/frontends/tensorflow/docs/supported_ops.md +++ b/src/frontends/tensorflow/docs/supported_ops.md @@ -487,7 +487,7 @@ A "supported operation" is one that TensorFlow Frontend can convert to the OpenV | GroupByReducerDataset | NO | | | GroupByWindowDataset | NO | | | GuaranteeConst | NO | | -| HSVToRGB | NO | | +| HSVToRGB | YES | | | HashTable | YES | | | HashTableV2 | YES | | | HistogramFixedWidth | NO | | diff --git a/src/frontends/tensorflow/src/op_table.cpp b/src/frontends/tensorflow/src/op_table.cpp index 1a852150333..9c97debd0b8 100644 --- a/src/frontends/tensorflow/src/op_table.cpp +++ b/src/frontends/tensorflow/src/op_table.cpp @@ -271,6 +271,7 @@ const std::map get_supported_ops() { {"Addons>GatherTree", CreatorFunction(translate_gather_tree_op)}, {"HashTable", CreatorFunction(translate_hash_table_op)}, {"HashTableV2", CreatorFunction(translate_hash_table_op)}, + {"HSVToRGB", CreatorFunction(translate_hsv_to_rgb_op)}, {"Identity", CreatorFunction(translate_identity_op)}, {"IdentityN", CreatorFunction(translate_identity_n_op)}, {"Inv", CreatorFunction(translate_inv_op)}, diff --git a/src/frontends/tensorflow_common/include/common_op_table.hpp b/src/frontends/tensorflow_common/include/common_op_table.hpp index bd9ce5cadfb..784cd332794 100644 --- a/src/frontends/tensorflow_common/include/common_op_table.hpp +++ b/src/frontends/tensorflow_common/include/common_op_table.hpp @@ -87,6 +87,7 @@ OP_CONVERTER(translate_gather_v2_op); OP_CONVERTER(translate_gather_nd_op); OP_CONVERTER(translate_gather_tree_op); OP_CONVERTER(translate_gelu_op); +OP_CONVERTER(translate_hsv_to_rgb_op); OP_CONVERTER(translate_identity_op); OP_CONVERTER(translate_identity_n_op); OP_CONVERTER(translate_ifft_op); diff --git a/src/frontends/tensorflow_common/include/utils.hpp b/src/frontends/tensorflow_common/include/utils.hpp index d05b7104ff5..0ed5bbf9c22 100644 --- a/src/frontends/tensorflow_common/include/utils.hpp +++ b/src/frontends/tensorflow_common/include/utils.hpp @@ -158,9 +158,9 @@ ov::Output compute_broadcast_args(const ov::Output& shape1, std::shared_ptr, std::shared_ptr, std::shared_ptr>> rgb_to_hsv( const std::shared_ptr& images); -std::shared_ptr hsv_to_rgb(const std::shared_ptr& h, - const std::shared_ptr& s, - const std::shared_ptr& v); +std::shared_ptr hsv_to_rgb(const ov::Output& h, + const ov::Output& s, + const ov::Output& v); } // namespace tensorflow } // namespace frontend diff --git a/src/frontends/tensorflow_common/src/op/hsv_to_rgb.cpp b/src/frontends/tensorflow_common/src/op/hsv_to_rgb.cpp new file mode 100644 index 00000000000..2aa9433824c --- /dev/null +++ b/src/frontends/tensorflow_common/src/op/hsv_to_rgb.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "common_op_table.hpp" +#include "openvino/op/constant.hpp" +#include "openvino/op/split.hpp" +#include "utils.hpp" + +using namespace std; +using namespace ov; +using namespace ov::op; + +namespace ov { +namespace frontend { +namespace tensorflow { +namespace op { + +OutputVector translate_hsv_to_rgb_op(const NodeContext& node) { + default_op_checks(node, 1, {"HSVToRGB"}); + auto images = node.get_input(0); + auto node_name = node.get_name(); + + auto const_minus_one_i = make_shared(element::i32, Shape{}, -1); + auto channels = make_shared(images, const_minus_one_i, 3); + + auto hh = channels->output(0); + auto ss = channels->output(1); + auto vv = channels->output(2); + + auto new_images = hsv_to_rgb(hh, ss, vv); + + set_node_name(node_name, new_images); + return {new_images}; +} + +} // namespace op +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow_common/src/utils.cpp b/src/frontends/tensorflow_common/src/utils.cpp index ff3be236b30..ab5ecd94f4d 100644 --- a/src/frontends/tensorflow_common/src/utils.cpp +++ b/src/frontends/tensorflow_common/src/utils.cpp @@ -499,7 +499,9 @@ shared_ptr, shared_ptr, shared_ptr>> rgb_to_h return make_shared, shared_ptr, shared_ptr>>(hh_final, ss, vv); } -shared_ptr hsv_to_rgb(const shared_ptr& h, const shared_ptr& s, const shared_ptr& v) { +shared_ptr hsv_to_rgb(const ov::Output& h, + const ov::Output& s, + const ov::Output& v) { // image format conversion based on // https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/image/adjust_saturation_op.cc auto const_six_f_ = create_same_type_const_scalar(h, 6.0f); diff --git a/tests/layer_tests/tensorflow_tests/test_tf_HSVToRGB.py b/tests/layer_tests/tensorflow_tests/test_tf_HSVToRGB.py new file mode 100644 index 00000000000..0960801f957 --- /dev/null +++ b/tests/layer_tests/tensorflow_tests/test_tf_HSVToRGB.py @@ -0,0 +1,65 @@ +# Copyright (C) 2018-2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import platform + +import numpy as np +import pytest +import tensorflow as tf +from common.tf_layer_test_class import CommonTFLayerTest + +class TestHSVToRGB(CommonTFLayerTest): + def _prepare_input(self, inputs_info): + assert 'images:0' in inputs_info + if self.special_case == "Black Image": + images_shape = inputs_info['images:0'] + inputs_data = {} + inputs_data['images:0'] = np.zeros(images_shape).astype(self.input_type) + elif self.special_case == "Grayscale Image": + images_shape = inputs_info['images:0'] + inputs_data = {} + inputs_data['images:0'] = np.ones(images_shape).astype(self.input_type) * np.random.rand() + else: + images_shape = inputs_info['images:0'] + inputs_data = {} + inputs_data['images:0'] = np.random.rand(*images_shape).astype(self.input_type) + + return inputs_data + + def create_hsv_to_rgb_net(self, input_shape, input_type, special_case=False): + self.special_case = special_case + self.input_type = input_type + tf.compat.v1.reset_default_graph() + # Create the graph and model + with tf.compat.v1.Session() as sess: + images = tf.compat.v1.placeholder(input_type, input_shape, 'images') + tf.raw_ops.HSVToRGB(images=images) + tf.compat.v1.global_variables_initializer() + tf_net = sess.graph_def + + return tf_net, None + + # Each input is a tensor of with values in [0,1]. + # The last dimension must be size 3. + test_data_basic = [ + dict(input_shape=[7, 7, 3], input_type=np.float32, special_case="Black Image"), + dict(input_shape=[7, 7, 3], input_type=np.float32, special_case="Grayscale Image"), + dict(input_shape=[5, 5, 3], input_type=np.float32), + dict(input_shape=[5, 23, 27, 3], input_type=np.float64), + dict(input_shape=[3, 4, 13, 15, 3], input_type=np.float64), + ] + + @pytest.mark.parametrize("params", test_data_basic) + @pytest.mark.precommit + @pytest.mark.nightly + @pytest.mark.xfail(condition=platform.system() in ('Darwin', 'Linux') and platform.machine() in ['arm', 'armv7l', + 'aarch64', + 'arm64', 'ARM64'], + reason='Ticket - 126314, 132699') + def test_hsv_to_rgb_basic(self, params, ie_device, precision, ir_version, temp_dir, + use_legacy_frontend): + if ie_device == 'GPU': + pytest.skip("Accuracy mismatch on GPU") + self._test(*self.create_hsv_to_rgb_net(**params), + ie_device, precision, ir_version, temp_dir=temp_dir, + use_legacy_frontend=use_legacy_frontend)