Repository: tensorflow/addons

Issue:
Unify styles of custom operations
Currently, custom operations in addons have no unified styles. For example
the `name` argument in
https://github.com/tensorflow/addons/blob/463112e6e7c8d3623d3d97e6b1f365d1c403f2c4/tensorflow_addons/image/dense_image_warp.py#L161
and
https://github.com/tensorflow/addons/blob/463112e6e7c8d3623d3d97e6b1f365d1c403f2c4/tensorflow_addons/image/transform_ops.py#L38-L42
are not consistent.

As per [coding style for python operation in Tensorflow](https://www.tensorflow.org/community/contribute/code_style#python_operations), the last argument should be `name` with a default value of `None`. Though not all operations in core Tensorflow conform to this (see `tf.image` source code), I'd suppose we should unify the API/style of python operations in addons (like what we do for losses and layers). Maybe we can try to follow the link above first.


Discussion:
Good catch, I think `None` is better for default value, because it's easy to maintain API compatibility.
Got it, I'll create a PR for image ops first.
I think this is complete? Are there any other ops that need modifying?
Just find we miss some:
https://github.com/tensorflow/addons/blob/c2faddd818280815bd5e47b81742efdfc90e7aaf/tensorflow_addons/image/distance_transform.py#L34-L36
https://github.com/tensorflow/addons/blob/c2faddd818280815bd5e47b81742efdfc90e7aaf/tensorflow_addons/image/transform_ops.py#L320
https://github.com/tensorflow/addons/blob/c2faddd818280815bd5e47b81742efdfc90e7aaf/tensorflow_addons/image/median_filter_2d.py#L24

Some ops like `compose_transforms` in [transform_ops.py](https://github.com/tensorflow/addons/blob/master/tensorflow_addons/image/transform_ops.py) do not have `name` argument (even when they are in contrib though). Would it be better to add `name` to them for API compatibility?
1. For `rotate` op, we should replace `str(name)` by `(name or 'rotate')`. 
2. There exists an known annoying issue about `name`:  if we want to add new attribute, we have to append it after `name` for ensuring backward compatibility.  And all new attributes are forced to be optional. But I agree to conform to tensorflow convention: add `name` for all public API in image module. 
 cc @martinwicke Hi, Martin, do you have any good idea?

@seanpmorgan Sean, what do you think?
The name arg problem is annoying. 

We're not quite ready to remove the name args. At this point, they're exclusively used for visualization in TensorBoard. We have some ideas about visualizing summarized stack traces instead of manually set names, and once that works well, we can think about soft deprecating all name args. 

For now, I'd keep the name args.
Thank @martinwicke for the input. 

@WindQAQ Tzu-Wei, let's add `name` argument for all public operators.

Resolving patch:
diff --git a/tensorflow_addons/image/distance_transform.py b/tensorflow_addons/image/distance_transform.py
--- a/tensorflow_addons/image/distance_transform.py
+++ b/tensorflow_addons/image/distance_transform.py
@@ -31,9 +31,7 @@
 
 
 @tf.function
-def euclidean_dist_transform(images,
-                             dtype=tf.float32,
-                             name="euclidean_distance_transform"):
+def euclidean_dist_transform(images, dtype=tf.float32, name=None):
     """Applies euclidean distance transform(s) to the image(s).
 
     Args:
@@ -54,7 +52,7 @@ def euclidean_dist_transform(images,
         rank 3 or 4.
     """
 
-    with tf.name_scope(name):
+    with tf.name_scope(name or "euclidean_distance_transform"):
         image_or_images = tf.convert_to_tensor(images, name="images")
 
         if image_or_images.dtype.base_dtype != tf.uint8:
diff --git a/tensorflow_addons/image/median_filter_2d.py b/tensorflow_addons/image/median_filter_2d.py
--- a/tensorflow_addons/image/median_filter_2d.py
+++ b/tensorflow_addons/image/median_filter_2d.py
@@ -21,7 +21,7 @@
 
 
 @tf.function
-def median_filter_2D(image, filter_shape=(3, 3)):
+def median_filter_2D(image, filter_shape=(3, 3), name=None):
     """This method performs Median Filtering on image. Filter shape can be user
     given.
 
@@ -36,6 +36,7 @@ def median_filter_2D(image, filter_shape=(3, 3)):
                C is the second value in the filter is the number of columns
                in the filter. This creates a filter of shape (R,C) or RxC
                filter. Default value = (3,3)
+        name: The name of the op.
 
      Returns:
          A 3D median filtered image tensor of shape [rows,columns,channels] and
@@ -54,56 +55,58 @@ def func2():
 
         return tf.cond(tf.math.greater(ma, one), func2, func1)
 
-    if not isinstance(filter_shape, tuple):
-        raise TypeError('Filter shape must be a tuple')
-    if len(filter_shape) != 2:
-        raise ValueError('Filter shape must be a tuple of 2 integers. '
-                         'Got %s values in tuple' % len(filter_shape))
-    filter_shapex = filter_shape[0]
-    filter_shapey = filter_shape[1]
-    if not isinstance(filter_shapex, int) or not isinstance(
-            filter_shapey, int):
-        raise TypeError('Size of the filter must be Integers')
-    (row, col, ch) = (image.shape[0], image.shape[1], image.shape[2])
-    if row != None and col != None and ch != None:
-        (row, col, ch) = (int(row), int(col), int(ch))
-    else:
-        raise TypeError(
-            'All the Dimensions of the input image tensor must be Integers.')
-    if row < filter_shapex or col < filter_shapey:
-        raise ValueError(
-            'No of Pixels in each dimension of the image should be more \
-            than the filter size. Got filter_shape (%sx' % filter_shape[0] +
-            '%s).' % filter_shape[1] + ' Image Shape (%s)' % image.shape)
-    if filter_shapex % 2 == 0 or filter_shapey % 2 == 0:
-        raise ValueError('Filter size should be odd. Got filter_shape (%sx' %
-                         filter_shape[0] + '%s)' % filter_shape[1])
-    image = tf.cast(image, tf.float32)
-    tf_i = tf.reshape(image, [row * col * ch])
-    ma = tf.math.reduce_max(tf_i)
-    image = _normalize(image)
-
-    # k and l is the Zero-padding size
-
-    listi = []
-    for a in range(ch):
-        img = image[:, :, a:a + 1]
-        img = tf.reshape(img, [1, row, col, 1])
-        slic = tf.image.extract_image_patches(
-            img, [1, filter_shapex, filter_shapey, 1], [1, 1, 1, 1],
-            [1, 1, 1, 1],
-            padding='SAME')
-        mid = int(filter_shapex * filter_shapey / 2 + 1)
-        top = tf.nn.top_k(slic, mid, sorted=True)
-        li = tf.slice(top[0], [0, 0, 0, mid - 1], [-1, -1, -1, 1])
-        li = tf.reshape(li, [row, col, 1])
-        listi.append(li)
-    y = tf.concat(listi[0], 2)
-
-    for i in range(len(listi) - 1):
-        y = tf.concat([y, listi[i + 1]], 2)
-
-    y *= 255
-    y = tf.cast(y, tf.int32)
-
-    return y
+    with tf.name_scope(name or "median_filter_2D"):
+        if not isinstance(filter_shape, tuple):
+            raise TypeError('Filter shape must be a tuple')
+        if len(filter_shape) != 2:
+            raise ValueError('Filter shape must be a tuple of 2 integers. '
+                             'Got %s values in tuple' % len(filter_shape))
+        filter_shapex = filter_shape[0]
+        filter_shapey = filter_shape[1]
+        if not isinstance(filter_shapex, int) or not isinstance(
+                filter_shapey, int):
+            raise TypeError('Size of the filter must be Integers')
+        (row, col, ch) = (image.shape[0], image.shape[1], image.shape[2])
+        if row != None and col != None and ch != None:
+            (row, col, ch) = (int(row), int(col), int(ch))
+        else:
+            raise TypeError('All the Dimensions of the input image '
+                            'tensor must be Integers.')
+        if row < filter_shapex or col < filter_shapey:
+            raise ValueError(
+                'No of Pixels in each dimension of the image should be more \
+                than the filter size. Got filter_shape (%sx' % filter_shape[0]
+                + '%s).' % filter_shape[1] + ' Image Shape (%s)' % image.shape)
+        if filter_shapex % 2 == 0 or filter_shapey % 2 == 0:
+            raise ValueError(
+                'Filter size should be odd. Got filter_shape '
+                '(%sx%s)' % filter_shape[0], filter_shape[1])
+        image = tf.cast(image, tf.float32)
+        tf_i = tf.reshape(image, [row * col * ch])
+        ma = tf.math.reduce_max(tf_i)
+        image = _normalize(image)
+
+        # k and l is the Zero-padding size
+
+        listi = []
+        for a in range(ch):
+            img = image[:, :, a:a + 1]
+            img = tf.reshape(img, [1, row, col, 1])
+            slic = tf.image.extract_image_patches(
+                img, [1, filter_shapex, filter_shapey, 1], [1, 1, 1, 1],
+                [1, 1, 1, 1],
+                padding='SAME')
+            mid = int(filter_shapex * filter_shapey / 2 + 1)
+            top = tf.nn.top_k(slic, mid, sorted=True)
+            li = tf.slice(top[0], [0, 0, 0, mid - 1], [-1, -1, -1, 1])
+            li = tf.reshape(li, [row, col, 1])
+            listi.append(li)
+        y = tf.concat(listi[0], 2)
+
+        for i in range(len(listi) - 1):
+            y = tf.concat([y, listi[i + 1]], 2)
+
+        y *= 255
+        y = tf.cast(y, tf.int32)
+
+        return y
diff --git a/tensorflow_addons/image/transform_ops.py b/tensorflow_addons/image/transform_ops.py
--- a/tensorflow_addons/image/transform_ops.py
+++ b/tensorflow_addons/image/transform_ops.py
@@ -123,14 +123,15 @@ def transform(images,
 
 
 @tf.function
-def compose_transforms(*transforms):
+def compose_transforms(transforms, name=None):
     """Composes the transforms tensors.
 
     Args:
-      *transforms: List of image projective transforms to be composed. Each
-          transform is length 8 (single transform) or shape (N, 8) (batched
-          transforms). The shapes of all inputs must be equal, and at least one
-          input must be given.
+      transforms: List of image projective transforms to be composed. Each
+        transform is length 8 (single transform) or shape (N, 8) (batched
+        transforms). The shapes of all inputs must be equal, and at least one
+        input must be given.
+      name: The name for the op.
 
     Returns:
       A composed transform tensor. When passed to `transform` op,
@@ -138,7 +139,7 @@ def compose_transforms(*transforms):
           order.
     """
     assert transforms, "transforms cannot be empty"
-    with ops.name_scope("compose_transforms"):
+    with tf.name_scope(name or "compose_transforms"):
         composed = flat_transforms_to_matrices(transforms[0])
         for tr in transforms[1:]:
             # Multiply batches of matrices.
@@ -147,7 +148,7 @@ def compose_transforms(*transforms):
 
 
 @tf.function
-def flat_transforms_to_matrices(transforms):
+def flat_transforms_to_matrices(transforms, name=None):
     """Converts projective transforms to affine matrices.
 
     Note that the output matrices map output coordinates to input coordinates.
@@ -156,6 +157,7 @@ def flat_transforms_to_matrices(transforms):
     Args:
       transforms: Vector of length 8, or batches of transforms with shape
         `(N, 8)`.
+      name: The name for the op.
 
     Returns:
       3D tensor of matrices with shape `(N, 3, 3)`. The output matrices map the
@@ -165,7 +167,7 @@ def flat_transforms_to_matrices(transforms):
     Raises:
       ValueError: If `transforms` have an invalid shape.
     """
-    with ops.name_scope("flat_transforms_to_matrices"):
+    with tf.name_scope(name or "flat_transforms_to_matrices"):
         transforms = ops.convert_to_tensor(transforms, name="transforms")
         if transforms.shape.ndims not in (1, 2):
             raise ValueError(
@@ -180,7 +182,7 @@ def flat_transforms_to_matrices(transforms):
 
 
 @tf.function
-def matrices_to_flat_transforms(transform_matrices):
+def matrices_to_flat_transforms(transform_matrices, name=None):
     """Converts affine matrices to projective transforms.
 
     Note that we expect matrices that map output coordinates to input
@@ -191,6 +193,7 @@ def matrices_to_flat_transforms(transform_matrices):
       transform_matrices: One or more affine transformation matrices, for the
         reverse transformation in homogeneous coordinates. Shape `(3, 3)` or
         `(N, 3, 3)`.
+      name: The name for the op.
 
     Returns:
       2D tensor of flat transforms with shape `(N, 8)`, which may be passed
@@ -199,7 +202,7 @@ def matrices_to_flat_transforms(transform_matrices):
     Raises:
       ValueError: If `transform_matrices` have an invalid shape.
     """
-    with ops.name_scope("matrices_to_flat_transforms"):
+    with tf.name_scope(name or "matrices_to_flat_transforms"):
         transform_matrices = ops.convert_to_tensor(
             transform_matrices, name="transform_matrices")
         if transform_matrices.shape.ndims not in (2, 3):
@@ -296,7 +299,7 @@ def _image_projective_transform_grad(op, grad):
 @tf.function
 def rotate(images, angles, interpolation="NEAREST", name=None):
     """Rotate image(s) counterclockwise by the passed angle(s) in radians.
-    
+
     Args:
       images: A tensor of shape
         (num_images, num_rows, num_columns, num_channels)
@@ -309,15 +312,15 @@ def rotate(images, angles, interpolation="NEAREST", name=None):
       interpolation: Interpolation mode. Supported values: "NEAREST",
         "BILINEAR".
       name: The name of the op.
-      
+
     Returns:
       Image(s) with the same type and shape as `images`, rotated by the given
       angle(s). Empty space due to the rotation will be filled with zeros.
-      
+
     Raises:
       TypeError: If `image` is an invalid type.
     """
-    with tf.name_scope(str(name)):
+    with tf.name_scope(name or "rotate"):
         image_or_images = tf.convert_to_tensor(images)
         if image_or_images.dtype.base_dtype not in _IMAGE_DTYPES:
             raise TypeError("Invalid dtype %s." % image_or_images.dtype)


Test patch:
diff --git a/tensorflow_addons/image/transform_ops_test.py b/tensorflow_addons/image/transform_ops_test.py
--- a/tensorflow_addons/image/transform_ops_test.py
+++ b/tensorflow_addons/image/transform_ops_test.py
@@ -45,7 +45,8 @@ def test_compose(self):
             # hence the -1).
             translation = tf.constant([1, 0, -1, 0, 1, 0, 0, 0],
                                       dtype=tf.dtypes.float32)
-            composed = transform_ops.compose_transforms(rotation, translation)
+            composed = transform_ops.compose_transforms(
+                [rotation, translation])
             image_transformed = transform_ops.transform(image, composed)
             self.assertAllEqual(
                 [[0, 0, 0, 0], [0, 1, 0, 1], [0, 1, 0, 1], [0, 1, 1, 1]],
@@ -220,7 +221,8 @@ def test_compose_rotate(self):
             # hence the -1).
             translation = tf.constant([1, 0, -1, 0, 1, 0, 0, 0],
                                       dtype=tf.float32)
-            composed = transform_ops.compose_transforms(rotation, translation)
+            composed = transform_ops.compose_transforms(
+                [rotation, translation])
             image_transformed = transform_ops.transform(image, composed)
             self.assertAllEqual(
                 image_transformed,