diff --git a/dali/kernels/imgproc/color_manipulation/brightness_contrast.h b/dali/kernels/imgproc/color_manipulation/brightness_contrast.h index 0f4d8fe6c1..a51e170ef1 100644 --- a/dali/kernels/imgproc/color_manipulation/brightness_contrast.h +++ b/dali/kernels/imgproc/color_manipulation/brightness_contrast.h @@ -28,8 +28,8 @@ namespace dali { namespace kernels { -template -class BrightnessContrastCPU { +template +class BrightnessContrastCpu { private: static constexpr size_t spatial_dims = ndims - 1; diff --git a/dali/kernels/imgproc/color_manipulation/brightness_contrast_test.cc b/dali/kernels/imgproc/color_manipulation/brightness_contrast_test.cc index def356e7c6..de913dc8e6 100644 --- a/dali/kernels/imgproc/color_manipulation/brightness_contrast_test.cc +++ b/dali/kernels/imgproc/color_manipulation/brightness_contrast_test.cc @@ -76,18 +76,27 @@ class BrightnessContrastCpuTest : public ::testing::Test { }; -using TestTypes = std::tuple; +using TestTypes = std::tuple; INPUT_OUTPUT_TYPED_TEST_SUITE(BrightnessContrastCpuTest, TestTypes); +namespace { + +template +using BrightnessContrastKernel = BrightnessContrastCpu + ; + +} // namespace + + TYPED_TEST(BrightnessContrastCpuTest, check_kernel) { - BrightnessContrastCPU kernel; + BrightnessContrastKernel kernel; check_kernel(); } TYPED_TEST(BrightnessContrastCpuTest, SetupTestAndCheckKernel) { - BrightnessContrastCPU kernel; + BrightnessContrastKernel kernel; constexpr auto ndims = std::remove_reference_t::ndims; KernelContext ctx; InTensorCPU in(this->input_.data(), this->shape_); @@ -98,7 +107,7 @@ TYPED_TEST(BrightnessContrastCpuTest, SetupTestAndCheckKernel) { TYPED_TEST(BrightnessContrastCpuTest, RunTest) { - BrightnessContrastCPU kernel; + BrightnessContrastKernel kernel; constexpr auto ndims = std::remove_reference_t::ndims; KernelContext ctx; InTensorCPU in(this->input_.data(), this->shape_); @@ -117,7 +126,7 @@ TYPED_TEST(BrightnessContrastCpuTest, RunTest) { TYPED_TEST(BrightnessContrastCpuTest, RunTestWithRoi) { - BrightnessContrastCPU kernel; + BrightnessContrastKernel kernel; constexpr auto ndims = std::remove_reference_t::ndims; KernelContext ctx; InTensorCPU in(this->input_.data(), this->shape_); diff --git a/dali/pipeline/graph/op_graph.h b/dali/pipeline/graph/op_graph.h index 5fd1f370ab..532e8eabdb 100644 --- a/dali/pipeline/graph/op_graph.h +++ b/dali/pipeline/graph/op_graph.h @@ -208,7 +208,7 @@ class DLL_PUBLIC OpGraph { return tensor_nodes_[id]; } - DLL_PUBLIC const TensorNodeId TensorId(const std::string& name) const { + DLL_PUBLIC TensorNodeId TensorId(const std::string& name) const { auto it = tensor_name_to_id_.find(name); DALI_ENFORCE(it != tensor_name_to_id_.end(), "Tensor with name " + name + " does not exist in graph."); diff --git a/dali/pipeline/operators/color/brightness_contrast.cu b/dali/pipeline/operators/color/brightness_contrast.cu new file mode 100644 index 0000000000..45d42e9556 --- /dev/null +++ b/dali/pipeline/operators/color/brightness_contrast.cu @@ -0,0 +1,40 @@ +// Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. +// +// 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. + +#include "dali/pipeline/operators/color/brightness_contrast.h" + +namespace dali { +namespace brightness_contrast { + +DALI_REGISTER_OPERATOR(BrightnessContrast, BrightnessContrast, CPU) + +DALI_REGISTER_OPERATOR(BrightnessContrast, BrightnessContrast, GPU) + + +DALI_SCHEMA(BrightnessContrast) + .DocStr(R"code(Change the brightness and contrast of the image. +Additionally, this operator can change the type of data.)code") + .NumInput(1) + .NumOutput(1) + .AddOptionalArg(spec::kBrightness, + R"code(Set additive brightness delta. 0 denotes no-op)code", .0f, + true) + .AddOptionalArg(spec::kContrast, + R"code(Set multiplicative contrast delta. 1 denotes no-op)code", + 1.f, true) + .AddOptionalArg(spec::kOutputType, R"code(Set output data type)code", DALI_INT16); + + +} // namespace brightness_contrast +} // namespace dali diff --git a/dali/pipeline/operators/color/brightness_contrast.h b/dali/pipeline/operators/color/brightness_contrast.h new file mode 100644 index 0000000000..fdaf311a01 --- /dev/null +++ b/dali/pipeline/operators/color/brightness_contrast.h @@ -0,0 +1,197 @@ +// Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. +// +// 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. + +#ifndef DALI_PIPELINE_OPERATORS_COLOR_BRIGHTNESS_CONTRAST_H_ +#define DALI_PIPELINE_OPERATORS_COLOR_BRIGHTNESS_CONTRAST_H_ + +#include +#include +#include +#include "dali/core/static_switch.h" +#include "dali/pipeline/data/views.h" +#include "dali/pipeline/operators/common.h" +#include "dali/pipeline/operators/operator.h" +#include "dali/kernels/imgproc/color_manipulation/brightness_contrast.h" +#include "dali/kernels/imgproc/color_manipulation/brightness_contrast_gpu.h" +#include "dali/kernels/kernel_manager.h" + +namespace dali { +namespace brightness_contrast { + +namespace spec { + +const std::string kBrightness = "brightness_delta"; // NOLINT +const std::string kContrast = "contrast_delta"; // NOLINT +const std::string kOutputType = "output_type"; // NOLINT + +} // namespace spec + + +namespace detail { + +template +struct Kernel { + using type = kernels::BrightnessContrastCpu; +}; + + +template +struct Kernel { + using type = kernels::brightness_contrast::BrightnessContrastGpu; +}; + + +template +struct ArgumentType { + static_assert( + std::is_same::value || std::is_same::value, + "Unsupported Backend"); + using type = float; +}; + + +template <> +struct ArgumentType { + using type = std::vector; +}; + + +/** + * Select proper type for argument (for either sample processing or batch processing cases) + */ +template +using argument_t = typename ArgumentType::type; + + +/** + * Chooses proper kernel (CPU or GPU) for given template parameters + */ +template +using BrightnessContrastKernel = typename Kernel::type; + + +/** + * Assign argument, whether it is a single value (for sample-wise processing) + * or vector of values (for batch processing, where every argument is defined per-sample) + */ +template +void assign_argument_value(const OpSpec &, const std::string &, argument_t &) { + DALI_FAIL("Unsupported Backend. You may want to write your own specialization."); +} + + +template <> +void assign_argument_value(const OpSpec &spec, const std::string &arg_name, + argument_t &arg) { + std::vector tmp; + GetSingleOrRepeatedArg(spec, tmp, arg_name); + arg = tmp[0]; +} + + +template <> +void assign_argument_value(const OpSpec &spec, const std::string &arg_name, + argument_t &arg) { + GetSingleOrRepeatedArg(spec, arg, arg_name); +} + +} // namespace detail + + +template +class BrightnessContrast : public Operator { + public: + explicit BrightnessContrast(const OpSpec &spec) : + Operator(spec), + output_type_(spec.GetArgument(spec::kOutputType)) { + detail::assign_argument_value(spec, spec::kBrightness, brightness_); + detail::assign_argument_value(spec, spec::kContrast, contrast_); + if (std::is_same::value) { + kernel_manager_.Resize(1, 1); + } else { + kernel_manager_.Resize(num_threads_, batch_size_); + } + } + + + ~BrightnessContrast() override = default; + + + DISABLE_COPY_MOVE_ASSIGN(BrightnessContrast); + + + protected: + bool CanInferOutputs() const override { + return true; + } + + + bool SetupImpl(std::vector<::dali::OutputDesc> &output_desc, + const ::dali::workspace_t &ws) override { + const auto &input = ws.template InputRef(0); + const auto &output = ws.template OutputRef(0); + output_desc.resize(1); + + TYPE_SWITCH(output_type_, type2id, OutputType, (uint8_t, int16_t, int32_t, float), ( + { + TypeInfo type; + type.SetType(output_type_); + output_desc[0] = {input.shape(), type}; + } + ), DALI_FAIL("Unsupported image type")) // NOLINT + return true; + } + + /* + * So that compiler wouldn't complain, that + * "overloaded virtual function `dali::Operator::RunImpl` is only partially + * overridden in class `dali::brightness_contrast::BrightnessContrast`" + */ + using Operator::RunImpl; + + + void RunImpl(Workspace &ws) { + const auto &input = ws.template Input(0); + auto &output = ws.template Output(0); + + TYPE_SWITCH(input.type().id(), type2id, InputType, (uint8_t, int16_t, int32_t, float), ( + TYPE_SWITCH(output_type_, type2id, OutputType, (uint8_t, int16_t, int32_t, float), ( + { + using BrightnessContrastKernel = + detail::BrightnessContrastKernel; + auto tvin = view(input); + auto tvout = view(output); + kernel_manager_.Initialize(); + kernels::KernelContext ctx; + kernel_manager_.Setup(ws.data_idx(), + ctx, tvin, brightness_, contrast_); + kernel_manager_.Run(ws.thread_idx(), ws.data_idx(), + ctx, tvout, tvin, brightness_, contrast_); + } + ), DALI_FAIL("Unsupported output type")) // NOLINT + ), DALI_FAIL("Unsupported input type")) // NOLINT + } + + + private: + USE_OPERATOR_MEMBERS(); + detail::argument_t brightness_, contrast_; + DALIDataType output_type_; + kernels::KernelManager kernel_manager_; +}; + +} // namespace brightness_contrast +} // namespace dali + +#endif // DALI_PIPELINE_OPERATORS_COLOR_BRIGHTNESS_CONTRAST_H_ diff --git a/dali/pipeline/operators/color/brightness_contrast_test.cu b/dali/pipeline/operators/color/brightness_contrast_test.cu new file mode 100644 index 0000000000..520bfacd9b --- /dev/null +++ b/dali/pipeline/operators/color/brightness_contrast_test.cu @@ -0,0 +1,140 @@ +// Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. +// +// 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. + +#include +#include +#include "dali/test/dali_operator_test.h" +#include "dali/test/dali_operator_test_utils.h" +#include "dali/kernels/test/tensor_test_utils.h" +#include "dali/core/convert.h" + +namespace dali { + +namespace testing { +namespace brightness_contrast { + + +using InputDataType = float; + + +class BrightnessContrastTest : public testing::DaliOperatorTest { + public: + BrightnessContrastTest() { + Init(input_, volume(shape_)); + } + + + GraphDescr GenerateOperatorGraph() const override { + GraphDescr g("BrightnessContrast"); + return g; + } + + + void Init(std::vector &input, size_t n) { + std::mt19937_64 rng; + input.resize(n); + kernels::UniformRandomFill(input, rng, 0.f, 10.f); + } + + + template + std::unique_ptr> ToTensorList(std::vector data) { + std::unique_ptr> tl(new TensorList()); + tl->Resize(kernels::TensorListShape<3>({shape_})); + auto ptr = tl->template mutable_data(); + assert(data.size() == static_cast(volume(shape_))); + std::memcpy(ptr, data.data(), data.size() * sizeof(InputDataType)); + return tl; + } + + + std::vector input_; + kernels::TensorShape<3> shape_ = {2, 4, 3}; +}; + +namespace { + + +template +void BrightnessContrastVerify(TensorListWrapper input, TensorListWrapper output, Arguments args) { + static_assert(std::is_fundamental::value, ""); + auto input_tl = input.CopyTo(); + auto output_tl = output.CopyTo(); + auto brightness = args["brightness_delta"].GetValue(); + auto contrast = args["contrast_delta"].GetValue(); + ASSERT_EQ(input_tl->ntensor(), output_tl->ntensor()); + for (size_t t = 0; t < input.cpu().ntensor(); t++) { + auto out_shape = output_tl->tensor_shape(t); + auto out_tensor = output_tl->tensor(t); + auto in_shape = input_tl->tensor_shape(t); + auto in_tensor = input_tl->tensor(t); + ASSERT_EQ(in_shape, out_shape); + for (int i = 0; i < volume(out_shape); i++) { + EXPECT_EQ(out_tensor[i], ConvertSat(in_tensor[i] * contrast + brightness)); + } + } +} + + +Arguments args1 = { + {"output_type", DALI_UINT8}, + {"brightness_delta", 0.f}, + {"contrast_delta", 1.f} +}; +Arguments args2 = { + {"output_type", DALI_UINT8}, + {"brightness_delta", 1.f}, + {"contrast_delta", 0.f} +}; +Arguments args3 = { + {"output_type", DALI_UINT8}, + {"brightness_delta", 13.f}, + {"contrast_delta", 0.5f} +}; + +std::vector args_for_types = {args1, args2, args3}; + +} // namespace + +INSTANTIATE_TEST_SUITE_P(BrightnessContrastTest, BrightnessContrastTest, + ::testing::ValuesIn(testing::cartesian(utils::kDevices, args_for_types))); + + +TEST_P(BrightnessContrastTest, basic_test_float) { + auto tl = ToTensorList(this->input_); + auto args = GetParam(); + TensorListWrapper tlout; + this->RunTest(tl.get(), tlout, args, BrightnessContrastVerify); +} + + +TEST_P(BrightnessContrastTest, basic_test_int16) { + auto tl = ToTensorList(this->input_); + auto args = GetParam(); + TensorListWrapper tlout; + this->RunTest(tl.get(), tlout, args, BrightnessContrastVerify); +} + + +TEST_P(BrightnessContrastTest, basic_test_uint8) { + auto tl = ToTensorList(this->input_); + auto args = GetParam(); + TensorListWrapper tlout; + this->RunTest(tl.get(), tlout, args, BrightnessContrastVerify); +} + + +} // namespace brightness_contrast +} // namespace testing +} // namespace dali diff --git a/dali/pipeline/operators/util/external_source.h b/dali/pipeline/operators/util/external_source.h index c728f32713..369d83e2c0 100644 --- a/dali/pipeline/operators/util/external_source.h +++ b/dali/pipeline/operators/util/external_source.h @@ -197,6 +197,13 @@ class ExternalSource : public Operator { return false; } + /* + * So that compiler wouldn't complain, that + * "overloaded virtual function `dali::Operator::RunImpl` is only partially + * overridden in class `dali::brightness_contrast::BrightnessContrast`" + */ + using Operator::RunImpl; + void RunImpl(workspace_t &ws) override; void RecycleHelper(std::list &data) { diff --git a/dali/pipeline/workspace/sample_workspace.h b/dali/pipeline/workspace/sample_workspace.h index 711d6c79bf..6a84382acb 100644 --- a/dali/pipeline/workspace/sample_workspace.h +++ b/dali/pipeline/workspace/sample_workspace.h @@ -76,7 +76,9 @@ class DLL_PUBLIC SampleWorkspace : public WorkspaceBase class ToExecute, typename Ret, typename... T> Ret Switch_OpType_Device(OpType op_type, StorageDevice device, T &&... args) { + Ret ret; VALUE_SWITCH(op_type, op_type_static, (OpType::GPU, OpType::CPU, OpType::MIXED, OpType::SUPPORT), ( VALUE_SWITCH(device, device_static, (StorageDevice::CPU, StorageDevice::GPU), ( - return ToExecute{}(std::forward(args)...); + ret = ToExecute{}(std::forward(args)...); ), DALI_FAIL("Unexpected device")) // NOLINT(whitespace/parens) ), DALI_FAIL("Unexpected op_type")); // NOLINT(whitespace/parens) + return ret; } template