Repository: kornia/kornia

Issue:
[Feature Request] Implement addWeighted
we might want to implement `addWeighted` from OpenCV.

https://docs.opencv.org/3.4/d2/de8/group__core__array.html#gafafb2513349db3bcff51f54ee5592a19

the motivation is that all can be used as base to implement all the adjust functions


Discussion:
@edgarriba Would like to work on it if that is okay with you.

Just wondering in which of the current namespace will it fit or should there be a namespace with core functionality.
that would be awesome. We can place inside kornia.color by now. Ping us in case you need any help

Resolving patch:
diff --git a/kornia/color/__init__.py b/kornia/color/__init__.py
--- a/kornia/color/__init__.py
+++ b/kornia/color/__init__.py
@@ -7,6 +7,7 @@
 from .hls import RgbToHls, rgb_to_hls
 from .hls import HlsToRgb, hls_to_rgb
 from .normalize import Normalize, normalize
+from .core import add_weighted, AddWeighted
 from .adjust import (
     AdjustBrightness, AdjustContrast, AdjustGamma, AdjustHue, AdjustSaturation,
 )
@@ -29,6 +30,8 @@
     "adjust_gamma",
     "adjust_hue",
     "adjust_saturation",
+    "add_weighted",
+    "AddWeighted",
     "RgbToGrayscale",
     "BgrToGrayscale",
     "BgrToRgb",
diff --git a/kornia/color/core.py b/kornia/color/core.py
new file mode 100644
--- /dev/null
+++ b/kornia/color/core.py
@@ -0,0 +1,56 @@
+import torch
+import torch.nn as nn
+
+
+def add_weighted(src1: torch.Tensor, alpha: float,
+                 src2: torch.Tensor, beta: float,
+                 gamma: float) -> torch.Tensor:
+    r"""Blend two Tensors.
+
+    See :class:`~kornia.color.AddWeighted` for details.
+    """
+    if not isinstance(src1, torch.Tensor):
+        raise TypeError("src1 should be a tensor. Got {}".format(type(src1)))
+
+    if not isinstance(src2, torch.Tensor):
+        raise TypeError("src2 should be a tensor. Got {}".format(type(src2)))
+
+    if not isinstance(alpha, float):
+        raise TypeError("alpha should be a float. Got {}".format(type(alpha)))
+
+    if not isinstance(beta, float):
+        raise TypeError("beta should be a float. Got {}".format(type(beta)))
+
+    if not isinstance(gamma, float):
+        raise TypeError("gamma should be a float. Got {}".format(type(gamma)))
+
+    return src1 * alpha + src2 * beta + gamma
+
+
+class AddWeighted(nn.Module):
+    r"""Calculates the weighted sum of two Tensors.
+
+    The function calculates the weighted sum of two Tensors as follows:
+
+    .. math::
+        out = src1 * alpha + src2 * beta + gamma
+
+    Args:
+        src1 (torch.Tensor): Tensor.
+        alpha (float): weight of the src1 elements.
+        src2 (torch.Tensor): Tensor of same size and channel number as src1.
+        beta (float): weight of the src2 elements.
+        gamma (float): scalar added to each sum.
+
+    Returns:
+        torch.Tensor: Weighted Tensor.
+    """
+
+    def __init__(self, alpha: float, beta: float, gamma: float) -> None:
+        super(AddWeighted, self).__init__()
+        self.alpha = alpha
+        self.beta = beta
+        self.gamma = gamma
+
+    def forward(self, src1: torch.Tensor, src2: torch.Tensor) -> torch.Tensor:  # type: ignore
+        return add_weighted(src1, self.alpha, src2, self.beta, self.gamma)


Test patch:
diff --git a/test/color/test_core.py b/test/color/test_core.py
new file mode 100644
--- /dev/null
+++ b/test/color/test_core.py
@@ -0,0 +1,60 @@
+import pytest
+import random
+
+import kornia
+import kornia.testing as utils  # test utils
+from test.common import device_type
+
+import torch
+from torch.autograd import gradcheck
+from torch.testing import assert_allclose
+
+
+def random_shape(dim, min_elem=1, max_elem=10):
+    return tuple(random.randint(min_elem, max_elem) for _ in range(dim))
+
+
+class TestAddWeighted:
+    def get_input(self, size, max_elem=10):
+        shape = random_shape(size, max_elem)
+        src1 = torch.randn(shape)
+        src2 = torch.randn(shape)
+        alpha = random.random()
+        beta = random.random()
+        gamma = random.random()
+        return src1, src2, alpha, beta, gamma
+
+    @pytest.mark.parametrize("size", [2, 3, 4, 5])
+    def test_addweighted(self, size):
+        src1, src2, alpha, beta, gamma = self.get_input(3)
+
+        f = kornia.color.AddWeighted(alpha, beta, gamma)
+        assert_allclose(f(src1, src2), src1 * alpha + src2 * beta + gamma)
+
+    @pytest.mark.skip(reason="turn off all jit for a while")
+    def test_jit(self):
+        @torch.jit.script
+        def op_script(src1: torch.Tensor, alpha: float, src2: torch.Tensor,
+                      beta: float, gamma: float) -> torch.Tensor:
+            return kornia.color.add_weighted(src1, alpha, src2, beta, gamma)
+
+        src1, src2, alpha, beta, gamma = self.get_input(3)
+
+        actual = op_script(src1, alpha, src2, beta, gamma)
+        expected = kornia.color.add_weighted(src1, alpha, src2, beta, gamma)
+        assert_allclose(actual, expected)
+
+    @pytest.mark.parametrize("size", [2, 3])
+    def test_gradcheck(self, size):
+        shape = random_shape(size, max_elem=5)  # to shave time on gradcheck
+        src1 = torch.randn(shape)
+        src2 = torch.randn(shape)
+        alpha = random.random()
+        beta = random.random()
+        gamma = random.random()
+
+        src1 = utils.tensor_to_gradcheck_var(src1)  # to var
+        src2 = utils.tensor_to_gradcheck_var(src2)  # to var
+
+        assert gradcheck(kornia.color.AddWeighted(alpha, beta, gamma), (src1, src2),
+                         raise_exception=True)