From 45535c46901d7e5c2d06c191cb700770c2a11f09 Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Thu, 25 Apr 2019 20:02:27 -0700 Subject: [PATCH 01/16] Add bzl file to control CXX11_ABI --- tensorflow_addons/D_GLIBCXX_USE_CXX11_ABI.bzl | 1 + tensorflow_addons/custom_ops/image/BUILD | 6 ++++-- tensorflow_addons/custom_ops/seq2seq/BUILD | 4 +++- tensorflow_addons/custom_ops/text/BUILD | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 tensorflow_addons/D_GLIBCXX_USE_CXX11_ABI.bzl diff --git a/tensorflow_addons/D_GLIBCXX_USE_CXX11_ABI.bzl b/tensorflow_addons/D_GLIBCXX_USE_CXX11_ABI.bzl new file mode 100644 index 0000000000..871a646cff --- /dev/null +++ b/tensorflow_addons/D_GLIBCXX_USE_CXX11_ABI.bzl @@ -0,0 +1 @@ +D_GLIBCXX_USE_CXX11_ABI = "-D_GLIBCXX_USE_CXX11_ABI=0" \ No newline at end of file diff --git a/tensorflow_addons/custom_ops/image/BUILD b/tensorflow_addons/custom_ops/image/BUILD index d9ba3029f5..acf156791f 100644 --- a/tensorflow_addons/custom_ops/image/BUILD +++ b/tensorflow_addons/custom_ops/image/BUILD @@ -2,6 +2,8 @@ licenses(["notice"]) # Apache 2.0 package(default_visibility = ["//visibility:public"]) +load("//tensorflow_addons:D_GLIBCXX_USE_CXX11_ABI.bzl", "D_GLIBCXX_USE_CXX11_ABI") + cc_binary( name = "_distort_image_ops.so", srcs = [ @@ -12,7 +14,7 @@ cc_binary( copts = [ "-pthread", "-std=c++11", - "-D_GLIBCXX_USE_CXX11_ABI=0", + D_GLIBCXX_USE_CXX11_ABI, ], linkshared = 1, deps = [ @@ -33,7 +35,7 @@ cc_binary( copts = [ "-pthread", "-std=c++11", - "-D_GLIBCXX_USE_CXX11_ABI=0", + D_GLIBCXX_USE_CXX11_ABI, ], linkshared = 1, deps = [ diff --git a/tensorflow_addons/custom_ops/seq2seq/BUILD b/tensorflow_addons/custom_ops/seq2seq/BUILD index cf0849842e..5efa1db742 100644 --- a/tensorflow_addons/custom_ops/seq2seq/BUILD +++ b/tensorflow_addons/custom_ops/seq2seq/BUILD @@ -2,6 +2,8 @@ licenses(["notice"]) # Apache 2.0 package(default_visibility = ["//visibility:public"]) +load("//tensorflow_addons:D_GLIBCXX_USE_CXX11_ABI.bzl", "D_GLIBCXX_USE_CXX11_ABI") + cc_binary( name = "_beam_search_ops.so", srcs = [ @@ -13,7 +15,7 @@ cc_binary( copts = [ "-pthread", "-std=c++11", - "-D_GLIBCXX_USE_CXX11_ABI=0", + D_GLIBCXX_USE_CXX11_ABI, ], linkshared = 1, deps = [ diff --git a/tensorflow_addons/custom_ops/text/BUILD b/tensorflow_addons/custom_ops/text/BUILD index c8375f7b36..4bf34e2476 100644 --- a/tensorflow_addons/custom_ops/text/BUILD +++ b/tensorflow_addons/custom_ops/text/BUILD @@ -2,6 +2,8 @@ licenses(["notice"]) # Apache 2.0 package(default_visibility = ["//visibility:public"]) +load("//tensorflow_addons:D_GLIBCXX_USE_CXX11_ABI.bzl", "D_GLIBCXX_USE_CXX11_ABI") + cc_binary( name = "_skip_gram_ops.so", srcs = [ @@ -11,7 +13,7 @@ cc_binary( copts = [ "-pthread", "-std=c++11", - "-D_GLIBCXX_USE_CXX11_ABI=0", + D_GLIBCXX_USE_CXX11_ABI, ], linkshared = 1, deps = [ From 89855b623c8d3df5dc79e5ed58f3efc968c34e56 Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Sat, 4 May 2019 08:58:27 -0700 Subject: [PATCH 02/16] Rename macros --- .../{D_GLIBCXX_USE_CXX11_ABI.bzl => addons_bazel_macros.bzl} | 0 tensorflow_addons/custom_ops/image/BUILD | 2 +- tensorflow_addons/custom_ops/seq2seq/BUILD | 2 +- tensorflow_addons/custom_ops/text/BUILD | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename tensorflow_addons/{D_GLIBCXX_USE_CXX11_ABI.bzl => addons_bazel_macros.bzl} (100%) diff --git a/tensorflow_addons/D_GLIBCXX_USE_CXX11_ABI.bzl b/tensorflow_addons/addons_bazel_macros.bzl similarity index 100% rename from tensorflow_addons/D_GLIBCXX_USE_CXX11_ABI.bzl rename to tensorflow_addons/addons_bazel_macros.bzl diff --git a/tensorflow_addons/custom_ops/image/BUILD b/tensorflow_addons/custom_ops/image/BUILD index acf156791f..59d9267ce4 100644 --- a/tensorflow_addons/custom_ops/image/BUILD +++ b/tensorflow_addons/custom_ops/image/BUILD @@ -2,7 +2,7 @@ licenses(["notice"]) # Apache 2.0 package(default_visibility = ["//visibility:public"]) -load("//tensorflow_addons:D_GLIBCXX_USE_CXX11_ABI.bzl", "D_GLIBCXX_USE_CXX11_ABI") +load("//tensorflow_addons:addons_bazel_macros.bzl", "D_GLIBCXX_USE_CXX11_ABI") cc_binary( name = "_distort_image_ops.so", diff --git a/tensorflow_addons/custom_ops/seq2seq/BUILD b/tensorflow_addons/custom_ops/seq2seq/BUILD index 5efa1db742..733db76a93 100644 --- a/tensorflow_addons/custom_ops/seq2seq/BUILD +++ b/tensorflow_addons/custom_ops/seq2seq/BUILD @@ -2,7 +2,7 @@ licenses(["notice"]) # Apache 2.0 package(default_visibility = ["//visibility:public"]) -load("//tensorflow_addons:D_GLIBCXX_USE_CXX11_ABI.bzl", "D_GLIBCXX_USE_CXX11_ABI") +load("//tensorflow_addons:addons_bazel_macros.bzl", "D_GLIBCXX_USE_CXX11_ABI") cc_binary( name = "_beam_search_ops.so", diff --git a/tensorflow_addons/custom_ops/text/BUILD b/tensorflow_addons/custom_ops/text/BUILD index 4bf34e2476..8bc1db6d63 100644 --- a/tensorflow_addons/custom_ops/text/BUILD +++ b/tensorflow_addons/custom_ops/text/BUILD @@ -2,7 +2,7 @@ licenses(["notice"]) # Apache 2.0 package(default_visibility = ["//visibility:public"]) -load("//tensorflow_addons:D_GLIBCXX_USE_CXX11_ABI.bzl", "D_GLIBCXX_USE_CXX11_ABI") +load("//tensorflow_addons:addons_bazel_macros.bzl", "D_GLIBCXX_USE_CXX11_ABI") cc_binary( name = "_skip_gram_ops.so", From 6568587ba0831b95e7df43d4b01f54694510bede Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Sat, 4 May 2019 09:23:26 -0700 Subject: [PATCH 03/16] Add tf.sysconfig lookup --- configure.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure.sh b/configure.sh index cc0d9d0fee..248aaa9959 100755 --- a/configure.sh +++ b/configure.sh @@ -45,7 +45,9 @@ pip install $QUIET_FLAG -r requirements.txt TF_CFLAGS=( $(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_compile_flags()))') ) TF_LFLAGS=( $(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_link_flags()))') ) TF_SHAREDLIB=( $(python -c 'import tensorflow as tf; print(tf.sysconfig.get_link_flags()[-1])') ) +TF_CXX11_ABI_FLAG=( $(python -c 'import tensorflow as tf; print(tf.sysconfig.CXX11_ABI_FLAG)') ) write_action_env_to_bazelrc "TF_HEADER_DIR" ${TF_CFLAGS:2} write_action_env_to_bazelrc "TF_SHARED_LIBRARY_DIR" ${TF_LFLAGS:2} write_action_env_to_bazelrc "TF_SHARED_LIBRARY_NAME" ${TF_SHAREDLIB:3} +write_action_env_to_bazelrc "TF_CXX11_ABI_FLAG" ${TF_CXX11_ABI_FLAG:1} From 8294024b1b52b201292266c864e95607344f10cd Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Sun, 5 May 2019 11:59:16 -0700 Subject: [PATCH 04/16] Add TODO note in configure --- configure.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.sh b/configure.sh index 248aaa9959..d1aeec17d7 100755 --- a/configure.sh +++ b/configure.sh @@ -51,3 +51,4 @@ write_action_env_to_bazelrc "TF_HEADER_DIR" ${TF_CFLAGS:2} write_action_env_to_bazelrc "TF_SHARED_LIBRARY_DIR" ${TF_LFLAGS:2} write_action_env_to_bazelrc "TF_SHARED_LIBRARY_NAME" ${TF_SHAREDLIB:3} write_action_env_to_bazelrc "TF_CXX11_ABI_FLAG" ${TF_CXX11_ABI_FLAG:1} +# TODO: propagate TF_* variables to bazel macro file From 04eed3feb11278dff091bd76cf54d9109bba341d Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Sun, 5 May 2019 16:45:01 -0700 Subject: [PATCH 05/16] Fix --- configure.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.sh b/configure.sh index d1aeec17d7..ea7068dd51 100755 --- a/configure.sh +++ b/configure.sh @@ -50,5 +50,5 @@ TF_CXX11_ABI_FLAG=( $(python -c 'import tensorflow as tf; print(tf.sysconfig.CXX write_action_env_to_bazelrc "TF_HEADER_DIR" ${TF_CFLAGS:2} write_action_env_to_bazelrc "TF_SHARED_LIBRARY_DIR" ${TF_LFLAGS:2} write_action_env_to_bazelrc "TF_SHARED_LIBRARY_NAME" ${TF_SHAREDLIB:3} -write_action_env_to_bazelrc "TF_CXX11_ABI_FLAG" ${TF_CXX11_ABI_FLAG:1} +write_action_env_to_bazelrc "TF_CXX11_ABI_FLAG" ${TF_CXX11_ABI_FLAG} # TODO: propagate TF_* variables to bazel macro file From 5805f7ca23af331497a510364ca8074002b67c54 Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Sat, 18 May 2019 11:28:29 -0700 Subject: [PATCH 06/16] Add sparse_image_warp and interpolate_spline --- tensorflow_addons/image/BUILD | 28 ++ tensorflow_addons/image/__init__.py | 2 + tensorflow_addons/image/interpolate_spline.py | 297 +++++++++++++++ .../image/interpolate_spline_test.py | 352 ++++++++++++++++++ tensorflow_addons/image/sparse_image_warp.py | 200 ++++++++++ .../image/sparse_image_warp_test.py | 253 +++++++++++++ 6 files changed, 1132 insertions(+) create mode 100644 tensorflow_addons/image/interpolate_spline.py create mode 100644 tensorflow_addons/image/interpolate_spline_test.py create mode 100644 tensorflow_addons/image/sparse_image_warp.py create mode 100644 tensorflow_addons/image/sparse_image_warp_test.py diff --git a/tensorflow_addons/image/BUILD b/tensorflow_addons/image/BUILD index daeb47d3a8..8b8075438b 100644 --- a/tensorflow_addons/image/BUILD +++ b/tensorflow_addons/image/BUILD @@ -11,6 +11,8 @@ py_library( "distort_image_ops.py", "filters.py", "transform_ops.py", + "sparse_image_warp.py", + "interpolate_spline.py", ]), data = [ "//tensorflow_addons/custom_ops/image:_distort_image_ops.so", @@ -85,3 +87,29 @@ py_test( ":image", ], ) + +py_test( + name = "sparse_image_warp_test", + size = "medium", + srcs = [ + "sparse_image_warp_test.py", + ], + main = "sparse_image_warp_test.py", + srcs_version = "PY2AND3", + deps = [ + ":image", + ], +) + +py_test( + name = "interpolate_spline_test", + size = "medium", + srcs = [ + "interpolate_spline_test.py", + ], + main = "interpolate_spline_test.py", + srcs_version = "PY2AND3", + deps = [ + ":image", + ], +) diff --git a/tensorflow_addons/image/__init__.py b/tensorflow_addons/image/__init__.py index 15028405f8..dc019fd559 100644 --- a/tensorflow_addons/image/__init__.py +++ b/tensorflow_addons/image/__init__.py @@ -26,3 +26,5 @@ from tensorflow_addons.image.filters import median_filter2d from tensorflow_addons.image.transform_ops import rotate from tensorflow_addons.image.transform_ops import transform +from tensorflow_addons.image.sparse_image_warp import sparse_image_warp +from tensorflow_addons.image.interpolate_spline import interpolate_spline diff --git a/tensorflow_addons/image/interpolate_spline.py b/tensorflow_addons/image/interpolate_spline.py new file mode 100644 index 0000000000..64e05e2e81 --- /dev/null +++ b/tensorflow_addons/image/interpolate_spline.py @@ -0,0 +1,297 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Polyharmonic spline interpolation.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +EPSILON = 0.0000000001 + + +def _cross_squared_distance_matrix(x, y): + """Pairwise squared distance between two (batch) matrices' rows (2nd dim). + + Computes the pairwise distances between rows of x and rows of y + Args: + x: [batch_size, n, d] float `Tensor` + y: [batch_size, m, d] float `Tensor` + + Returns: + squared_dists: [batch_size, n, m] float `Tensor`, where + squared_dists[b,i,j] = ||x[b,i,:] - y[b,j,:]||^2 + """ + x_norm_squared = tf.reduce_sum(tf.square(x), 2) + y_norm_squared = tf.reduce_sum(tf.square(y), 2) + + # Expand so that we can broadcast. + x_norm_squared_tile = tf.expand_dims(x_norm_squared, 2) + y_norm_squared_tile = tf.expand_dims(y_norm_squared, 1) + + x_y_transpose = tf.matmul(x, y, adjoint_b=True) + + # squared_dists[b,i,j] = ||x_bi - y_bj||^2 = x_bi'x_bi- 2x_bi'x_bj + x_bj'x_bj + squared_dists = x_norm_squared_tile - 2 * x_y_transpose + y_norm_squared_tile + + return squared_dists + + +def _pairwise_squared_distance_matrix(x): + """Pairwise squared distance among a (batch) matrix's rows (2nd dim). + + This saves a bit of computation vs. using _cross_squared_distance_matrix(x,x) + + Args: + x: `[batch_size, n, d]` float `Tensor` + + Returns: + squared_dists: `[batch_size, n, n]` float `Tensor`, where + squared_dists[b,i,j] = ||x[b,i,:] - x[b,j,:]||^2 + """ + + x_x_transpose = tf.matmul(x, x, adjoint_b=True) + x_norm_squared = tf.matrix_diag_part(x_x_transpose) + x_norm_squared_tile = tf.expand_dims(x_norm_squared, 2) + + # squared_dists[b,i,j] = ||x_bi - x_bj||^2 = x_bi'x_bi- 2x_bi'x_bj + x_bj'x_bj + squared_dists = x_norm_squared_tile - 2 * x_x_transpose + tf.transpose( + x_norm_squared_tile, [0, 2, 1]) + + return squared_dists + + +def _solve_interpolation(train_points, train_values, order, + regularization_weight): + """Solve for interpolation coefficients. + + Computes the coefficients of the polyharmonic interpolant for the 'training' + data defined by (train_points, train_values) using the kernel phi. + + Args: + train_points: `[b, n, d]` interpolation centers + train_values: `[b, n, k]` function values + order: order of the interpolation + regularization_weight: weight to place on smoothness regularization term + + Returns: + w: `[b, n, k]` weights on each interpolation center + v: `[b, d, k]` weights on each input dimension + Raises: + ValueError: if d or k is not fully specified. + """ + + # These dimensions are set dynamically at runtime. + b, n, _ = tf.unstack(tf.shape(train_points), num=3) + + d = train_points.shape[-1] + if tf.dimension_value(d) is None: + raise ValueError('The dimensionality of the input points (d) must be ' + 'statically-inferrable.') + + k = train_values.shape[-1] + if tf.dimension_value(k) is None: + raise ValueError('The dimensionality of the output values (k) must be ' + 'statically-inferrable.') + + # First, rename variables so that the notation (c, f, w, v, A, B, etc.) + # follows https://en.wikipedia.org/wiki/Polyharmonic_spline. + # To account for python style guidelines we use + # matrix_a for A and matrix_b for B. + + c = train_points + f = train_values + + # Next, construct the linear system. + with tf.name_scope('construct_linear_system'): + + matrix_a = _phi(_pairwise_squared_distance_matrix(c), + order) # [b, n, n] + if regularization_weight > 0: + batch_identity_matrix = tf.expand_dims( + tf.eye(n, dtype=c.dtype), 0) + matrix_a += regularization_weight * batch_identity_matrix + + # Append ones to the feature values for the bias term in the linear model. + ones = tf.ones_like(c[..., :1], dtype=c.dtype) + matrix_b = tf.concat([c, ones], 2) # [b, n, d + 1] + + # [b, n + d + 1, n] + left_block = tf.concat( + [matrix_a, tf.transpose(matrix_b, [0, 2, 1])], 1) + + num_b_cols = matrix_b.get_shape()[2] # d + 1 + lhs_zeros = tf.zeros([b, num_b_cols, num_b_cols], + train_points.dtype) + right_block = tf.concat([matrix_b, lhs_zeros], + 1) # [b, n + d + 1, d + 1] + lhs = tf.concat([left_block, right_block], + 2) # [b, n + d + 1, n + d + 1] + + rhs_zeros = tf.zeros([b, d + 1, k], train_points.dtype) + rhs = tf.concat([f, rhs_zeros], 1) # [b, n + d + 1, k] + + # Then, solve the linear system and unpack the results. + with tf.name_scope('solve_linear_system'): + w_v = tf.matrix_solve(lhs, rhs) + w = w_v[:, :n, :] + v = w_v[:, n:, :] + + return w, v + + +def _apply_interpolation(query_points, train_points, w, v, order): + """Apply polyharmonic interpolation model to data. + + Given coefficients w and v for the interpolation model, we evaluate + interpolated function values at query_points. + + Args: + query_points: `[b, m, d]` x values to evaluate the interpolation at + train_points: `[b, n, d]` x values that act as the interpolation centers + ( the c variables in the wikipedia article) + w: `[b, n, k]` weights on each interpolation center + v: `[b, d, k]` weights on each input dimension + order: order of the interpolation + + Returns: + Polyharmonic interpolation evaluated at points defined in query_points. + """ + + # First, compute the contribution from the rbf term. + pairwise_dists = _cross_squared_distance_matrix(query_points, train_points) + phi_pairwise_dists = _phi(pairwise_dists, order) + + rbf_term = tf.matmul(phi_pairwise_dists, w) + + # Then, compute the contribution from the linear term. + # Pad query_points with ones, for the bias term in the linear model. + query_points_pad = tf.concat([ + query_points, + tf.ones_like(query_points[..., :1], train_points.dtype) + ], 2) + linear_term = tf.matmul(query_points_pad, v) + + return rbf_term + linear_term + + +def _phi(r, order): + """Coordinate-wise nonlinearity used to define the order of the interpolation. + + See https://en.wikipedia.org/wiki/Polyharmonic_spline for the definition. + + Args: + r: input op + order: interpolation order + + Returns: + phi_k evaluated coordinate-wise on r, for k = r + """ + + # using EPSILON prevents log(0), sqrt0), etc. + # sqrt(0) is well-defined, but its gradient is not + with tf.name_scope('phi'): + if order == 1: + r = tf.maximum(r, EPSILON) + r = tf.sqrt(r) + return r + elif order == 2: + return 0.5 * r * tf.log(tf.maximum(r, EPSILON)) + elif order == 4: + return 0.5 * tf.square(r) * tf.log( + tf.maximum(r, EPSILON)) + elif order % 2 == 0: + r = tf.maximum(r, EPSILON) + return 0.5 * tf.pow(r, 0.5 * order) * tf.log(r) + else: + r = tf.maximum(r, EPSILON) + return tf.pow(r, 0.5 * order) + + +def interpolate_spline(train_points, + train_values, + query_points, + order, + regularization_weight=0.0, + name='interpolate_spline'): + r"""Interpolate signal using polyharmonic interpolation. + + The interpolant has the form + $$f(x) = \sum_{i = 1}^n w_i \phi(||x - c_i||) + v^T x + b.$$ + + This is a sum of two terms: (1) a weighted sum of radial basis function (RBF) + terms, with the centers \\(c_1, ... c_n\\), and (2) a linear term with a bias. + The \\(c_i\\) vectors are 'training' points. In the code, b is absorbed into v + by appending 1 as a final dimension to x. The coefficients w and v are + estimated such that the interpolant exactly fits the value of the function at + the \\(c_i\\) points, the vector w is orthogonal to each \\(c_i\\), and the + vector w sums to 0. With these constraints, the coefficients can be obtained + by solving a linear system. + + \\(\phi\\) is an RBF, parametrized by an interpolation + order. Using order=2 produces the well-known thin-plate spline. + + We also provide the option to perform regularized interpolation. Here, the + interpolant is selected to trade off between the squared loss on the training + data and a certain measure of its curvature + ([details](https://en.wikipedia.org/wiki/Polyharmonic_spline)). + Using a regularization weight greater than zero has the effect that the + interpolant will no longer exactly fit the training data. However, it may be + less vulnerable to overfitting, particularly for high-order interpolation. + + Note the interpolation procedure is differentiable with respect to all inputs + besides the order parameter. + + We support dynamically-shaped inputs, where batch_size, n, and m are None + at graph construction time. However, d and k must be known. + + Args: + train_points: `[batch_size, n, d]` float `Tensor` of n d-dimensional + locations. These do not need to be regularly-spaced. + train_values: `[batch_size, n, k]` float `Tensor` of n c-dimensional values + evaluated at train_points. + query_points: `[batch_size, m, d]` `Tensor` of m d-dimensional locations + where we will output the interpolant's values. + order: order of the interpolation. Common values are 1 for + \\(\phi(r) = r\\), 2 for \\(\phi(r) = r^2 * log(r)\\) (thin-plate spline), + or 3 for \\(\phi(r) = r^3\\). + regularization_weight: weight placed on the regularization term. + This will depend substantially on the problem, and it should always be + tuned. For many problems, it is reasonable to use no regularization. + If using a non-zero value, we recommend a small value like 0.001. + name: name prefix for ops created by this function + + Returns: + `[b, m, k]` float `Tensor` of query values. We use train_points and + train_values to perform polyharmonic interpolation. The query values are + the values of the interpolant evaluated at the locations specified in + query_points. + """ + with tf.name_scope(name or "interpolate_spline"): + train_points = tf.convert_to_tensor(train_points) + train_values = tf.convert_to_tensor(train_values) + query_points = tf.convert_to_tensor(query_points) + + # First, fit the spline to the observed data. + with tf.name_scope('solve'): + w, v = _solve_interpolation(train_points, train_values, order, + regularization_weight) + + # Then, evaluate the spline at the query locations. + with tf.name_scope('predict'): + query_values = _apply_interpolation(query_points, train_points, w, + v, order) + + return query_values diff --git a/tensorflow_addons/image/interpolate_spline_test.py b/tensorflow_addons/image/interpolate_spline_test.py new file mode 100644 index 0000000000..881eba240f --- /dev/null +++ b/tensorflow_addons/image/interpolate_spline_test.py @@ -0,0 +1,352 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for interpolate_spline.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from scipy import interpolate as sc_interpolate + +import tensorflow as tf +import tensorflow.compat.v1 as tf1 # TODO: locate placeholder +from tensorflow.python.training import momentum +from tensorflow_addons.image import interpolate_spline + + +class _InterpolationProblem(object): + """Abstract class for interpolation problem descriptions.""" + + def get_problem(self, optimizable=False, extrapolate=True, + dtype='float32'): + """Make data for an interpolation problem where all x vectors are n-d. + + Args: + optimizable: If True, then make train_points a tf.Variable. + extrapolate: If False, then clamp the query_points values to be within + the max and min of train_points. + dtype: The data type to use. + + Returns: + query_points, query_values, train_points, train_values: training and + test tensors for interpolation problem + """ + + # The values generated here depend on a seed of 0. + np.random.seed(0) + + batch_size = 1 + num_training_points = 10 + num_query_points = 4 + + init_points = np.random.uniform( + size=[batch_size, num_training_points, self.DATA_DIM]) + + init_points = init_points.astype(dtype) + train_points = (tf.Variable(init_points) + if optimizable else tf.constant(init_points)) + train_values = self.tf_function(train_points) + + query_points_np = np.random.uniform( + size=[batch_size, num_query_points, self.DATA_DIM]) + query_points_np = query_points_np.astype(dtype) + if not extrapolate: + query_points_np = np.clip(query_points_np, np.min(init_points), + np.max(init_points)) + + query_points = tf.constant(query_points_np) + query_values = self.np_function(query_points_np) + + return query_points, query_values, train_points, train_values + + +class _QuadraticPlusSinProblem1D(_InterpolationProblem): + """1D interpolation problem used for regression testing.""" + DATA_DIM = 1 + HARDCODED_QUERY_VALUES = { + (1.0, 0.0): + [6.2647187603, -7.84362604077, -5.63690142322, 1.42928896387], + (1.0, 0.01): + [6.77688289946, -8.02163669853, -5.79491157027, 1.4063285693], + (2.0, 0.0): + [8.67110264937, -8.41281390883, -5.80190044693, 1.50155606059], + (2.0, 0.01): + [6.70797816797, -7.49709587663, -5.28965776238, 1.52284731741], + (3.0, 0.0): + [9.37691802935, -8.50390141515, -5.80786417426, 1.63467762122], + (3.0, 0.01): + [4.47106304758, -5.71266128361, -3.92529303296, 1.86755293857], + (4.0, 0.0): + [9.58172461111, -8.51432104771, -5.80967675388, 1.63361164256], + (4.0, 0.01): + [-3.87902711352, -0.0253462273846, 1.79857618022, -0.769339675725] + } + + def np_function(self, x): + """Takes np array, evaluates the test function, and returns np array.""" + return np.sum(np.power((x - 0.5), 3) - 0.25 * x + 10 * np.sin(x * 10), + axis=2, + keepdims=True) + + def tf_function(self, x): + """Takes tf tensor, evaluates the test function, and returns tf tensor.""" + return tf.reduce_mean(tf.pow( + (x - 0.5), 3) - 0.25 * x + 10 * tf.sin(x * 10), + 2, + keepdims=True) + + +class _QuadraticPlusSinProblemND(_InterpolationProblem): + """3D interpolation problem used for regression testing.""" + + DATA_DIM = 3 + HARDCODED_QUERY_VALUES = { + (1.0, 0.0): + [1.06609663962, 1.28894849357, 1.10882405595, 1.63966936885], + (1.0, 0.01): + [1.03123780748, 1.2952930985, 1.10366822954, 1.65265118569], + (2.0, 0.0): + [0.627787735064, 1.43802857251, 1.00194632358, 1.91667538215], + (2.0, 0.01): + [0.730159985046, 1.41702471595, 1.0065827217, 1.85758519312], + (3.0, 0.0): + [0.350460417862, 1.67223539464, 1.00475331246, 2.31580322491], + (3.0, 0.01): + [0.624557250556, 1.63138876667, 0.976588193162, 2.12511237866], + (4.0, 0.0): + [0.898129669986, 1.24434133638, -0.938056116931, 1.59910338833], + (4.0, 0.01): + [0.0930360338179, -3.38791305538, -1.00969032567, 0.745535080382], + } + + def np_function(self, x): + """Takes np array, evaluates the test function, and returns np array.""" + return np.sum(np.square(x - 0.5) + 0.25 * x + 1 * np.sin(x * 15), + axis=2, + keepdims=True) + + def tf_function(self, x): + """Takes tf tensor, evaluates the test function, and returns tf tensor.""" + return tf.reduce_sum(tf.square(x - 0.5) + 0.25 * x + + 1 * tf.sin(x * 15), + 2, + keepdims=True) + + +class InterpolateSplineTest(tf.test.TestCase): + def test_1d_linear_interpolation(self): + """For 1d linear interpolation, we can compare directly to scipy.""" + + tp = _QuadraticPlusSinProblem1D() + (query_points, _, train_points, + train_values) = tp.get_problem(extrapolate=False, dtype='float64') + interpolation_order = 1 + + with tf.name_scope('interpolator'): + interpolator = interpolate_spline.interpolate_spline( + train_points, train_values, query_points, interpolation_order) + with self.cached_session() as sess: + fetches = [ + query_points, train_points, train_values, interpolator + ] + query_points_, train_points_, train_values_, interp_ = sess.run( + fetches) + + # Just look at the first element of the minibatch. + # Also, trim the final singleton dimension. + interp_ = interp_[0, :, 0] + query_points_ = query_points_[0, :, 0] + train_points_ = train_points_[0, :, 0] + train_values_ = train_values_[0, :, 0] + + # Compute scipy interpolation. + scipy_interp_function = sc_interpolate.interp1d(train_points_, + train_values_, + kind='linear') + + scipy_interpolation = scipy_interp_function(query_points_) + scipy_interpolation_on_train = scipy_interp_function( + train_points_) + + # Even with float64 precision, the interpolants disagree with scipy a + # bit due to the fact that we add the EPSILON to prevent sqrt(0), etc. + tol = 1e-3 + + self.assertAllClose(train_values_, + scipy_interpolation_on_train, + atol=tol, + rtol=tol) + self.assertAllClose(interp_, + scipy_interpolation, + atol=tol, + rtol=tol) + + def test_1d_interpolation(self): + """Regression test for interpolation with 1-D points.""" + + tp = _QuadraticPlusSinProblem1D() + (query_points, _, train_points, + train_values) = tp.get_problem(dtype='float64') + + for order in (1, 2, 3): + for reg_weight in (0, 0.01): + interpolator = interpolate_spline.interpolate_spline( + train_points, train_values, query_points, order, + reg_weight) + + target_interpolation = tp.HARDCODED_QUERY_VALUES[(order, + reg_weight)] + target_interpolation = np.array(target_interpolation) + with self.cached_session() as sess: + interp_val = sess.run(interpolator) + self.assertAllClose(interp_val[0, :, 0], + target_interpolation) + + def test_nd_linear_interpolation(self): + """Regression test for interpolation with N-D points.""" + + tp = _QuadraticPlusSinProblemND() + (query_points, _, train_points, + train_values) = tp.get_problem(dtype='float64') + + for order in (1, 2, 3): + for reg_weight in (0, 0.01): + interpolator = interpolate_spline.interpolate_spline( + train_points, train_values, query_points, order, + reg_weight) + + target_interpolation = tp.HARDCODED_QUERY_VALUES[(order, + reg_weight)] + target_interpolation = np.array(target_interpolation) + with self.cached_session() as sess: + interp_val = sess.run(interpolator) + self.assertAllClose(interp_val[0, :, 0], + target_interpolation) + + def test_nd_linear_interpolation_unspecified_shape(self): + """Ensure that interpolation supports dynamic batch_size and num_points.""" + + tp = _QuadraticPlusSinProblemND() + (query_points, _, train_points, + train_values) = tp.get_problem(dtype='float64') + + # Construct placeholders such that the batch size, number of train points, + # and number of query points are not known at graph construction time. + feature_dim = query_points.shape[-1] + value_dim = train_values.shape[-1] + train_points_ph = tf1.placeholder( + dtype=train_points.dtype, shape=[None, None, feature_dim]) + train_values_ph = tf1.placeholder(dtype=train_values.dtype, + shape=[None, None, value_dim]) + query_points_ph = tf1.placeholder( + dtype=query_points.dtype, shape=[None, None, feature_dim]) + + order = 1 + reg_weight = 0.01 + + interpolator = interpolate_spline.interpolate_spline( + train_points_ph, train_values_ph, query_points_ph, order, + reg_weight) + + target_interpolation = tp.HARDCODED_QUERY_VALUES[(order, reg_weight)] + target_interpolation = np.array(target_interpolation) + with self.cached_session() as sess: + + (train_points_value, train_values_value, + query_points_value) = sess.run( + [train_points, train_values, query_points]) + + interp_val = sess.run(interpolator, + feed_dict={ + train_points_ph: train_points_value, + train_values_ph: train_values_value, + query_points_ph: query_points_value + }) + self.assertAllClose(interp_val[0, :, 0], target_interpolation) + + def test_fully_unspecified_shape(self): + """Ensure that erreor is thrown when input/output dim unspecified.""" + + tp = _QuadraticPlusSinProblemND() + (query_points, _, train_points, + train_values) = tp.get_problem(dtype='float64') + + # Construct placeholders such that the batch size, number of train points, + # and number of query points are not known at graph construction time. + feature_dim = query_points.shape[-1] + value_dim = train_values.shape[-1] + train_points_ph = tf1.placeholder( + dtype=train_points.dtype, shape=[None, None, feature_dim]) + train_points_ph_invalid = tf1.placeholder( + dtype=train_points.dtype, shape=[None, None, None]) + train_values_ph = tf1.placeholder(dtype=train_values.dtype, + shape=[None, None, value_dim]) + train_values_ph_invalid = tf1.placeholder( + dtype=train_values.dtype, shape=[None, None, None]) + query_points_ph = tf1.placeholder( + dtype=query_points.dtype, shape=[None, None, feature_dim]) + + order = 1 + reg_weight = 0.01 + + with self.assertRaises(ValueError): + _ = interpolate_spline.interpolate_spline(train_points_ph_invalid, + train_values_ph, + query_points_ph, order, + reg_weight) + + with self.assertRaises(ValueError): + _ = interpolate_spline.interpolate_spline(train_points_ph, + train_values_ph_invalid, + query_points_ph, order, + reg_weight) + + def test_interpolation_gradient(self): + """Make sure that backprop can run. Correctness of gradients is assumed. + + Here, we create a use a small 'training' set and a more densely-sampled + set of query points, for which we know the true value in advance. The goal + is to choose x locations for the training data such that interpolating using + this training data yields the best reconstruction for the function + values at the query points. The training data locations are optimized + iteratively using gradient descent. + """ + tp = _QuadraticPlusSinProblemND() + (query_points, query_values, train_points, + train_values) = tp.get_problem(optimizable=True) + + regularization = 0.001 + for interpolation_order in (1, 2, 3, 4): + interpolator = interpolate_spline.interpolate_spline( + train_points, train_values, query_points, interpolation_order, + regularization) + + loss = tf.reduce_mean( + tf.square(query_values - interpolator)) + + optimizer = momentum.MomentumOptimizer(0.001, 0.9) + grad = tf.gradients(loss, [train_points]) + grad, _ = tf.clip_by_global_norm(grad, 1.0) + opt_func = optimizer.apply_gradients(zip(grad, [train_points])) + init_op = tf1.variables.global_variables_initializer() + + with self.cached_session() as sess: + sess.run(init_op) + for _ in range(100): + sess.run([loss, opt_func]) + + +if __name__ == '__main__': + tf.test.main() diff --git a/tensorflow_addons/image/sparse_image_warp.py b/tensorflow_addons/image/sparse_image_warp.py new file mode 100644 index 0000000000..db0962c65e --- /dev/null +++ b/tensorflow_addons/image/sparse_image_warp.py @@ -0,0 +1,200 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Image warping using sparse flow defined at control points.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf +from tensorflow.python.framework import tensor_shape # TODO: better import +from tensorflow_addons.image import dense_image_warp +from tensorflow_addons.image import interpolate_spline + + +def _get_grid_locations(image_height, image_width): + """Wrapper for np.meshgrid.""" + + y_range = np.linspace(0, image_height - 1, image_height) + x_range = np.linspace(0, image_width - 1, image_width) + y_grid, x_grid = np.meshgrid(y_range, x_range, indexing='ij') + return np.stack((y_grid, x_grid), -1) + + +def _expand_to_minibatch(np_array, batch_size): + """Tile arbitrarily-sized np_array to include new batch dimension.""" + tiles = [batch_size] + [1] * np_array.ndim + return np.tile(np.expand_dims(np_array, 0), tiles) + + +def _get_boundary_locations(image_height, image_width, num_points_per_edge): + """Compute evenly-spaced indices along edge of image.""" + y_range = np.linspace(0, image_height - 1, num_points_per_edge + 2) + x_range = np.linspace(0, image_width - 1, num_points_per_edge + 2) + ys, xs = np.meshgrid(y_range, x_range, indexing='ij') + is_boundary = np.logical_or(np.logical_or(xs == 0, xs == image_width - 1), + np.logical_or(ys == 0, ys == image_height - 1)) + return np.stack([ys[is_boundary], xs[is_boundary]], axis=-1) + + +def _add_zero_flow_controls_at_boundary(control_point_locations, + control_point_flows, image_height, + image_width, boundary_points_per_edge): + """Add control points for zero-flow boundary conditions. + + Augment the set of control points with extra points on the + boundary of the image that have zero flow. + + Args: + control_point_locations: input control points + control_point_flows: their flows + image_height: image height + image_width: image width + boundary_points_per_edge: number of points to add in the middle of each + edge (not including the corners). + The total number of points added is + 4 + 4*(boundary_points_per_edge). + + Returns: + merged_control_point_locations: augmented set of control point locations + merged_control_point_flows: augmented set of control point flows + """ + + batch_size = tensor_shape.dimension_value(control_point_locations.shape[0]) + + boundary_point_locations = _get_boundary_locations( + image_height, image_width, boundary_points_per_edge) + + boundary_point_flows = np.zeros([boundary_point_locations.shape[0], 2]) + + type_to_use = control_point_locations.dtype + boundary_point_locations = tf.constant(_expand_to_minibatch( + boundary_point_locations, batch_size), + dtype=type_to_use) + + boundary_point_flows = tf.constant(_expand_to_minibatch( + boundary_point_flows, batch_size), + dtype=type_to_use) + + merged_control_point_locations = tf.concat( + [control_point_locations, boundary_point_locations], 1) + + merged_control_point_flows = tf.concat( + [control_point_flows, boundary_point_flows], 1) + + return merged_control_point_locations, merged_control_point_flows + + +def sparse_image_warp(image, + source_control_point_locations, + dest_control_point_locations, + interpolation_order=2, + regularization_weight=0.0, + num_boundary_points=0, + name='sparse_image_warp'): + """Image warping using correspondences between sparse control points. + + Apply a non-linear warp to the image, where the warp is specified by + the source and destination locations of a (potentially small) number of + control points. First, we use a polyharmonic spline + (`tf.contrib.image.interpolate_spline`) to interpolate the displacements + between the corresponding control points to a dense flow field. + Then, we warp the image using this dense flow field + (`tf.contrib.image.dense_image_warp`). + + Let t index our control points. For regularization_weight=0, we have: + warped_image[b, dest_control_point_locations[b, t, 0], + dest_control_point_locations[b, t, 1], :] = + image[b, source_control_point_locations[b, t, 0], + source_control_point_locations[b, t, 1], :]. + + For regularization_weight > 0, this condition is met approximately, since + regularized interpolation trades off smoothness of the interpolant vs. + reconstruction of the interpolant at the control points. + See `tf.contrib.image.interpolate_spline` for further documentation of the + interpolation_order and regularization_weight arguments. + + + Args: + image: `[batch, height, width, channels]` float `Tensor` + source_control_point_locations: `[batch, num_control_points, 2]` float + `Tensor` + dest_control_point_locations: `[batch, num_control_points, 2]` float + `Tensor` + interpolation_order: polynomial order used by the spline interpolation + regularization_weight: weight on smoothness regularizer in interpolation + num_boundary_points: How many zero-flow boundary points to include at + each image edge.Usage: + num_boundary_points=0: don't add zero-flow points + num_boundary_points=1: 4 corners of the image + num_boundary_points=2: 4 corners and one in the middle of each edge + (8 points total) + num_boundary_points=n: 4 corners and n-1 along each edge + name: A name for the operation (optional). + + Note that image and offsets can be of type tf.half, tf.float32, or + tf.float64, and do not necessarily have to be the same type. + + Returns: + warped_image: `[batch, height, width, channels]` float `Tensor` with same + type as input image. + flow_field: `[batch, height, width, 2]` float `Tensor` containing the dense + flow field produced by the interpolation. + """ + + image = tf.convert_to_tensor(image) + source_control_point_locations = tf.convert_to_tensor( + source_control_point_locations) + dest_control_point_locations = tf.convert_to_tensor( + dest_control_point_locations) + + control_point_flows = (dest_control_point_locations - + source_control_point_locations) + + clamp_boundaries = num_boundary_points > 0 + boundary_points_per_edge = num_boundary_points - 1 + + with tf.name_scope(name or "sparse_image_warp"): + + batch_size, image_height, image_width, _ = image.get_shape().as_list() + + # This generates the dense locations where the interpolant + # will be evaluated. + grid_locations = _get_grid_locations(image_height, image_width) + + flattened_grid_locations = np.reshape(grid_locations, + [image_height * image_width, 2]) + + flattened_grid_locations = tf.constant( + _expand_to_minibatch(flattened_grid_locations, batch_size), + image.dtype) + + if clamp_boundaries: + (dest_control_point_locations, + control_point_flows) = _add_zero_flow_controls_at_boundary( + dest_control_point_locations, control_point_flows, + image_height, image_width, boundary_points_per_edge) + + flattened_flows = interpolate_spline.interpolate_spline( + dest_control_point_locations, control_point_flows, + flattened_grid_locations, interpolation_order, + regularization_weight) + + dense_flows = tf.reshape( + flattened_flows, [batch_size, image_height, image_width, 2]) + + warped_image = dense_image_warp.dense_image_warp(image, dense_flows) + + return warped_image, dense_flows diff --git a/tensorflow_addons/image/sparse_image_warp_test.py b/tensorflow_addons/image/sparse_image_warp_test.py new file mode 100644 index 0000000000..5cf54655eb --- /dev/null +++ b/tensorflow_addons/image/sparse_image_warp_test.py @@ -0,0 +1,253 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for sparse_image_warp.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf +import tensorflow.compat.v1 as tf1 # TODO: port TF1 test files? +from . import sparse_image_warp # Need to directly import full module, not just public API +from tensorflow.python.training import momentum + + +class SparseImageWarpTest(tf.test.TestCase): + def setUp(self): + np.random.seed(0) + + def testGetBoundaryLocations(self): + image_height = 11 + image_width = 11 + num_points_per_edge = 4 + locs = sparse_image_warp._get_boundary_locations( + image_height, image_width, num_points_per_edge) + num_points = locs.shape[0] + self.assertEqual(num_points, 4 + 4 * num_points_per_edge) + locs = [(locs[i, 0], locs[i, 1]) for i in range(num_points)] + for i in (0, image_height - 1): + for j in (0, image_width - 1): + self.assertIn((i, j), locs, + '{},{} not in the locations'.format(i, j)) + + for i in (2, 4, 6, 8): + for j in (0, image_width - 1): + self.assertIn((i, j), locs, + '{},{} not in the locations'.format(i, j)) + + for i in (0, image_height - 1): + for j in (2, 4, 6, 8): + self.assertIn((i, j), locs, + '{},{} not in the locations'.format(i, j)) + + def testGetGridLocations(self): + image_height = 5 + image_width = 3 + grid = sparse_image_warp._get_grid_locations(image_height, image_width) + for i in range(image_height): + for j in range(image_width): + self.assertEqual(grid[i, j, 0], i) + self.assertEqual(grid[i, j, 1], j) + + def testZeroShift(self): + """Run assertZeroShift for various hyperparameters.""" + for order in (1, 2): + for regularization in (0, 0.01): + for num_boundary_points in (0, 1): + self.assertZeroShift(order, regularization, + num_boundary_points) + + def assertZeroShift(self, order, regularization, num_boundary_points): + """Check that warping with zero displacements doesn't change the image.""" + batch_size = 1 + image_height = 4 + image_width = 4 + channels = 3 + + image = np.random.uniform( + size=[batch_size, image_height, image_width, channels]) + + input_image_op = tf.constant(np.float32(image)) + + control_point_locations = [[1., 1.], [2., 2.], [2., 1.]] + control_point_locations = tf.constant( + np.float32(np.expand_dims(control_point_locations, 0))) + + control_point_displacements = np.zeros( + control_point_locations.shape.as_list()) + control_point_displacements = tf.constant( + np.float32(control_point_displacements)) + + (warped_image_op, flow_field) = sparse_image_warp.sparse_image_warp( + input_image_op, + control_point_locations, + control_point_locations + control_point_displacements, + interpolation_order=order, + regularization_weight=regularization, + num_boundary_points=num_boundary_points) + + with self.cached_session() as sess: + warped_image, input_image, _ = sess.run( + [warped_image_op, input_image_op, flow_field]) + + self.assertAllClose(warped_image, input_image) + + def testMoveSinglePixel(self): + """Run assertMoveSinglePixel for various hyperparameters and data types.""" + for order in (1, 2): + for num_boundary_points in (1, 2): + for type_to_use in (tf.dtypes.float32, tf.dtypes.float64): + self.assertMoveSinglePixel(order, num_boundary_points, + type_to_use) + + def assertMoveSinglePixel(self, order, num_boundary_points, type_to_use): + """Move a single block in a small grid using warping.""" + batch_size = 1 + image_height = 7 + image_width = 7 + channels = 3 + + image = np.zeros([batch_size, image_height, image_width, channels]) + image[:, 3, 3, :] = 1.0 + input_image_op = tf.constant(image, dtype=type_to_use) + + # Place a control point at the one white pixel. + control_point_locations = [[3., 3.]] + control_point_locations = tf.constant(np.float32( + np.expand_dims(control_point_locations, 0)), + dtype=type_to_use) + # Shift it one pixel to the right. + control_point_displacements = [[0., 1.0]] + control_point_displacements = tf.constant(np.float32( + np.expand_dims(control_point_displacements, 0)), + dtype=type_to_use) + + (warped_image_op, flow_field) = sparse_image_warp.sparse_image_warp( + input_image_op, + control_point_locations, + control_point_locations + control_point_displacements, + interpolation_order=order, + num_boundary_points=num_boundary_points) + + with self.cached_session() as sess: + warped_image, input_image, flow = sess.run( + [warped_image_op, input_image_op, flow_field]) + # Check that it moved the pixel correctly. + self.assertAllClose(warped_image[0, 4, 5, :], + input_image[0, 4, 4, :], + atol=1e-5, + rtol=1e-5) + + # Test that there is no flow at the corners. + for i in (0, image_height - 1): + for j in (0, image_width - 1): + self.assertAllClose(flow[0, i, j, :], + np.zeros([2]), + atol=1e-5, + rtol=1e-5) + + def load_image(self, image_file, sess): + image_op = tf.image.decode_png(tf.io.read_file(image_file), + dtype=tf.dtypes.uint8, + channels=4)[:, :, 0:3] + return sess.run(image_op) + + def testSmileyFace(self): + """Check warping accuracy by comparing to hardcoded warped images.""" + + test_data_dir = tf1.test.test_src_dir_path('contrib/image/python/' + 'kernel_tests/test_data/') + input_file = test_data_dir + 'Yellow_Smiley_Face.png' + with self.cached_session() as sess: + input_image = self.load_image(input_file, sess) + control_points = np.asarray([[64, 59], [180 - 64, 59], [39, 111], + [180 - 39, 111], [90, 143], [58, 134], + [180 - 58, 134]]) # pyformat: disable + control_point_displacements = np.asarray([[-10.5, 10.5], [10.5, 10.5], + [0, 0], [0, 0], [0, -10], + [-20, 10.25], [10, 10.75]]) + control_points_op = tf.constant( + np.expand_dims(np.float32(control_points[:, [1, 0]]), 0)) + control_point_displacements_op = tf.constant( + np.expand_dims(np.float32(control_point_displacements[:, [1, 0]]), + 0)) + float_image = np.expand_dims(np.float32(input_image) / 255, 0) + input_image_op = tf.constant(float_image) + + for interpolation_order in (1, 2, 3): + for num_boundary_points in (0, 1, 4): + warp_op, _ = sparse_image_warp.sparse_image_warp( + input_image_op, + control_points_op, + control_points_op + control_point_displacements_op, + interpolation_order=interpolation_order, + num_boundary_points=num_boundary_points) + with self.cached_session() as sess: + warped_image = sess.run(warp_op) + out_image = np.uint8(warped_image[0, :, :, :] * 255) + target_file = ( + test_data_dir + 'Yellow_Smiley_Face_Warp-interp' + + '-{}-clamp-{}.png'.format(interpolation_order, + num_boundary_points)) + + target_image = self.load_image(target_file, sess) + + # Check that the target_image and out_image difference is no + # bigger than 2 (on a scale of 0-255). Due to differences in + # floating point computation on different devices, the float + # output in warped_image may get rounded to a different int + # than that in the saved png file loaded into target_image. + self.assertAllClose(target_image, + out_image, + atol=2, + rtol=1e-3) + + def testThatBackpropRuns(self): + """Run optimization to ensure that gradients can be computed.""" + + batch_size = 1 + image_height = 9 + image_width = 12 + image = tf.Variable( + np.float32( + np.random.uniform( + size=[batch_size, image_height, image_width, 3]))) + control_point_locations = [[3., 3.]] + control_point_locations = tf.constant( + np.float32(np.expand_dims(control_point_locations, 0))) + control_point_displacements = [[0.25, -0.5]] + control_point_displacements = tf.constant( + np.float32(np.expand_dims(control_point_displacements, 0))) + warped_image, _ = sparse_image_warp.sparse_image_warp( + image, + control_point_locations, + control_point_locations + control_point_displacements, + num_boundary_points=3) + + loss = tf.reduce_mean(tf.abs(warped_image - image)) + optimizer = momentum.MomentumOptimizer(0.001, 0.9) + grad = tf.gradients(loss, [image]) + grad, _ = tf.clip_by_global_norm(grad, 1.0) + opt_func = optimizer.apply_gradients(zip(grad, [image])) + init_op = tf1.variables.global_variables_initializer() # TODO: fix TF1 ref. + + with self.cached_session() as sess: + sess.run(init_op) + for _ in range(5): + sess.run([loss, opt_func]) + + +if __name__ == "__main__": + tf.test.main() From d4499fda65e4177e8db2ea74728ec277aed998ab Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Sat, 18 May 2019 16:45:18 -0700 Subject: [PATCH 07/16] Fix tests for sparse_image_warp --- tensorflow_addons/image/BUILD | 6 + tensorflow_addons/image/__init__.py | 3 + tensorflow_addons/image/interpolate_spline.py | 83 ++++---- .../image/interpolate_spline_test.py | 201 ++++++++++-------- tensorflow_addons/image/sparse_image_warp.py | 123 +++++------ .../image/sparse_image_warp_test.py | 84 ++++---- .../image/test_data/Yellow_Smiley_Face.png | Bin 0 -> 14060 bytes ...llow_Smiley_Face_Warp-interp-1-clamp-0.png | Bin 0 -> 18537 bytes ...llow_Smiley_Face_Warp-interp-1-clamp-1.png | Bin 0 -> 19086 bytes ...llow_Smiley_Face_Warp-interp-1-clamp-4.png | Bin 0 -> 18884 bytes ...llow_Smiley_Face_Warp-interp-2-clamp-0.png | Bin 0 -> 18109 bytes ...llow_Smiley_Face_Warp-interp-2-clamp-1.png | Bin 0 -> 19251 bytes ...llow_Smiley_Face_Warp-interp-2-clamp-4.png | Bin 0 -> 19132 bytes ...llow_Smiley_Face_Warp-interp-3-clamp-0.png | Bin 0 -> 17500 bytes ...llow_Smiley_Face_Warp-interp-3-clamp-1.png | Bin 0 -> 18058 bytes ...llow_Smiley_Face_Warp-interp-3-clamp-4.png | Bin 0 -> 19313 bytes 16 files changed, 266 insertions(+), 234 deletions(-) create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-1-clamp-0.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-1-clamp-1.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-1-clamp-4.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-0.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-1.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-4.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-3-clamp-0.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-3-clamp-1.png create mode 100644 tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-3-clamp-4.png diff --git a/tensorflow_addons/image/BUILD b/tensorflow_addons/image/BUILD index 8b8075438b..3824bc00f8 100644 --- a/tensorflow_addons/image/BUILD +++ b/tensorflow_addons/image/BUILD @@ -15,6 +15,7 @@ py_library( "interpolate_spline.py", ]), data = [ + ":sparse_image_warp_test_data", "//tensorflow_addons/custom_ops/image:_distort_image_ops.so", "//tensorflow_addons/custom_ops/image:_image_ops.so", "//tensorflow_addons/utils", @@ -22,6 +23,11 @@ py_library( srcs_version = "PY2AND3", ) +filegroup( + name = "sparse_image_warp_test_data", + srcs = glob(["test_data/*.png"]), +) + py_test( name = "dense_image_warp_test", size = "small", diff --git a/tensorflow_addons/image/__init__.py b/tensorflow_addons/image/__init__.py index dc019fd559..2e9413e8ab 100644 --- a/tensorflow_addons/image/__init__.py +++ b/tensorflow_addons/image/__init__.py @@ -27,4 +27,7 @@ from tensorflow_addons.image.transform_ops import rotate from tensorflow_addons.image.transform_ops import transform from tensorflow_addons.image.sparse_image_warp import sparse_image_warp +# partially expose private functions needed by test suite. +from tensorflow_addons.image.sparse_image_warp import _get_grid_locations +from tensorflow_addons.image.sparse_image_warp import _get_boundary_locations from tensorflow_addons.image.interpolate_spline import interpolate_spline diff --git a/tensorflow_addons/image/interpolate_spline.py b/tensorflow_addons/image/interpolate_spline.py index 64e05e2e81..81616aa6d6 100644 --- a/tensorflow_addons/image/interpolate_spline.py +++ b/tensorflow_addons/image/interpolate_spline.py @@ -18,6 +18,7 @@ from __future__ import print_function import tensorflow as tf +from tensorflow.python.framework import tensor_shape # TODO: better import? EPSILON = 0.0000000001 @@ -43,8 +44,10 @@ def _cross_squared_distance_matrix(x, y): x_y_transpose = tf.matmul(x, y, adjoint_b=True) - # squared_dists[b,i,j] = ||x_bi - y_bj||^2 = x_bi'x_bi- 2x_bi'x_bj + x_bj'x_bj - squared_dists = x_norm_squared_tile - 2 * x_y_transpose + y_norm_squared_tile + # squared_dists[b,i,j] = ||x_bi - y_bj||^2 = + # x_bi'x_bi- 2x_bi'x_bj + x_bj'x_bj + squared_dists = ( + x_norm_squared_tile - 2 * x_y_transpose + y_norm_squared_tile) return squared_dists @@ -52,7 +55,8 @@ def _cross_squared_distance_matrix(x, y): def _pairwise_squared_distance_matrix(x): """Pairwise squared distance among a (batch) matrix's rows (2nd dim). - This saves a bit of computation vs. using _cross_squared_distance_matrix(x,x) + This saves a bit of computation vs. using + _cross_squared_distance_matrix(x,x) Args: x: `[batch_size, n, d]` float `Tensor` @@ -60,13 +64,14 @@ def _pairwise_squared_distance_matrix(x): Returns: squared_dists: `[batch_size, n, n]` float `Tensor`, where squared_dists[b,i,j] = ||x[b,i,:] - x[b,j,:]||^2 - """ + """ x_x_transpose = tf.matmul(x, x, adjoint_b=True) - x_norm_squared = tf.matrix_diag_part(x_x_transpose) + x_norm_squared = tf.linalg.diag_part(x_x_transpose) x_norm_squared_tile = tf.expand_dims(x_norm_squared, 2) - # squared_dists[b,i,j] = ||x_bi - x_bj||^2 = x_bi'x_bi- 2x_bi'x_bj + x_bj'x_bj + # squared_dists[b,i,j] = ||x_bi - x_bj||^2 = + # = x_bi'x_bi- 2x_bi'x_bj + x_bj'x_bj squared_dists = x_norm_squared_tile - 2 * x_x_transpose + tf.transpose( x_norm_squared_tile, [0, 2, 1]) @@ -77,8 +82,9 @@ def _solve_interpolation(train_points, train_values, order, regularization_weight): """Solve for interpolation coefficients. - Computes the coefficients of the polyharmonic interpolant for the 'training' - data defined by (train_points, train_values) using the kernel phi. + Computes the coefficients of the polyharmonic interpolant for the + 'training' data defined by (train_points, train_values) using the kernel + phi. Args: train_points: `[b, n, d]` interpolation centers @@ -97,12 +103,12 @@ def _solve_interpolation(train_points, train_values, order, b, n, _ = tf.unstack(tf.shape(train_points), num=3) d = train_points.shape[-1] - if tf.dimension_value(d) is None: + if tensor_shape.dimension_value(d) is None: raise ValueError('The dimensionality of the input points (d) must be ' 'statically-inferrable.') k = train_values.shape[-1] - if tf.dimension_value(k) is None: + if tensor_shape.dimension_value(k) is None: raise ValueError('The dimensionality of the output values (k) must be ' 'statically-inferrable.') @@ -120,11 +126,11 @@ def _solve_interpolation(train_points, train_values, order, matrix_a = _phi(_pairwise_squared_distance_matrix(c), order) # [b, n, n] if regularization_weight > 0: - batch_identity_matrix = tf.expand_dims( - tf.eye(n, dtype=c.dtype), 0) + batch_identity_matrix = tf.expand_dims(tf.eye(n, dtype=c.dtype), 0) matrix_a += regularization_weight * batch_identity_matrix - # Append ones to the feature values for the bias term in the linear model. + # Append ones to the feature values for the bias term + # in the linear model. ones = tf.ones_like(c[..., :1], dtype=c.dtype) matrix_b = tf.concat([c, ones], 2) # [b, n, d + 1] @@ -133,8 +139,7 @@ def _solve_interpolation(train_points, train_values, order, [matrix_a, tf.transpose(matrix_b, [0, 2, 1])], 1) num_b_cols = matrix_b.get_shape()[2] # d + 1 - lhs_zeros = tf.zeros([b, num_b_cols, num_b_cols], - train_points.dtype) + lhs_zeros = tf.zeros([b, num_b_cols, num_b_cols], train_points.dtype) right_block = tf.concat([matrix_b, lhs_zeros], 1) # [b, n + d + 1, d + 1] lhs = tf.concat([left_block, right_block], @@ -145,7 +150,7 @@ def _solve_interpolation(train_points, train_values, order, # Then, solve the linear system and unpack the results. with tf.name_scope('solve_linear_system'): - w_v = tf.matrix_solve(lhs, rhs) + w_v = tf.linalg.solve(lhs, rhs) w = w_v[:, :n, :] v = w_v[:, n:, :] @@ -188,7 +193,8 @@ def _apply_interpolation(query_points, train_points, w, v, order): def _phi(r, order): - """Coordinate-wise nonlinearity used to define the order of the interpolation. + """Coordinate-wise nonlinearity used to define the order of the + interpolation. See https://en.wikipedia.org/wiki/Polyharmonic_spline for the definition. @@ -208,13 +214,12 @@ def _phi(r, order): r = tf.sqrt(r) return r elif order == 2: - return 0.5 * r * tf.log(tf.maximum(r, EPSILON)) + return 0.5 * r * tf.math.log(tf.maximum(r, EPSILON)) elif order == 4: - return 0.5 * tf.square(r) * tf.log( - tf.maximum(r, EPSILON)) + return 0.5 * tf.square(r) * tf.math.log(tf.maximum(r, EPSILON)) elif order % 2 == 0: r = tf.maximum(r, EPSILON) - return 0.5 * tf.pow(r, 0.5 * order) * tf.log(r) + return 0.5 * tf.pow(r, 0.5 * order) * tf.math.log(r) else: r = tf.maximum(r, EPSILON) return tf.pow(r, 0.5 * order) @@ -231,28 +236,30 @@ def interpolate_spline(train_points, The interpolant has the form $$f(x) = \sum_{i = 1}^n w_i \phi(||x - c_i||) + v^T x + b.$$ - This is a sum of two terms: (1) a weighted sum of radial basis function (RBF) - terms, with the centers \\(c_1, ... c_n\\), and (2) a linear term with a bias. - The \\(c_i\\) vectors are 'training' points. In the code, b is absorbed into v + This is a sum of two terms: (1) a weighted sum of radial basis function + (RBF) terms, with the centers \\(c_1, ... c_n\\), and (2) a linear term + with a bias. The \\(c_i\\) vectors are 'training' points. + In the code, b is absorbed into v by appending 1 as a final dimension to x. The coefficients w and v are - estimated such that the interpolant exactly fits the value of the function at - the \\(c_i\\) points, the vector w is orthogonal to each \\(c_i\\), and the - vector w sums to 0. With these constraints, the coefficients can be obtained - by solving a linear system. + estimated such that the interpolant exactly fits the value of the function + at the \\(c_i\\) points, the vector w is orthogonal to each \\(c_i\\), + and the vector w sums to 0. With these constraints, the coefficients + can be obtained by solving a linear system. \\(\phi\\) is an RBF, parametrized by an interpolation order. Using order=2 produces the well-known thin-plate spline. We also provide the option to perform regularized interpolation. Here, the - interpolant is selected to trade off between the squared loss on the training - data and a certain measure of its curvature + interpolant is selected to trade off between the squared loss on the + training data and a certain measure of its curvature ([details](https://en.wikipedia.org/wiki/Polyharmonic_spline)). Using a regularization weight greater than zero has the effect that the - interpolant will no longer exactly fit the training data. However, it may be - less vulnerable to overfitting, particularly for high-order interpolation. + interpolant will no longer exactly fit the training data. However, it may + be less vulnerable to overfitting, particularly for high-order + interpolation. - Note the interpolation procedure is differentiable with respect to all inputs - besides the order parameter. + Note the interpolation procedure is differentiable with respect to all + inputs besides the order parameter. We support dynamically-shaped inputs, where batch_size, n, and m are None at graph construction time. However, d and k must be known. @@ -260,13 +267,13 @@ def interpolate_spline(train_points, Args: train_points: `[batch_size, n, d]` float `Tensor` of n d-dimensional locations. These do not need to be regularly-spaced. - train_values: `[batch_size, n, k]` float `Tensor` of n c-dimensional values - evaluated at train_points. + train_values: `[batch_size, n, k]` float `Tensor` of n c-dimensional + values evaluated at train_points. query_points: `[batch_size, m, d]` `Tensor` of m d-dimensional locations where we will output the interpolant's values. order: order of the interpolation. Common values are 1 for - \\(\phi(r) = r\\), 2 for \\(\phi(r) = r^2 * log(r)\\) (thin-plate spline), - or 3 for \\(\phi(r) = r^3\\). + \\(\phi(r) = r\\), 2 for \\(\phi(r) = r^2 * log(r)\\) + (thin-plate spline), or 3 for \\(\phi(r) = r^3\\). regularization_weight: weight placed on the regularization term. This will depend substantially on the problem, and it should always be tuned. For many problems, it is reasonable to use no regularization. diff --git a/tensorflow_addons/image/interpolate_spline_test.py b/tensorflow_addons/image/interpolate_spline_test.py index 881eba240f..a337c5615f 100644 --- a/tensorflow_addons/image/interpolate_spline_test.py +++ b/tensorflow_addons/image/interpolate_spline_test.py @@ -82,30 +82,38 @@ class _QuadraticPlusSinProblem1D(_InterpolationProblem): [6.77688289946, -8.02163669853, -5.79491157027, 1.4063285693], (2.0, 0.0): [8.67110264937, -8.41281390883, -5.80190044693, 1.50155606059], - (2.0, 0.01): - [6.70797816797, -7.49709587663, -5.28965776238, 1.52284731741], - (3.0, 0.0): - [9.37691802935, -8.50390141515, -5.80786417426, 1.63467762122], - (3.0, 0.01): - [4.47106304758, -5.71266128361, -3.92529303296, 1.86755293857], - (4.0, 0.0): - [9.58172461111, -8.51432104771, -5.80967675388, 1.63361164256], - (4.0, 0.01): - [-3.87902711352, -0.0253462273846, 1.79857618022, -0.769339675725] + (2.0, 0.01): [ + 6.70797816797, -7.49709587663, -5.28965776238, 1.52284731741 + ], + (3.0, 0.0): [ + 9.37691802935, -8.50390141515, -5.80786417426, 1.63467762122 + ], + (3.0, 0.01): [ + 4.47106304758, -5.71266128361, -3.92529303296, 1.86755293857 + ], + (4.0, 0.0): [ + 9.58172461111, -8.51432104771, -5.80967675388, 1.63361164256 + ], + (4.0, 0.01): [ + -3.87902711352, -0.0253462273846, 1.79857618022, -0.769339675725 + ] } def np_function(self, x): - """Takes np array, evaluates the test function, and returns np array.""" - return np.sum(np.power((x - 0.5), 3) - 0.25 * x + 10 * np.sin(x * 10), - axis=2, - keepdims=True) + """Takes np array, evaluates the test function, and returns np + array.""" + return np.sum( + np.power((x - 0.5), 3) - 0.25 * x + 10 * np.sin(x * 10), + axis=2, + keepdims=True) def tf_function(self, x): - """Takes tf tensor, evaluates the test function, and returns tf tensor.""" - return tf.reduce_mean(tf.pow( - (x - 0.5), 3) - 0.25 * x + 10 * tf.sin(x * 10), - 2, - keepdims=True) + """Takes tf tensor, evaluates the test function, and returns tf + tensor.""" + return tf.reduce_mean( + tf.pow((x - 0.5), 3) - 0.25 * x + 10 * tf.sin(x * 10), + 2, + keepdims=True) class _QuadraticPlusSinProblemND(_InterpolationProblem): @@ -119,30 +127,38 @@ class _QuadraticPlusSinProblemND(_InterpolationProblem): [1.03123780748, 1.2952930985, 1.10366822954, 1.65265118569], (2.0, 0.0): [0.627787735064, 1.43802857251, 1.00194632358, 1.91667538215], - (2.0, 0.01): - [0.730159985046, 1.41702471595, 1.0065827217, 1.85758519312], - (3.0, 0.0): - [0.350460417862, 1.67223539464, 1.00475331246, 2.31580322491], - (3.0, 0.01): - [0.624557250556, 1.63138876667, 0.976588193162, 2.12511237866], - (4.0, 0.0): - [0.898129669986, 1.24434133638, -0.938056116931, 1.59910338833], - (4.0, 0.01): - [0.0930360338179, -3.38791305538, -1.00969032567, 0.745535080382], + (2.0, 0.01): [ + 0.730159985046, 1.41702471595, 1.0065827217, 1.85758519312 + ], + (3.0, 0.0): [ + 0.350460417862, 1.67223539464, 1.00475331246, 2.31580322491 + ], + (3.0, 0.01): [ + 0.624557250556, 1.63138876667, 0.976588193162, 2.12511237866 + ], + (4.0, 0.0): [ + 0.898129669986, 1.24434133638, -0.938056116931, 1.59910338833 + ], + (4.0, 0.01): [ + 0.0930360338179, -3.38791305538, -1.00969032567, 0.745535080382 + ], } def np_function(self, x): - """Takes np array, evaluates the test function, and returns np array.""" - return np.sum(np.square(x - 0.5) + 0.25 * x + 1 * np.sin(x * 15), - axis=2, - keepdims=True) + """Takes np array, evaluates the test function, and returns np + array.""" + return np.sum( + np.square(x - 0.5) + 0.25 * x + 1 * np.sin(x * 15), + axis=2, + keepdims=True) def tf_function(self, x): - """Takes tf tensor, evaluates the test function, and returns tf tensor.""" - return tf.reduce_sum(tf.square(x - 0.5) + 0.25 * x + - 1 * tf.sin(x * 15), - 2, - keepdims=True) + """Takes tf tensor, evaluates the test function, and returns tf + tensor.""" + return tf.reduce_sum( + tf.square(x - 0.5) + 0.25 * x + 1 * tf.sin(x * 15), + 2, + keepdims=True) class InterpolateSplineTest(tf.test.TestCase): @@ -150,18 +166,18 @@ def test_1d_linear_interpolation(self): """For 1d linear interpolation, we can compare directly to scipy.""" tp = _QuadraticPlusSinProblem1D() - (query_points, _, train_points, - train_values) = tp.get_problem(extrapolate=False, dtype='float64') + (query_points, _, train_points, train_values) = tp.get_problem( + extrapolate=False, dtype='float64') interpolation_order = 1 with tf.name_scope('interpolator'): - interpolator = interpolate_spline.interpolate_spline( + interpolator = interpolate_spline( train_points, train_values, query_points, interpolation_order) with self.cached_session() as sess: fetches = [ query_points, train_points, train_values, interpolator ] - query_points_, train_points_, train_values_, interp_ = sess.run( + query_points_, train_points_, train_values_, interp_ = sess.run( # pylint: disable=C0301 fetches) # Just look at the first element of the minibatch. @@ -172,9 +188,8 @@ def test_1d_linear_interpolation(self): train_values_ = train_values_[0, :, 0] # Compute scipy interpolation. - scipy_interp_function = sc_interpolate.interp1d(train_points_, - train_values_, - kind='linear') + scipy_interp_function = sc_interpolate.interp1d( + train_points_, train_values_, kind='linear') scipy_interpolation = scipy_interp_function(query_points_) scipy_interpolation_on_train = scipy_interp_function( @@ -184,14 +199,13 @@ def test_1d_linear_interpolation(self): # bit due to the fact that we add the EPSILON to prevent sqrt(0), etc. tol = 1e-3 - self.assertAllClose(train_values_, - scipy_interpolation_on_train, - atol=tol, - rtol=tol) - self.assertAllClose(interp_, - scipy_interpolation, - atol=tol, - rtol=tol) + self.assertAllClose( + train_values_, + scipy_interpolation_on_train, + atol=tol, + rtol=tol) + self.assertAllClose( + interp_, scipy_interpolation, atol=tol, rtol=tol) def test_1d_interpolation(self): """Regression test for interpolation with 1-D points.""" @@ -202,9 +216,9 @@ def test_1d_interpolation(self): for order in (1, 2, 3): for reg_weight in (0, 0.01): - interpolator = interpolate_spline.interpolate_spline( - train_points, train_values, query_points, order, - reg_weight) + interpolator = interpolate_spline(train_points, train_values, + query_points, order, + reg_weight) target_interpolation = tp.HARDCODED_QUERY_VALUES[(order, reg_weight)] @@ -223,9 +237,9 @@ def test_nd_linear_interpolation(self): for order in (1, 2, 3): for reg_weight in (0, 0.01): - interpolator = interpolate_spline.interpolate_spline( - train_points, train_values, query_points, order, - reg_weight) + interpolator = interpolate_spline(train_points, train_values, + query_points, order, + reg_weight) target_interpolation = tp.HARDCODED_QUERY_VALUES[(order, reg_weight)] @@ -236,8 +250,9 @@ def test_nd_linear_interpolation(self): target_interpolation) def test_nd_linear_interpolation_unspecified_shape(self): - """Ensure that interpolation supports dynamic batch_size and num_points.""" - + """Ensure that interpolation supports dynamic batch_size and + num_points.""" + self.skipTest("TODO: port to tf2.0 / eager") tp = _QuadraticPlusSinProblemND() (query_points, _, train_points, train_values) = tp.get_problem(dtype='float64') @@ -248,17 +263,16 @@ def test_nd_linear_interpolation_unspecified_shape(self): value_dim = train_values.shape[-1] train_points_ph = tf1.placeholder( dtype=train_points.dtype, shape=[None, None, feature_dim]) - train_values_ph = tf1.placeholder(dtype=train_values.dtype, - shape=[None, None, value_dim]) + train_values_ph = tf1.placeholder( + dtype=train_values.dtype, shape=[None, None, value_dim]) query_points_ph = tf1.placeholder( dtype=query_points.dtype, shape=[None, None, feature_dim]) order = 1 reg_weight = 0.01 - interpolator = interpolate_spline.interpolate_spline( - train_points_ph, train_values_ph, query_points_ph, order, - reg_weight) + interpolator = interpolate_spline(train_points_ph, train_values_ph, + query_points_ph, order, reg_weight) target_interpolation = tp.HARDCODED_QUERY_VALUES[(order, reg_weight)] target_interpolation = np.array(target_interpolation) @@ -268,17 +282,18 @@ def test_nd_linear_interpolation_unspecified_shape(self): query_points_value) = sess.run( [train_points, train_values, query_points]) - interp_val = sess.run(interpolator, - feed_dict={ - train_points_ph: train_points_value, - train_values_ph: train_values_value, - query_points_ph: query_points_value - }) + interp_val = sess.run( + interpolator, + feed_dict={ + train_points_ph: train_points_value, + train_values_ph: train_values_value, + query_points_ph: query_points_value + }) self.assertAllClose(interp_val[0, :, 0], target_interpolation) def test_fully_unspecified_shape(self): """Ensure that erreor is thrown when input/output dim unspecified.""" - + self.skipTest("TODO: port to tf2.0 / eager") tp = _QuadraticPlusSinProblemND() (query_points, _, train_points, train_values) = tp.get_problem(dtype='float64') @@ -291,8 +306,8 @@ def test_fully_unspecified_shape(self): dtype=train_points.dtype, shape=[None, None, feature_dim]) train_points_ph_invalid = tf1.placeholder( dtype=train_points.dtype, shape=[None, None, None]) - train_values_ph = tf1.placeholder(dtype=train_values.dtype, - shape=[None, None, value_dim]) + train_values_ph = tf1.placeholder( + dtype=train_values.dtype, shape=[None, None, value_dim]) train_values_ph_invalid = tf1.placeholder( dtype=train_values.dtype, shape=[None, None, None]) query_points_ph = tf1.placeholder( @@ -302,39 +317,37 @@ def test_fully_unspecified_shape(self): reg_weight = 0.01 with self.assertRaises(ValueError): - _ = interpolate_spline.interpolate_spline(train_points_ph_invalid, - train_values_ph, - query_points_ph, order, - reg_weight) + _ = interpolate_spline(train_points_ph_invalid, train_values_ph, + query_points_ph, order, reg_weight) with self.assertRaises(ValueError): - _ = interpolate_spline.interpolate_spline(train_points_ph, - train_values_ph_invalid, - query_points_ph, order, - reg_weight) + _ = interpolate_spline(train_points_ph, train_values_ph_invalid, + query_points_ph, order, reg_weight) def test_interpolation_gradient(self): - """Make sure that backprop can run. Correctness of gradients is assumed. - - Here, we create a use a small 'training' set and a more densely-sampled - set of query points, for which we know the true value in advance. The goal - is to choose x locations for the training data such that interpolating using - this training data yields the best reconstruction for the function - values at the query points. The training data locations are optimized - iteratively using gradient descent. + """Make sure that backprop can run. Correctness of gradients is + assumed. + + Here, we create a use a small 'training' set and a more densely- + sampled set of query points, for which we know the true value in + advance. The goal is to choose x locations for the training data + such that interpolating using this training data yields the best + reconstruction for the function values at the query points. The + training data locations are optimized iteratively using gradient + descent. """ + self.skipTest("TODO: port gradient to tf2.0") tp = _QuadraticPlusSinProblemND() (query_points, query_values, train_points, train_values) = tp.get_problem(optimizable=True) regularization = 0.001 for interpolation_order in (1, 2, 3, 4): - interpolator = interpolate_spline.interpolate_spline( + interpolator = interpolate_spline( train_points, train_values, query_points, interpolation_order, regularization) - loss = tf.reduce_mean( - tf.square(query_values - interpolator)) + loss = tf.reduce_mean(tf.square(query_values - interpolator)) optimizer = momentum.MomentumOptimizer(0.001, 0.9) grad = tf.gradients(loss, [train_points]) diff --git a/tensorflow_addons/image/sparse_image_warp.py b/tensorflow_addons/image/sparse_image_warp.py index db0962c65e..e5227adfd7 100644 --- a/tensorflow_addons/image/sparse_image_warp.py +++ b/tensorflow_addons/image/sparse_image_warp.py @@ -19,7 +19,7 @@ import numpy as np import tensorflow as tf -from tensorflow.python.framework import tensor_shape # TODO: better import +from tensorflow.python.framework import tensor_shape # TODO: better import? from tensorflow_addons.image import dense_image_warp from tensorflow_addons.image import interpolate_spline @@ -44,8 +44,9 @@ def _get_boundary_locations(image_height, image_width, num_points_per_edge): y_range = np.linspace(0, image_height - 1, num_points_per_edge + 2) x_range = np.linspace(0, image_width - 1, num_points_per_edge + 2) ys, xs = np.meshgrid(y_range, x_range, indexing='ij') - is_boundary = np.logical_or(np.logical_or(xs == 0, xs == image_width - 1), - np.logical_or(ys == 0, ys == image_height - 1)) + is_boundary = np.logical_or( + np.logical_or(xs == 0, xs == image_width - 1), + np.logical_or(ys == 0, ys == image_height - 1)) return np.stack([ys[is_boundary], xs[is_boundary]], axis=-1) @@ -80,13 +81,13 @@ def _add_zero_flow_controls_at_boundary(control_point_locations, boundary_point_flows = np.zeros([boundary_point_locations.shape[0], 2]) type_to_use = control_point_locations.dtype - boundary_point_locations = tf.constant(_expand_to_minibatch( - boundary_point_locations, batch_size), - dtype=type_to_use) + boundary_point_locations = tf.constant( + _expand_to_minibatch(boundary_point_locations, batch_size), + dtype=type_to_use) - boundary_point_flows = tf.constant(_expand_to_minibatch( - boundary_point_flows, batch_size), - dtype=type_to_use) + boundary_point_flows = tf.constant( + _expand_to_minibatch(boundary_point_flows, batch_size), + dtype=type_to_use) merged_control_point_locations = tf.concat( [control_point_locations, boundary_point_locations], 1) @@ -106,53 +107,53 @@ def sparse_image_warp(image, name='sparse_image_warp'): """Image warping using correspondences between sparse control points. - Apply a non-linear warp to the image, where the warp is specified by - the source and destination locations of a (potentially small) number of - control points. First, we use a polyharmonic spline - (`tf.contrib.image.interpolate_spline`) to interpolate the displacements - between the corresponding control points to a dense flow field. - Then, we warp the image using this dense flow field - (`tf.contrib.image.dense_image_warp`). - - Let t index our control points. For regularization_weight=0, we have: - warped_image[b, dest_control_point_locations[b, t, 0], - dest_control_point_locations[b, t, 1], :] = - image[b, source_control_point_locations[b, t, 0], - source_control_point_locations[b, t, 1], :]. - - For regularization_weight > 0, this condition is met approximately, since - regularized interpolation trades off smoothness of the interpolant vs. - reconstruction of the interpolant at the control points. - See `tf.contrib.image.interpolate_spline` for further documentation of the - interpolation_order and regularization_weight arguments. - - - Args: - image: `[batch, height, width, channels]` float `Tensor` - source_control_point_locations: `[batch, num_control_points, 2]` float - `Tensor` - dest_control_point_locations: `[batch, num_control_points, 2]` float - `Tensor` - interpolation_order: polynomial order used by the spline interpolation - regularization_weight: weight on smoothness regularizer in interpolation - num_boundary_points: How many zero-flow boundary points to include at - each image edge.Usage: - num_boundary_points=0: don't add zero-flow points - num_boundary_points=1: 4 corners of the image - num_boundary_points=2: 4 corners and one in the middle of each edge - (8 points total) - num_boundary_points=n: 4 corners and n-1 along each edge - name: A name for the operation (optional). - - Note that image and offsets can be of type tf.half, tf.float32, or - tf.float64, and do not necessarily have to be the same type. - - Returns: - warped_image: `[batch, height, width, channels]` float `Tensor` with same - type as input image. - flow_field: `[batch, height, width, 2]` float `Tensor` containing the dense - flow field produced by the interpolation. - """ + Apply a non-linear warp to the image, where the warp is specified by + the source and destination locations of a (potentially small) number of + control points. First, we use a polyharmonic spline + (`tf.contrib.image.interpolate_spline`) to interpolate the displacements + between the corresponding control points to a dense flow field. + Then, we warp the image using this dense flow field + (`tf.contrib.image.dense_image_warp`). + + Let t index our control points. For regularization_weight=0, we have: + warped_image[b, dest_control_point_locations[b, t, 0], + dest_control_point_locations[b, t, 1], :] = + image[b, source_control_point_locations[b, t, 0], + source_control_point_locations[b, t, 1], :]. + + For regularization_weight > 0, this condition is met approximately, since + regularized interpolation trades off smoothness of the interpolant vs. + reconstruction of the interpolant at the control points. + See `tf.contrib.image.interpolate_spline` for further documentation of the + interpolation_order and regularization_weight arguments. + + + Args: + image: `[batch, height, width, channels]` float `Tensor` + source_control_point_locations: `[batch, num_control_points, 2]` float + `Tensor` + dest_control_point_locations: `[batch, num_control_points, 2]` float + `Tensor` + interpolation_order: polynomial order used by the spline interpolation + regularization_weight: weight on smoothness regularizer in interpolation + num_boundary_points: How many zero-flow boundary points to include at + each image edge.Usage: + num_boundary_points=0: don't add zero-flow points + num_boundary_points=1: 4 corners of the image + num_boundary_points=2: 4 corners and one in the middle of each edge + (8 points total) + num_boundary_points=n: 4 corners and n-1 along each edge + name: A name for the operation (optional). + + Note that image and offsets can be of type tf.half, tf.float32, or + tf.float64, and do not necessarily have to be the same type. + + Returns: + warped_image: `[batch, height, width, channels]` float `Tensor` with same + type as input image. + flow_field: `[batch, height, width, 2]` float `Tensor` containing the + dense flow field produced by the interpolation. + """ image = tf.convert_to_tensor(image) source_control_point_locations = tf.convert_to_tensor( @@ -160,8 +161,8 @@ def sparse_image_warp(image, dest_control_point_locations = tf.convert_to_tensor( dest_control_point_locations) - control_point_flows = (dest_control_point_locations - - source_control_point_locations) + control_point_flows = ( + dest_control_point_locations - source_control_point_locations) clamp_boundaries = num_boundary_points > 0 boundary_points_per_edge = num_boundary_points - 1 @@ -192,9 +193,9 @@ def sparse_image_warp(image, flattened_grid_locations, interpolation_order, regularization_weight) - dense_flows = tf.reshape( - flattened_flows, [batch_size, image_height, image_width, 2]) + dense_flows = tf.reshape(flattened_flows, + [batch_size, image_height, image_width, 2]) - warped_image = dense_image_warp.dense_image_warp(image, dense_flows) + warped_image = dense_image_warp(image, dense_flows) return warped_image, dense_flows diff --git a/tensorflow_addons/image/sparse_image_warp_test.py b/tensorflow_addons/image/sparse_image_warp_test.py index 5cf54655eb..6e46746b7a 100644 --- a/tensorflow_addons/image/sparse_image_warp_test.py +++ b/tensorflow_addons/image/sparse_image_warp_test.py @@ -20,8 +20,11 @@ import numpy as np import tensorflow as tf import tensorflow.compat.v1 as tf1 # TODO: port TF1 test files? -from . import sparse_image_warp # Need to directly import full module, not just public API +from tensorflow_addons.image import _get_boundary_locations +from tensorflow_addons.image import sparse_image_warp +from tensorflow_addons.image import _get_grid_locations from tensorflow.python.training import momentum +from tensorflow_addons.utils.resource_loader import get_path_to_datafile class SparseImageWarpTest(tf.test.TestCase): @@ -32,8 +35,8 @@ def testGetBoundaryLocations(self): image_height = 11 image_width = 11 num_points_per_edge = 4 - locs = sparse_image_warp._get_boundary_locations( - image_height, image_width, num_points_per_edge) + locs = _get_boundary_locations(image_height, image_width, + num_points_per_edge) num_points = locs.shape[0] self.assertEqual(num_points, 4 + 4 * num_points_per_edge) locs = [(locs[i, 0], locs[i, 1]) for i in range(num_points)] @@ -55,7 +58,7 @@ def testGetBoundaryLocations(self): def testGetGridLocations(self): image_height = 5 image_width = 3 - grid = sparse_image_warp._get_grid_locations(image_height, image_width) + grid = _get_grid_locations(image_height, image_width) for i in range(image_height): for j in range(image_width): self.assertEqual(grid[i, j, 0], i) @@ -70,7 +73,8 @@ def testZeroShift(self): num_boundary_points) def assertZeroShift(self, order, regularization, num_boundary_points): - """Check that warping with zero displacements doesn't change the image.""" + """Check that warping with zero displacements doesn't change the + image.""" batch_size = 1 image_height = 4 image_width = 4 @@ -90,7 +94,7 @@ def assertZeroShift(self, order, regularization, num_boundary_points): control_point_displacements = tf.constant( np.float32(control_point_displacements)) - (warped_image_op, flow_field) = sparse_image_warp.sparse_image_warp( + (warped_image_op, flow_field) = sparse_image_warp( input_image_op, control_point_locations, control_point_locations + control_point_displacements, @@ -105,7 +109,8 @@ def assertZeroShift(self, order, regularization, num_boundary_points): self.assertAllClose(warped_image, input_image) def testMoveSinglePixel(self): - """Run assertMoveSinglePixel for various hyperparameters and data types.""" + """Run assertMoveSinglePixel for various hyperparameters and data + types.""" for order in (1, 2): for num_boundary_points in (1, 2): for type_to_use in (tf.dtypes.float32, tf.dtypes.float64): @@ -125,16 +130,16 @@ def assertMoveSinglePixel(self, order, num_boundary_points, type_to_use): # Place a control point at the one white pixel. control_point_locations = [[3., 3.]] - control_point_locations = tf.constant(np.float32( - np.expand_dims(control_point_locations, 0)), - dtype=type_to_use) + control_point_locations = tf.constant( + np.float32(np.expand_dims(control_point_locations, 0)), + dtype=type_to_use) # Shift it one pixel to the right. control_point_displacements = [[0., 1.0]] - control_point_displacements = tf.constant(np.float32( - np.expand_dims(control_point_displacements, 0)), - dtype=type_to_use) + control_point_displacements = tf.constant( + np.float32(np.expand_dims(control_point_displacements, 0)), + dtype=type_to_use) - (warped_image_op, flow_field) = sparse_image_warp.sparse_image_warp( + (warped_image_op, flow_field) = sparse_image_warp( input_image_op, control_point_locations, control_point_locations + control_point_displacements, @@ -145,31 +150,29 @@ def assertMoveSinglePixel(self, order, num_boundary_points, type_to_use): warped_image, input_image, flow = sess.run( [warped_image_op, input_image_op, flow_field]) # Check that it moved the pixel correctly. - self.assertAllClose(warped_image[0, 4, 5, :], - input_image[0, 4, 4, :], - atol=1e-5, - rtol=1e-5) + self.assertAllClose( + warped_image[0, 4, 5, :], + input_image[0, 4, 4, :], + atol=1e-5, + rtol=1e-5) # Test that there is no flow at the corners. for i in (0, image_height - 1): for j in (0, image_width - 1): - self.assertAllClose(flow[0, i, j, :], - np.zeros([2]), - atol=1e-5, - rtol=1e-5) + self.assertAllClose( + flow[0, i, j, :], np.zeros([2]), atol=1e-5, rtol=1e-5) def load_image(self, image_file, sess): - image_op = tf.image.decode_png(tf.io.read_file(image_file), - dtype=tf.dtypes.uint8, - channels=4)[:, :, 0:3] + image_op = tf.image.decode_png( + tf.io.read_file(image_file), dtype=tf.dtypes.uint8, + channels=4)[:, :, 0:3] return sess.run(image_op) def testSmileyFace(self): """Check warping accuracy by comparing to hardcoded warped images.""" - test_data_dir = tf1.test.test_src_dir_path('contrib/image/python/' - 'kernel_tests/test_data/') - input_file = test_data_dir + 'Yellow_Smiley_Face.png' + input_file = get_path_to_datafile( + "image/test_data/Yellow_Smiley_Face.png") with self.cached_session() as sess: input_image = self.load_image(input_file, sess) control_points = np.asarray([[64, 59], [180 - 64, 59], [39, 111], @@ -181,14 +184,14 @@ def testSmileyFace(self): control_points_op = tf.constant( np.expand_dims(np.float32(control_points[:, [1, 0]]), 0)) control_point_displacements_op = tf.constant( - np.expand_dims(np.float32(control_point_displacements[:, [1, 0]]), - 0)) + np.expand_dims( + np.float32(control_point_displacements[:, [1, 0]]), 0)) float_image = np.expand_dims(np.float32(input_image) / 255, 0) input_image_op = tf.constant(float_image) for interpolation_order in (1, 2, 3): for num_boundary_points in (0, 1, 4): - warp_op, _ = sparse_image_warp.sparse_image_warp( + warp_op, _ = sparse_image_warp( input_image_op, control_points_op, control_points_op + control_point_displacements_op, @@ -197,9 +200,9 @@ def testSmileyFace(self): with self.cached_session() as sess: warped_image = sess.run(warp_op) out_image = np.uint8(warped_image[0, :, :, :] * 255) - target_file = ( - test_data_dir + 'Yellow_Smiley_Face_Warp-interp' + - '-{}-clamp-{}.png'.format(interpolation_order, + target_file = get_path_to_datafile( + "image/test_data/Yellow_Smiley_Face_Warp-interp" + + "-{}-clamp-{}.png".format(interpolation_order, num_boundary_points)) target_image = self.load_image(target_file, sess) @@ -209,14 +212,12 @@ def testSmileyFace(self): # floating point computation on different devices, the float # output in warped_image may get rounded to a different int # than that in the saved png file loaded into target_image. - self.assertAllClose(target_image, - out_image, - atol=2, - rtol=1e-3) + self.assertAllClose( + target_image, out_image, atol=2, rtol=1e-3) def testThatBackpropRuns(self): """Run optimization to ensure that gradients can be computed.""" - + self.skipTest("TODO: port to tf2.0 / eager") batch_size = 1 image_height = 9 image_width = 12 @@ -230,7 +231,7 @@ def testThatBackpropRuns(self): control_point_displacements = [[0.25, -0.5]] control_point_displacements = tf.constant( np.float32(np.expand_dims(control_point_displacements, 0))) - warped_image, _ = sparse_image_warp.sparse_image_warp( + warped_image, _ = sparse_image_warp( image, control_point_locations, control_point_locations + control_point_displacements, @@ -241,7 +242,8 @@ def testThatBackpropRuns(self): grad = tf.gradients(loss, [image]) grad, _ = tf.clip_by_global_norm(grad, 1.0) opt_func = optimizer.apply_gradients(zip(grad, [image])) - init_op = tf1.variables.global_variables_initializer() # TODO: fix TF1 ref. + init_op = tf1.variables.global_variables_initializer( + ) # TODO: fix TF1 ref. with self.cached_session() as sess: sess.run(init_op) diff --git a/tensorflow_addons/image/test_data/Yellow_Smiley_Face.png b/tensorflow_addons/image/test_data/Yellow_Smiley_Face.png new file mode 100644 index 0000000000000000000000000000000000000000..7e303881e213a82e412d18de9d9d86f368726f06 GIT binary patch literal 14060 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 KMNUMnLSTZwH|2-` literal 0 HcmV?d00001 diff --git a/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-1-clamp-1.png b/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-1-clamp-1.png new file mode 100644 index 0000000000000000000000000000000000000000..86d225e5d2158804f88dca881f69ed3ab287d866 GIT binary patch literal 19086 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 literal 0 HcmV?d00001 diff --git a/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-1-clamp-4.png b/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-1-clamp-4.png new file mode 100644 index 0000000000000000000000000000000000000000..37e8ffae114625d0cc6a07ab2b8dbbb7413a3829 GIT binary patch literal 18884 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 SYriWXn>}6qT-G@yGywqK%|=@Q literal 0 HcmV?d00001 diff --git a/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-0.png b/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-0.png new file mode 100644 index 0000000000000000000000000000000000000000..e49b5816120d43a669264915f1b6747606e080e0 GIT binary patch literal 18109 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 literal 0 HcmV?d00001 diff --git a/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-1.png b/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-1.png new file mode 100644 index 0000000000000000000000000000000000000000..df3cf2004312ed0ed0ebf1f0340cbfec7fd9ac46 GIT binary patch literal 19251 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 SAB^`w@#5*~=d#Wzp$P!c@C_^g literal 0 HcmV?d00001 diff --git a/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-4.png b/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-2-clamp-4.png new file mode 100644 index 0000000000000000000000000000000000000000..e1799a87c8542d7e515b6185d7e8f6f75fe73f3e GIT binary patch literal 19132 zcmaI8cRUsD`#v~?-Eyd2pjGa}8m5z>%-NM}10sP%a`@>8R zzCV7L?MFvTc1({FqB z)1X+b-$<*!7ZEfTl=sEtX{|u^TOmlhmH)*f^?Ana@U0^fL$Svtct~Fg)joS^{f5$R zH~H3pBM}8>Bzap>IaN$25G#1i&)-@RRt%z>qJzkVCt5WYNi$-t4_7XU95pQqXi?-MBld%c|esf3PT-+kFtNR^_4)6nwiPH(%`m+iu3Cj;0w*Xj${hA8m6Mxoc1Q z*#1Y$*?=_x%~c}McTq`IB696Jl{){3Iq|yz-yOB6Z@M@f&PbU>D^Z^kAd;-Xu<_+- zy<||nbpQJO6`gD`GfnFOG(zI%F%!PCKdv$pge33L+xgw5cUtG=9a+!4DJ?zG#3Om9 zy^Fj%sPg+o{>{xdU$1VwXw3DxvL^is@;^=dPKhrqElNx*%FDBG$Z>RbHh0SPG9}u& z7MR&*yF28>-dH~(Ift)KHuI6J60=}X&M33PE>q!fJe5k_+FGFwl~X^z-d*Xb$?8WPfIf~GqZAWak94VZclKvNJ&Xb zicL(+E-lT@+Z1E#!<(AUGsG%zR}>&?9q=Qpc~mO5^Z+<4hDzC5Uu+T6qwG`mxAu4O zi-Y^0pRG)_CE?%1>~F2kwBV_*W9hOqQZy^eNinj^GP1UIGB7YUGt;+Bb9BnRo|)-v zmsLcg$fy(dh+lZzk!387p}sTr0&WRb?1^&F!&9QReodtbQr|@X-JR{A-Z)G>{bp}# z^9S{am+Icm(cwzdV+RyY{EvdEshx|9xml8zQM~PmR5Mc|ppzXB742HBqIaD|q0%O{ z^##Z-BNb8{Qe?cC?jE@3N<0(w^sDVFYqKq9KOnxm@!Q_co>?4<^%vv#hU2j^GQl%7{w>TG5VX< zuXmgjV;v(jvM9Dbf^g>!zIzsNNI+4VHM}%VGK^$GQAO`1E$yV`>QbnG+g4Y^+DbIx zXFsSef9hKO&fhlI1*cL}_9!~T;u-7*lrE;k7Zqj4$9J?EerYvqd+&V0CL^(=BrkqW zkCzv!eLgGlj@t=lQ`1}`y#pkk=jEzzf@fGo9&%Nq3)nN7jlWTbIp}%Z7p~Z%_43%* znH4W0rx@Z*j2|X47dN3wTpU7*>iJ`_h>LCO2P8ddHqbU~H#lfB&}r)FQEcf}c>Mvc7eVW4)XIdGFK3xdqQ{LOFxapT_2Nb5;EAPn% zoqurd?EOHT_~9c%ovp2OX=ehBAWwc;)RwT8E5zRKQiN05a`^siY*6)Hd8Iz>*S~8# zeOdgesr|=$h+8P?OfXxc)NiU@+wKXuY->5LXP1({K0c;wSK z`&HDIAC}EDD~H)b{^~jr_rrzwBmkKqf}{CnUjZaV0(L-y&`$2|uBa4WTCV%PTo({< zxQ7hkjD%^Qp?^vu<(xnh8?qD*Etfirymse1TFcL%U9aEe+Q6Q+Z&bXnpXNDNk&&r_ z%@+%_m|t=vUR_5#IPHhQm(8>>?U?a_90b%}b#@l(?ae2+Xz!2%=hXc3`)y9HFYz`@ zKf{{X$-9L6xHV3n@B9I(tu=I+%qNZ}xS+8%eK_H>W=p*iDHT$TzRE&AQ@TWUVFZT= zcKdhY%n#Q{Q^A9?y=Jii0rvLXmoB~E*gUGDA%OFzWU5y&+HwWHXA(;B%$rV+Xr}j! z$Bv(%UzX$iL=vn~lo5Pwh$Cc_OUQgjAgMl07RZJ?F+5c@dcPNaL8dK*7hZ7W-Bad& z!K=z_7VGanKQ}k}HYX$X@#^ol0zBUSIGitmL%4ZI{_uG1WTtdztT!MDqYDh z9~ee-W^FYUl_MW}6bHDmRVZ}4*nP_vt%;S>7-kS(ZowmCsQ#GC>A1}xqNEw(OUs>i z%ZL66>*_4l$0t93;n~yo>DQlXpt zCNgm%bgxzYO_u*hGj2 z`q!=QDDw-9hpWJok<^b}tLXWBr2fPhaXQs^9{pEZ< z5S!gFY}Iz-V#Cn%oXs)WGmQ1A2BP97);bAi1z@AZ?iM_JszuH7WoJXUbQ@(KL>blL z^F9B#X)K`?B5;!_&hyKiemZd_U&vo4J8xW-waszAc<<5N7|#im-i$qcVm|!H`_h`m z2YpS~PV~%S3d5QbeLr76$z(zLIPxLiST!e0q=+5ol-BxyeBooS9CfQAkn&5j7R>to=E%;7pXrayde0DwU2^Y^S$dZw43p(>2 z2`q9cXcaV?G@ks;KuRH(M_9`x%hEq$-avu{)Gzl9ESac}dJL0LScgCswqAmXaMkQC zRqT6=6pR$PrCqTRh?f92=MhyxD5|v-UGfWkhuD7 zFUj)`XTx)9JoegnhckpA-WOxoIJQ2Ud zlz)uPn$15pwuoccH~Y4m8$wYL;pUD6KV*pB_SgZWX|xS#A#8LsHzT8o9j-o2qn*&G zUxeUtZo>!q`zBzDSd@ITz4@e2F;fn>0wncB`jJ^$57aRom#P0tiY3GoWocfrZRTan^>+9={ z#l{)S2i=0aZT2zzZjDw^m3St=jZ3Ou?;#Ox$B~>e$1tROZ0BVjoImV=>?Am|J7g0U zL~DCiiEPCu5Q3*`^=~IzNEE_-N$u@e?^aq3HB=7Y{bP^I=lI8_dRTWatC@*=QUZd7 z?E*Q7*VL^Cq6Eu|YDiC~W_`!zw$eRKN%EAIJdA>+tH-*$!1`bDCFrb3^iaA?y|Yb} zesa~@pL@ZjyF(Q#macfO7^Fh?@C5oSm&C3rP_Dl}98W<9ON~nc3JW-2^ z2sAVGxOX?j(8kg*D1g)lpVyF!)=lJox^05CQ{&Zk7EeP|55t3WRaLS5qQ?} zaf80?n4t}_q3Ia!E1TG(Cde$I%0M{+L{8Q|vSjr<#7cTBpTQ+en@;KT^h3X76;=ho zU)IJygzr9$m404fqM-~^S&A<&Cx0%TAw!AJLXf9hWV(F*5%QKW;?&fPvPzFnOcW6m zRI5wSS0BcL!rvIn!lcO!l?^$txxItbDA#=);Zu-T&COT+_6^5E-nY0xS`!?66U; zw$YCM{))RjS%uxG!)D=b2d68A0kf8tim1K2MoN_noZ@+kHNGEKH2tfS!F(W@iulSz z5g_3W)2X$oVejb!!%VrR@41KG^(Eyi4mT&alL?wf(O64OH%4*h$LYkpJaJ4RdGcm# zNeMGmoK|6h_#`17#>^Vt-tKlavRAx*$q*wnQQy#XxN)VoyPNQ_W1I7W&MU0`kI7jG zRfPxQA%Y#4trn2?V{ngF0w3oDI^41_mYHwH|JCWNzqGuc7z!gZ&vLd`3+! zL2VtO){tU8C^WuAIKK1{l#^4%1`ECpWF#LJ8ukct{Or|8c~w*nBM?a6Eo!zyo8G*1 zBnWSI3Ws2B-mP_Vlv@(Mgu8{15+N&tI{7fUcxc>pJI@#Bv%Jgo?4C_8N|%?M8fQ_gC%_{CF!O&0-5dD>E-J_dW@Na zBFT>qq*JS$meVl5ccfd{CiJK!%h(Zkb-oxJVLwPvx>j+U$4bt8*U9=id7@}MU+$lh zZ(_FpPMq=_O}}nZRD?uV37mE$nRSi$sf_J=*TA)<8OqW#?vImL%}=PfK`gAD>R1xt z7eX~mkPXsouyj?Z_08B=1APLyBau8_7<>JC!_NcB+{9E@CmcGIXKXy4Rl;X(!VGmV zt}5+-dg*2~T^7l<3muh=;DQD{*1tcB$f_PTMn9j2)cHZnBoeYQ_t&I_wCinPA}sW- z?-Ei{3``Qqy{WPB@mXmI#KHMlnC9p1rKS9rE}4M5+Q5$(f5*@?N_fr~3pwcrFRUSi6=-O{Yibg@Mh_G)tjaC0@TAFwkKZr;DqR^z*7K}miQH!@1)&S0+P@^}MtpVv zegr7K37wgs>|gPEwcX#_D<#o-x4Qk|J$clTAqClP;<5K}O`j4WXM-eP>v|{1>HF)C zr`JYiGtNk*c7pIv-6pRt474P1QYT+-E%w}K@oJ&?_){KMwNC%&lS1h)%W@X0_VbH! z9d*0&HOt!0HDLHd0Om4W?RphWD8Of13ovf(MU#_!*ROYA5BIB8Wt>L0dMC2D9YP%UUVaoz2COTUXQq%CtkLzdQ*jpL+H8&yNpYrlv3h0}eK}yQ`t> zc)Z`~)Be@RI0Q-WlgS=NIA33-4@WPV=Si?%BdK%#SN)+F3yNzBH0o(aY(N#Q}5gNV-d6MbDUytBv&>fw|;ds1#j7uMPFW>zV$}; zxTvT=1hmL5dXApqmYAB(jH<|!RsCcQA!}>crAx$jch-V$|BFwp9~RI@^BN!L`1*J9 zq#;VUsFjyo{s)Rt5`S74Px&Vg$P$6p%Z_hV*Z6;UbL|SS0t!`6;PC6HEuZy;mqkTU z03X}g)%=}$1ZK#cQkR1;@Fh%0nq)9U-(BC|1$T*AS>B6Y;`w8~4Vtt+KdJ9(_Di(? zPQAM`e@>3{F~A_-&;?rXb>4S%4VP8W6+pu|fuf5rlx8}bDHd&*uC{d>RA1hpyGD;&yOK{XSJqY;R^KtUj3Xp^;&W;kminu86A&)+Cwe5em)I4QP`Xii(P2KBW9$gMzwuM({AWGx_*;^SMRDZH66`x2p5 zB!EtOz1ai$dw1J11OTgtj#5JT#hZyA5Lp*-kF!mf1uWf)bECE>lP@-R|E{;BXzf!5 zgo(hgnnDf-_h~<{XRmu4c<%CLn4TW6)tkhB4F!87CA7 zfc)WU(hUS%rgZZ1OrK!YFRpFC=q_F_h)k!neoWLwC^`Mllzl%PQ zM;Rs|0dp>xe`37BU$>E-?)5lH~zy7Y5p%-^btQGq8jdj#UAg&*O9AZZ0)%c5#f3-;`HzcQQf_3zvnZb1Xz4=+5mXogH!O3 zDW3bH9~uz;X$-EXc)7_aiMY=P2w!U%W@GcKu}L;BL*oSGzfJ(I8bX2roH2NeO)`cRq)F+$-@vO zAu8v$msp9fJ>;m50rbv5hL)(YJ9!SE^q150o8_Gu#xWtt!XP2lZ07$;{F z28Ker%$0->N4l@W`+r00`$--zDh}s93u$?Eb^Yg*UN{dE6iSCmdp%ltEJ3GFs_>VzDTal`& zo2I7p%ge)IVU2AcL!VhtZ-tKxi=E@=Qok1c_xGIM*oa*f`SZ*2^HY?BXA&e2Nvt(P zVNk;fTzKVoAOif~-w&^>$o6j2gr9!&K@%f1BZzVE>sLR}AiWLvmsAbKM0t5;?VZ13 zaX2h?o9RcHiorbx(@uN?)B=IMqi*n4KoWY9hw)9e$kj3St5rD=wPfnv-@Z=MF`^#3jz~xtM?EXYDu_1-2seM{uHxc-hv(o(<>~6mY$?;- z|Fgdj}H z*)k9Kv=BLW&PrZ>lAgY9{5R{E)NipSUA`yQsbj5#Ezjv04rfwZTkp`2jg2{p^x+|1 zRA(nuX1z4$3Fcx2*%ruSYP8G&E7xTDJE)J<>k%>v6=H( z`t&q??QDE1(bTh>V;Nv8yv^*-aEry;nUMcGs~$^`%HR~EIE;Vb;wFE&hGJe#52|3P zdU{e?8aeuY#F5r5QmKY#E;KxOlA0VE8j4Ed;9bsrMs3JtU=fOlqK{D&+CyN}+X=z; zaEZ+DFqNfm(}*fb4z!vY7`H7Bl!(FM(Z){FRJ254;V7X`Uwvhdx<;F(NUEusEdK53 z$u7d6=$<>r$|XMC92y!zNhO>PKhT|}h~nPfvXxc$m$*AD+m9a0THCvEvYzcFykJ_< z-o}IB$H|kEH}u*e5QxTTP$Cwx%6TQ)$5fkQO!TH?icz=!BK4xNd;2AO#U{3y3H4?S zknowI#Kgqd65(q*qW}22?^0M@jhT+=#N>#KER)8XNWCU!A9#m6>+K0skk9dY zvbVEo>y*2{IMg!!7UCgKc05#}C@ta1M_4U&pJsP(#D= zvK1@;xq=jjzrU%ov#5%SOhg4~r6V?puk-d~;XoP{VYE=_;}y8v(%%nXTk}|6o=>`) zz`&%Zr(nuT^0Kb@8q+vR2QePQFdJ=eBh=O1K#px0M{D@oxgI{L359Yi*$!^slp*B5@(Cb5Ajbgnz3Vo%!C&&PX5P@rc1 ztF)P^thwa{%<;4s%AWA2#F*|!%GI>6ttpoD4}wMBcmC zjhrE|4m&{o4>OiUPJbV)x+c6wn_Ze@|I1Xg>$acU@0BO(d| z`WyJU#l6c#dARs|^YXr`YYI*;__ro&dmg2K5dKw z0)#L*^N!72|X?#DLiLV;+ z#i%1rC(Ij;oLm67LF<}ve}DSlzrFPIiVaG9f%a)>*PmiZdCU#+Mq?S1_B$XlPA~P$ z&Kb3`1Orfwn;V{(h;d!cW(bvL^mK;bykDL0*7D!Dz)*_A!h%CZ1=bjva4MYT`T9PX z-|KC^@J`oY@_$U4qL5C{W3(*#%R|m$QO0QI!a@Y-0egF$oSmAtw+mIm*Y!qarE0Jw z$D4a?IK@B4w`w})e-3<~YcurCR98%|d?`HT;l&Ecu6IHrbIAuIUO-h;^z!L1dWcFo zcDB)QdbQJGc>-J;OivTn#BLQDYt#|hk6p+d6vP@5h&(4b6zAax_ne+JZ zap{>X#wC8V)CvX3{%wT}v{4pf8P)8Y-ZsW^_ib?bTIyPR{%l^#&qmnW^kp zwj+oi_V&3PV3C3rxscV8F^aC`oHXs*j`*2p3SHzEO3P_UxfGfYDfZa)M)(*h{efa!VT z?EkzBM2;HJV^FA^El*a&!h;BkqubPSmYc3wac>h z@!NlRyJP%)hX^;h!GB=t&~b`hOh2i+8zt3_JwM2W?3td{|F{+-1#xhMNlLao^pZ`a zjm&}Al(i6`?Cqlq#-YjiKP&QB!MQ|=q%@;8uR^z4=&BUGEv|bB+Pg68z44o8>o?Et z`r_2e6dsABK6#{~s-iNTA^N*{n>$;M^DOV87_c>;IcjHT`oI8H{QjBd=0M$JHV(an z_?0Ppo`0x*UFjoM5(;iyo;5+GI)ptwln^>HYIp|50x|hj)^5=z8daVe#Wa#xoBA6f zEn(=JcuqT^+%h3uoW=h`G}Y>Mye0pyYhV?8<6BnXxldRBG0J zG)3Y)%F3LD@vDnOH~WKbL1nV}e6yK?R)g}&c4oUjNkFC0BB`)At{dZz>eJW$+BJOw z*-Y#h<{5YvB75Or=m4$^Y~|YD-&<6CH$*2;2|k|_4_SJl3>(iQcYl~rnXYwga8d3) zGpm=;$>ho4sz>koN)_8KP@@bpWKFgZYyO@aYm0<83vOdsWA;8#-X=)eplsJ@Tuf+jJ>7T zishgq<)NnO?k{hx$d=qa5P`B5C3+VYash;U{RgUhoIYx;In3JE_PWMUe){~zVQ6__ zE$fOrF}S&uN1T3NcJoIusoQj_qdqe9b+@|-5LE{lD2M^~(*Nwj+aPBftPS~A=PZGQ zY;G|lru~LWHEYz!lINZeQ>#15CZx}sa0hBSitbQEo2P1P%MoLnHRNKhZ?k2R*&1wy z3`x6NvL_|}Peq_6dxNJkEvPh3W==XAN;^GbEF$Gkgk4}3{g~y z<~ecB-{^1n?pm0l-=4`K4L{P@Z~ZS#v3Pgrt5LE?IM!OAJFJsLkRD610HI9uF<_J) zWdT8QvkYr|I|%p|BIUaKJ9~eR967@45F+p~Aoh~`%v!9czkY^cl2>Yq%aaN%cGr05 zede(Z1Gq%t+!~)Y!+TU8r7e_-g%nyaD8$9%sbI;)MCJb~Vqk?aTfaDu9QgtIFA&iZ zRvhC_{p?LnMP!{2+*Y~^uU5xYywN`-u$xJi(Y-c3^c8MwtTooyLAb~EyoLQR6C3Jj zBO#M`ZKr(|dWj+oxCRbsAp(E}aBk)+ZM9cE^mk8CjQSWr5qfruzo?JUiz>t*?BWqx z*64}_f7rR~gnlgP)HyCI_QF&odZX&Dh$rFI6GhYu-56tAkpnqehyXvl3f9hA@O5Bn ze9g7LFU~&1(VsM8VIVwCTGB9tiUzJFC|X(_$*oZBpKSVw&^hS_Ny?_uED6>rk@DJ z&^6v$?BXOwZ4D1~9Yp*4E|f3N*Fl8DPfk*C_gn007G_?%qzf^;RR%p2RzLVEHaJEV zd&67b8d50HJe#t;EmOmNY&M6{cz!*Ac=8!S_m=1q^FL{U!vN;@W1s{-J3IV{{oId8 z1bUph<|13@$*{e>w({$9`Sa&RA|eqOEtTEI!jyPO$UH9ilB!*P=+^ zGDPwLX&)Hw*FLYjyrKDdx36D|o;FUOJvp+I6!;{pT0oeg_Z^84FofvsLzZa+ z5G(+#B7S*BCHs_%)hRd}4wmM$ucay^`3tz zTOj~Y)Udagspg@lpMK|B`1~eB@)t7na3cA1JeD35QQX`BBS8H5C4D*gSlKM(tRz&c zl7>ysKqK|?V`C5eMeGv=YzKaN23ob){?Ak&!l9RhMs6N)6ZO^@#jg63dM@=l?VX(n z!LGjk^t)fh-ns~S&aMV;7Rpm`wR>qNC$IppqZ1H-adHx;T98e_k~2Jvze!XoeCiEu zsY~_0-6qib0}@edvs--yZoFhKCoHx zR|DYQ;)vu1cArW;a?XY$#)YYv-u(ODG)cb--$@V@ls&g`@9!Oni?gz}rf;~mvwe%V zXie;$RrTwO?bRvdw@oBQj21E{2k{0U;MSNt^WE8duB^I7R%G&ZU+5x7zSKV}Z&mf% znQ3b|euaWYmCnUqZRh0fXv2@n8jLiD%@t56oKtW1OEhiW+zMlp8Dia?u+|VfdK7DY zIsTZtKAogwd>+4%9ameC8oaa=ZhU$rk$?JxO6cz|I?H#?9N>G9X5<>&36((&U;uR?Ge;i(2``XuX{W^VFSyu1^cx~!PaZ=#x zJUsf3o|Az#yDiM=B#{nG=CiV}=2pI#3q)K8D-lG`8Y>(ZkHq&Kb8os=#1*f6MUt0-c!2`Lr^?XmN~XdhWL-Tn=q!?f0paq{-`pJAe3iE^nAP{; z7s9t|>%^I_mdi{s2aKN%*N)!a`uq}q^X71jyrkq__XX3@xAdy+Lj?6ZLttxW@@RlB zfOfzZZ^04jv*2A7zupfZb^+Ltw)^<@_7s(}Z*(kxdBV2vlndnJdN)h3~dk*HaAtGNv*z(oNhyy zvt6BrdZnAsiBRo-6aZn+b~p_OBi~&9IW$JM%%BowSst}Y}0s2|J- z-jr|6nMGXZK<@4^3G@08s_DkNKj0%$alU&)-OS>ZB{E-L@4f^Zxj#SQ&wt*TTk(P^ zDG8i9mH+Mz9z>EAum#pvKy7H)Gdf`v=wlZcmOj>^fBrJ|P@j;@^cNi>n_z`XK~5`5 zX`mrSr>)Wmo++kwZW4@xyDBP3C=`j*(ScGP#0H&Me%`g3ySB)g?aQv4mBqm+%@7Kx4!vCR<C_N9_8}K4wOtMsjG|B(*v$)=hi*FY~@ih_tlaZFckV__&||mnA0s3 z{L))5Vc~g4O~kYxY&nQOnQvn{P$VxbdrmncsdJXI-ZMU3T)O z3TuKs+@$=61}jv<@sqn;1lvP2(}uCPj{>s?^GD{#LQ1)ii3zrITjC53+UC>vS)$L> z-FphA^0rLB8YaYkkWVXFuP+_5XRUuN`+IIXuO_CfcpBM|m|blU_5|5=2P zWQh|4$lt3s+Rp_ebz^+F1Wa3O6651zOAkd&5VYMh`GFEc_b_iBx&EJBp?oCEbqyq- zU;KSr%_Xu#S)o$a4Gp9|_4#a}+7Ju|@xY79w8by{2|s@68ypiP(BG~SM@x;s%{*WC zlE7DcJI#a#uJ0GJKVJLCk)n$N)W0e)e=yMxY zEJjg7qoXPP4UwBiI5>z$yntZ9g6B?%Z#%Q5Scs(0_A{l1IDo|~pH2`x^ylCDd0d=Z zu0h<+lwMAf8F<_fv0n_z`sknoV$WUIcCSP@eiegmIC&V?bK)eeyU8ch0xyKy-38Ah zbR#zeb5sH1vb&J3w2b_fwO#gM)LG1_SB-j@s94viX<9XJ^lJ zbJIWj@UPMb6ExjuEGC=*HwIeHQM&8)+lj2^kG47U1G7S2m{lA)Ua?89sTtplxkaF( zFsRIWAOfdBg?jcwg_;})+2_uUuP_=}vIa7}tGdlH&$qtF**5WnL0R@+Z3d19>BY^> zuI4I*YW606TTfCl|Nc*v-gFuc2pXwpXWog4kH@|TOQeBk_6u_NGL;NaXT?V79;>s3 z$G5Ib!9_&A6;xvB8=d0Jkf68gD}amXa=BH|9tZ(dOe}!J8L^cyGbGlYQq)lCz`Uz> zE>X@ii@TsiH+q};ZA35PzMFHNfu~6Q4b|}boSxI5On(+qxsJPe6Yk)Ek>vToV!Pe- z2Iy(n>DOMnB<_iJKT^$*n$S+r^w-^78AKH8FO`u-KsFZB*`Y4+ zwp!`vm>Q7r<{xgQ(_aK&&gvqkHW+bu! zZm#3(ISuk1c=B-iwZt;~Ki+kBhsuY8Sbouq$U!ISy9caHVMOY)^D2|ip?QAZ-dQ(% zXfvXKJ0Cr|XP8jgTVL5E4EFZ>Q1Yo?7WD5QM-4da87+j8*B3`@BU)+3KvS7(c=TR( z=#F~o^TKzL@LCqe_tv1S4A|aaQF;3tI2%Y5Z{J>gVK2zJy~TO#9wv|sqF=kXY&S|d zb})-@lIi?4QU-d*MMUil*K@$}p|IPlx^x;4UJ z!4=GyVWy@ucL&cDXm1ue+5flM9g|>lSxrSHIaO6wLG9ZQH4FxgKx#Obc zdfCM{U9h(v>6I&w@?X@*1{QBdElbT~=q?@dvnb;wGRNlnA`Q7r+SgVJW3T!n0zdJJ z=ghwu{8IY0ST<4Rq0n*b>nU_iYa@jGE=(H5qxtT-St&oAA~!^+DG3SLiXe$ z0^LC0`pOg%_&&%6O~F23jy==CMm%f_1Z1e?2YhhZ@&T2Scx-$33hn7oWEAb=>4|vt z3Zxn2{8zcxmN!uZD^Jo7K9DJVe5%yF>o;cT9Gs$eJ6FGP4=yMP{S%CY-CrtY0Az9K z;ln!>I?#-d4A!gQ&?_rkS_CjGFD;$f3MrmE#rSAv&F33wHscWd_+>PBVF3Cq=)!(b zdkG5!JYrd%XaoC#*fTv9_j;$N=}vjapT(x014BbvXLH0$qGkev87>H$z9PR6%?fpR zG6zVc39?-A7XNpRx>BGYeYmlhe0RQR`^UF^5v@9%smvOGJ}og;O~)n1d2{Pwn9TW4 z)3c~)4$SPwUhm6UYuA)W&_M>>Z%{9y zJbH8PNmz70z9Jy>;%#nN^#jhyR6D+J+&7M;3}K37Uf3L8Eqe;$IoROK!T>+TgvF5u zIdJSBcZ~Of9Uu^}erMc6Zf#xN({fTtoZzV1@8KneOd&wv7;lK3*||jg@DOQJT6MMb z(qGlKffBguLsi97y;;S*Sl@O~gjUp9Rp4x-nj`aOrVWU;Wo@HRShjRANosMZq@v6(m6}^B&K7USm#C+aq~WeA=i%BuOP$bDce83!PA*heI>S5gJ&MgX^c;HX{HxF* zrKAg-H$yqQ#TkNM-fB7W?h&f#_5LI0-w#B7sjo%PUUkb?)^oyWXKd&!s#zGitHS61 z)?^)PL|en-uP%g(XFUJROz&CnBkx_xB@^cS#IWigfhSYr?(Hzv(%{x^(2Byxg;ArS7KhmamB?OGHy6_D(A4P7-s~*)Q!UJ^uHGA-zO}wH3&;yq&?J zI-Ae%*h@YGa~y6?NBgvLg;uO$wKX^MJgY}?vSMw?B_+81Egt;Sj{LSa`*{DIv^lcg zryXPeId1cmM~UR-VIc|wjix^h3Rt8~9_{e%rcd zdc!RcJSK_)<~@N4V>)3CDuDwt`Z;!dAus9QE!|jpxaBqEwYGNVWMhpY^~Au*&;JW3 z={J=)<3hPQ@puhHqBXs+e>FmcXH$}^Hilj=e~L7{%n+~U&h^APjz#1Jq>b*zG@Iv( z(3fmm%M-7EzA($PXx$&g4ol1lp4f(EuOU92zhds*x!LOHp8H|??(7>+h^V(bsqa{D;`R`rSVYudg=6X}b*r$V+@PMv#yBERxpf_M^iSLcr74?FuptA?LP z9#LF{%R5PPW`;$`d@sC*i@YHKm_L+$hD(9pKe7bXvFJzt&BD7k*`hD5UL%;k%ex~A+J28jK| zXKn%y`g~i%Qy$z;v^;g;IU12Am2Af+#d3%DlTw%DqMF~|_jV6^>`w*g1|0Ll_^Nwr zI8JF(zl9g$VZOG^yB6H~?+Ir{O_u|u;+IK$rJ(~+f zWu!=SoM(r}quaN{N}m-~UGv!M{nG8+;cbw=&$SB@B$ZlS@`yDK%SF)RtE%Msex+gL z)RbZ9m9tCsLed`5LgkgGQKc7o!ng#vJu-+=N>b@>mYRQNglPRb?{dE?c|Qj2^EM|l zqW^FI+VxE6RLw<6W)V}etr1znF=vaJbuiVZi!sKfQ7vSKPYPkbUY83HZd7 zgwVWI^&NX-5WD;=PTi>K%-nVqVS1O~kS^IqSloAUyjcG6YNk(F5=#F>exela6Cv}d z&HbuVX3i70J(+P|S4uA34j$-ej(YXKi%Q6cxw)b1+}lWl8998Rw>g=Gk)R%GxTgwAA(V6n$Y>}9%jQiprPbfkv_`k!h+2Qy!0Luv z+q*jq`G0RglJSpRazTgn_AUJOZMcQSHy^K68Ac1~F7UMLzKcp@lj^GogJQ9Nu%txL zuRT!PL~Vh!g9c6WY~jvTXU6VCCN;JAc&O2N<+f^zZ+*1uxYkexGQaM1L<6RUippyPiPL2d{Y zH^S?kc`e6GxENGeSn^ zmEx{UHLIK5q2IV@HD(Lmhx52O3wen1bH9&Y2We;s8%PrZZFnltO6s$GtfvAAA8b1o zZUo!jNQuqM!+lUDmS`e>efAk|OaVLj;6-)xzG5<;uyFhMGy8qp%j)qU5xlFwLe8iU+7=_7(46kFacl}0*8owrDg@s=em<^Yk z%*R`oQ{xQpr$Eo5LOKC%}|e*Ce4p1P$nmwI$)JVm=Mb9HAz99)Ar^yVI-CrW>GAkJ-aU zY-hzLC4r(FCMO39KSV@C#bRo2pBiY=FC0?tnv;`XmCDr&L8PW4F8`9MzZ_2kGiiDx zzXL|rm#>0Aa@V>vNJP>%=Bs*g6@XG^6*LOQ$IoH;ZPCTt3MXLF4J~XYyJ)# zOhXPR*K}v$H**0CRaV-dIGtaKPw(~BlhsIFh51@7t?eFYUua8 zMTH1-4hNK5j=0T7kjZ$pxuEw}z4zB=IGbRO1|;pEJ(9S31uR?AZTj82>hrb+!T6B%CDDd-qsa38){%xFlQkF z$GrUkf{vgvdSi?Cbie6A>m%`GKiIn!9iLRF07>A@)`3)id{6_! zq2W(`q@I)ztgtJr^+v~|PT2?Tv1V)ra&a0tA77DzP!BqJUEJ&Bpf_SC2b*I`D_<1P z%v*SCR&yQjTy?QOGdml+0>RB1w<95|f0!s>0(P<|_6&xSY%sXRaEt5g%N!{}fHA*g z0^%7?M#%F$s|@rF-R?Jg;`g8vN$L$Wya;ewHQ3!03o!~f9A7C8g=VDi_xsC@M$QHa zfF1Wi)OfndSlT&3Wx{)XPn(VqK4?a}L4ybRhVJyYK0)Hsv&hKE)kEiP|GU-4#-s!W zBU6}>x5PysrB`jOglw5cJ;U+>sAwH22EcAd->7BuSwwE+7A!6)FG_~aN1&@)&z(PsXe0wZ-*>@>% zA)-#n4kqyy{Bi~`6;!KpBR52-wAG&+U3xtGACHuTyIEZEuXT(r9gRnVy+2ArfOUN%a&C!vjjRY z@dG2cEH1Dk(|-3tVK4_Q)sdwpQvF8QZBBO>(l zLi{nIgetaVi~p|VJPQDM@Bn>!&S!WSyZXmJGz@3wAYs>mnl!FuG1MmA&^1>oLLc=$ z#$uiD|JGBcq4pEFbp2Q_Pw{i3Id;C!X31`Xx+(FI1KWhRM<-6?v9jvedz!~$mO{lO zCeNpqXd@n2Ba4>vX`=AxE|z*_wF zcH^9TXF#WdY|REu4Hs^34ny36daP}H})C91rEpHc)7|I7t z%2InenYDpC^MR`bfk;3!6*wgbiu32`2F9Rh2X5p3@mIR)7^A*Ev$=Umw#q>lXPyJ` zg$di76`wpme_{5E{lH^I>gGKBQP?J~FEP&^IP}#C92y1A0H2iHx>z0R{G?81Ne4cs zpWpj|O^?R;^MOmC&D*zC_piCxmr_-3Z+5)BkNN%0@;&NqK}zW#ubp;I(>fNF+$O1*aoR`a8Pm^3 zLy_o;*gM;IRn1Ml{bKItE6X!_qcmN^6VF|jSP=gLXb~_X=5;a)8yGoVW>o$XmBI#W zznPh7Fe@=xPW=13{6%NLiS*Zp^7s7yBe^T^{dN5|`}zg0*Qe_{TMIsbW-BSp&Ssti q9UE8mHZneqSeD2S4Ix5@Nij3*cKKMq%5T&R(%|Xp=d#Wzp$P!)7iYd+%|M5hBN4k&&#FbIgP?%E&qnj=i^JWk!;aBzw<9Mk+HS z3CZT1@6$`~&-Xw0{`BpZlej&f*L+-$>$+dp6=QNkmzt7|l7N7KT3=7g4EVSH;)|RJ z_^f-C;zU58#jmfWZtkCFkA#puCm;iUPJqX=0pAM$|KI+9tcUKT4(fI_a3{|@M|>FL z`g1ddic#qm*3~UyghsL0`{!cKdmQ{=IY&dez-{d>s>eEj^~aJ0L0AflM?@NjFb2u(HgO}*&bwt2dyDc3DB zHEnWl8OB?iB&R%m8Wz5c5Emzw-3vW4#o-anJpBAp8QC^C)J+Pb`*=KFbLwbq46pDI z_x;1^;lWnh^eg;3pSA6Y@=!$Z$?5rNTLA)($6bSU=z;#Z>Z6$OXV0wF(3rO)PxZtCm?WG?4?Zz|Z;M~P;%GkqgXvlf-!*t{YL4abh-_pX` znu!S=Tp??JKSLhA_+wS4o6gyZ@VcN#N_T%hngiNz<0;Grs$S|BrIox2O}@`D`E!~3 z%5y-fFiQoz$q*#nKAiBwr(?2-qisM+;{q8DmHt{k>8>9`;8%9CL8m%vR+xVh7T^4j zo&}4KrwJvkrTHx<7ZeJuE&C+ie)Vy+X`)zkF;`_vM?N|1I;l!@vP}9H5?T4eD>iuE zY59JA5>cnK@C-RH9tRIPYWLjb>%yIb%Z+7iTMf7CNG=8PSXx*>W~MQEJ-$UnVfuM` z&DqIGZSWiqpPgCC4+~r57GvcSOg{O+T~tSCO;sbXPy3X|W=TzfP;}FQUh4*l-j=n$ z_i>c1H&10mIV}iu;V(t&L#)`%X($bs<4kkL;`eJ;<&FO7Pk#yN=t#%7JU*V1lsM(% zl|n~{j)(KGGJAsfUYNYY*OswcNrr!SK{;v(&=0rJym_`#N2nW^MrcI$Ts}6FE!mCn z!Xb+@#mVJ&{t#ETg!xlMdhPIZE%kuQZ;ZFxxlkPNd+#MA3YD6&`1bYq;OG^LTR)I4 zE^1pcFc?x!SbvF+!rs-lJ(b)BiheWPZkX7AALd5+6~xCtqSHkx*=jlnCTG9f++M|H zHQw2d&Q(pGRd^^PVbxyiBivPG36^71CcGqLvxIbTurOor^_?=lK9QB?;NawhY=0)S zpiRWjU;HD9{s$fkO;Iiti3gZol%}oaOJ!QFH2Fx9@`A$Ax|^Krcl{sQ%I{&8BK#yvr%&{wlokZm|bqGj=z_7;a^ zh8i){^0=eDedkcRt1Ak;IW6n{rQ35EM9f?s_>Y>L7de&B>Cw8N@Z5bjpEqx0`GiSz zDrWmr`p}tdANgqcU7nZIx?6Ks=}1tJ>aumdqPIRpa5z!qUlFx&W4gOvC`?q3A*sZ7 zqVrU1E#y3Rr(Z#Z43RU935!3j|BP?GG!-AhYv)g$wT+m7u(GnE-k%+qEMHEpE1H}|lVlJm_P8OmD3Wh8~uD%LU(7V7t+}70I zbx7n9t!t_MUsqmgt6#1}Qbee;Q!-}6lXTP|6Lz@|+8QdX{GjMkL_S%s=;$3L*7s4^ zpD}T7WMws})vJsaPzhBG9Tn=yX+sEe(e&sJ3u`*Y&2D~Mo7yt4oYd6cFu`{`g3)-$ zOS+X&D!CIVxge9`$ypAng9z(11mRLa7CJgZ{<9dL(uHuOpr3+*N^Fqmy4UcIR8oEk z&cagg-@joNwGN)}iEaew7ikxQ!!#O!(=(#$HdnJ^6e;5|pa@Gb!4*x3_CKaRppnFx z{{bvjdL1n5BI7=P14kf!NkGy|&w$e0;t7akJubSIUyuq^lHNfO=k5RF53NwN?0i>= zi5UG#1cqNGI)-5>ji?yYUIk%gcfqvtjOX%aZ+&d*ci76_Ajl`$$>L5fdh=rlDr98u zf1H}ved*(TSG;QLWCHxxSAV>-wi%Rxcck@%S}m#7+)NIv-4W5f!Gee7(%f?bIrFuH zoovU$ryjN2sVAG((A|7f2(J}N!BX$y6kX^1O&m$SI2 znaX&PXyNhImdZ3}9&oU{)VlLbQ|!~{<(|tD-{Y@aN0nPesx!{3<+fTvPV`;pqp?MC zUN)KVi2QwlRF~d;uZBRZC~1d}dopP)Jg;l-t0w8a*k6MIOLspeZ1|hy+~T)TK}lCr zzIftxhWUCUE2>e<{MJf8oLa2GfBvcu`69@!DPkwcqVc2^RHj! ztgIIYF!UBSFc}`ffcjbU5)he+HP5t==anfl)+DBUZ+6-H6hid&pXefEmZfRf@u(wu zDt5O9jUX{~%>slh6N}8Rd1LpJ%QtsgzXS|7JLi^_acpHR3WLKn2voe(CW!f*)b9PA zrPR^!CnZGT9gs+S$1}1j8c2#5QH6w#{4RI6quP>8yCM1w)TBYSFcyZqiKX!T%vmxe zir@97aEnU+xFZr3A#2kUR(r=kjuPbkgFE_B7Dq);$0&U_+!;u@x6IoeOYvCH>~jJV6LB%RQhZfD&^E7lvc zOK8lJ4JXvi=CfjCEMqYwjv*xBJfrPG9sT7irvSy9|Jmlg%E|}`b#~g<-rdb3J!W!u z^dgvzJ_92{8FntxAUr-I=Ncs%29C5Nip0dFk$JMb8XtJ?50YN<;)v5r2sbyQe(Z7G zVE!~0*NuMe5lp_UV%+r+&e#s6vT*c_DX&RLkx{PHO&F!lCDu#~IzEB=Ubi%2FC*+R zx2Px|68OblG&=Ien1}CFN>qPhK~bPLO|K=&XFj~zR^UU9nnlbz^y;eG`{#3nf3Ibd z&S4*)A3L@E$C$=LA98X}fB*H24AK{s6mVJDuA3kMUoR%E9mlYW)D)X%O=6Z!{dP^0 z^KGGi+pU4WW}b^%-o56(6PA@_WNd71mU`PdXKd(t*t2ITIXPiDIfQFGhn#+e^YoLO0QXlPdG zy7E1eZ-N{rIizT^dcX*J}vSSXc;Mzuu6UY2x5uz4C=5 zQ)a=|*{Xp^R3*8u8dv0HDodD|zPml&Mn?NxPGS4@o7KTAX|0#{8CabCe9}h7rf=T* z!N_3Wli34*&TSMC*3=2gVo@2OD%E=*csRYgj9_E?j(GAns()XxxEKtD8ag?FRa6ui z=)nudWKG>ER7nr#AcWhgnhB7yszI?YLl$GUIP`GZEs^eYP97{<{M#lep5?v+G9&Zlf zqa?z-Ga{eiNrwv%dbhJPE zod9H12Mth?w(@kIuDLA`$w^Gy1?2uf?ePKGF_N8qe*3NJ$sSpN5-D-2_PxgEJ$$(* zp%pP8C3Tp6b{G0SeIAt&bgTeh`6~47ll|!eg#L9X7!00oldZlpRO`jR=%k!n5!Oe; z$f!YL6iffXgM!)7*}0GUG> z-0NA|5%CMoYBaluP@HPe(StuL4v5fHrOLC#u2*ra^EdMRA9rD+j0-}#?%Y_=09V++ zf)jG`A<0@FTVnJ)EnnemTaG63ug%-F@JzVTa>3lcXid74JM-~e9L?xB+3r5z(*b_r zWO%$i1LLy6bst}!i62Xtu{PO4=SnI11WSK|DApU zH(qteSQHYuu!!3E>j;zSG;6-}B>O*y3Wt9uSs5K1)HsQv(n%n2YJD<1vSGkZuDkz; zwxLE{{4w3v=~rz)`2P@co{>uD2qZ=OoAZ%W+ZL}v{I|agyE8*=IK!$aFjehGQ6q-+ z5ts*~IkJcc-%B11q0t0gSZXUiFsoXPG7Nh3cwkV5gajrcT2ys;QC~1%Zn@?XQF9(q z1}cfX(r-$Ybc%pQh+Kg{7k~62CMUx!R@7#Ti#Y(FSzdDyR&DVNqva0Sn#bGsC6-&= ze@ibN@*i@43v%tNy*p71?1F= z41~CI2jE^(2|D^*fCz;zr4l~0BbF5{6r?(fB+2ytu`z)d%Res-qBuI-1{W6#{BrNI zdxvD!y201d0$*!DuI`wEmZv`N>cEY)?`)4eMFa)K8ycpc z=vif}TV;pE#gRzXDv^n>%33d^Iw6q&&oCK#n1ai~iWY?Y((yFDu|;GGnJhu_c@{>D z{cmAR>k9=rCDaFcd)xc^e*S!+b)z4J1`b;Pp!bL1Q{pbZ>SL}qI_;B@4Lj`;4v>$j zH2lk<%*-rtaS76hSB{qw8rR2zp_N~Q?`34 z=XeAq>$@sU$_pF{f)UZ4{2a;2`9n)ve}MC-Jyct}h8uxHGJ*X6T*a}GSemsn>1Im^ z_mxx+8@_XK_kgBn=ubuFsX}(95v&c}B(~tq37f6gjDehI@5&{MnhruV3+yaqNe)zJ;Yq>_S~=%8b?h zDxk!2b%kHQE*ld~_idyd)btK|b`BK~6s3ho5Lk{AxdudFWul|iNdI|~y_&?t30ake zNiGclusE<*wzpsH44cBKt@34Yi$uQ5Wec^=i76|C?FL_|Ug%3ggoUN5s2J8dPuRsj zLYIpj$MIsy{oCb8inu;K@bN;udF#SICmj@Y;WQwS<9*?}K+IQo7?rmdTtu9oLGaRq z_G&iu{b-p8(ge#%J`$VcLP4HldYkhnhwx6Ar$ zA{OpD*c){>4f3w!vKIBuWDoR2-nt$)JlHc7$14~a7Fjp0ewIV+c88eI*B2Zc`w`$d+u-r@EfoI zk+L#MTH1@BUv;uNDI?4lD48Aj#U0Q+3Knf=U8p~yAZ%z%Oto?T+40We{2Lbm|EQcC zb?Y3|$s-UUp;;FJ=K`CJwv-Z&dy@hA;hX}9j0{Um1_A#3&)hGJV0+Xy4vYzLvq|Ss zgGW&>>)Y#pAb<>zkuhasVk1yn*3=&f!6i7<_Hq@-wsx=J%t(u(wqFm<6^IC0k=EdoW@hLha4W@T0O(sXX`N- zu%skdU0vjwz5zi;kE;JvfJ#8;$1j7mPE?n9u|2>=VpteqzA6whf8lFOh>WP|&|;XD zWJr#dPB!0BF=bug(U%54D%S6_A5LdKS8?Od9gYrZO$~rrBs%7`vT|Hm`Ow@9Ijn%uagxC_O!7a#s0`x25d&Bf0CMNm{<@?{=1ZPgfICJlx zOq%M1|6~h0CLu+#K)}&*gbi#u#ey^|a$!P8+@cG~T~tcSGDtUG02(^$5sJ)dA-_WE zNg+d5{w$j*zLND?#745r0)ZqwvVpTTF`2$`BP={zT?hM*%dkjg4xYxqcEe-Zx1cX; z=VJs zTUgls&oR{v$SSUyG>L2wWNP8~t>Zaz!FmD~-RPHgYL2}<%$rBr4QfIkE>ZVT9M#UzdSFE$T^# z-xPxk*18J@B-s`)Iv3KqZL9!*$sO%Qb$m*_6blodtU)qPNemK@;-g1K!wPj&h;

pV5dn<+=&n#??Jm4JZzPn3(eqfgTRI?>in%?1Lo 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 literal 0 HcmV?d00001 diff --git a/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-3-clamp-4.png b/tensorflow_addons/image/test_data/Yellow_Smiley_Face_Warp-interp-3-clamp-4.png new file mode 100644 index 0000000000000000000000000000000000000000..8e78146d955ae8f02230121e6314f3285e87611e GIT binary patch literal 19313 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%0AW`i;Qk7J?3Z20KV6zVRavk|*sC92VUfH$ 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 Date: Sat, 18 May 2019 16:48:19 -0700 Subject: [PATCH 08/16] Fix comment --- tensorflow_addons/image/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow_addons/image/__init__.py b/tensorflow_addons/image/__init__.py index 2e9413e8ab..f0e4b25c78 100644 --- a/tensorflow_addons/image/__init__.py +++ b/tensorflow_addons/image/__init__.py @@ -27,7 +27,7 @@ from tensorflow_addons.image.transform_ops import rotate from tensorflow_addons.image.transform_ops import transform from tensorflow_addons.image.sparse_image_warp import sparse_image_warp -# partially expose private functions needed by test suite. +# partially expose two private functions needed by test suite. from tensorflow_addons.image.sparse_image_warp import _get_grid_locations from tensorflow_addons.image.sparse_image_warp import _get_boundary_locations from tensorflow_addons.image.interpolate_spline import interpolate_spline From cc752af14b1108f17594bea965d494a9d7abed6d Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Thu, 6 Jun 2019 19:47:20 -0700 Subject: [PATCH 09/16] Address comments, switch gradient calculation to use GradientTape --- tensorflow_addons/image/interpolate_spline.py | 5 ++- .../image/interpolate_spline_test.py | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tensorflow_addons/image/interpolate_spline.py b/tensorflow_addons/image/interpolate_spline.py index 81616aa6d6..bcb1e121f4 100644 --- a/tensorflow_addons/image/interpolate_spline.py +++ b/tensorflow_addons/image/interpolate_spline.py @@ -18,7 +18,6 @@ from __future__ import print_function import tensorflow as tf -from tensorflow.python.framework import tensor_shape # TODO: better import? EPSILON = 0.0000000001 @@ -103,12 +102,12 @@ def _solve_interpolation(train_points, train_values, order, b, n, _ = tf.unstack(tf.shape(train_points), num=3) d = train_points.shape[-1] - if tensor_shape.dimension_value(d) is None: + if d is None: raise ValueError('The dimensionality of the input points (d) must be ' 'statically-inferrable.') k = train_values.shape[-1] - if tensor_shape.dimension_value(k) is None: + if k is None: raise ValueError('The dimensionality of the output values (k) must be ' 'statically-inferrable.') diff --git a/tensorflow_addons/image/interpolate_spline_test.py b/tensorflow_addons/image/interpolate_spline_test.py index a337c5615f..608a1318d3 100644 --- a/tensorflow_addons/image/interpolate_spline_test.py +++ b/tensorflow_addons/image/interpolate_spline_test.py @@ -23,6 +23,7 @@ import tensorflow as tf import tensorflow.compat.v1 as tf1 # TODO: locate placeholder from tensorflow.python.training import momentum +from tensorflow_addons.utils import test_utils from tensorflow_addons.image import interpolate_spline @@ -249,6 +250,7 @@ def test_nd_linear_interpolation(self): self.assertAllClose(interp_val[0, :, 0], target_interpolation) + @test_utils.run_deprecated_v1 def test_nd_linear_interpolation_unspecified_shape(self): """Ensure that interpolation supports dynamic batch_size and num_points.""" @@ -336,29 +338,28 @@ def test_interpolation_gradient(self): training data locations are optimized iteratively using gradient descent. """ - self.skipTest("TODO: port gradient to tf2.0") tp = _QuadraticPlusSinProblemND() (query_points, query_values, train_points, train_values) = tp.get_problem(optimizable=True) regularization = 0.001 for interpolation_order in (1, 2, 3, 4): - interpolator = interpolate_spline( - train_points, train_values, query_points, interpolation_order, - regularization) - - loss = tf.reduce_mean(tf.square(query_values - interpolator)) - optimizer = momentum.MomentumOptimizer(0.001, 0.9) - grad = tf.gradients(loss, [train_points]) - grad, _ = tf.clip_by_global_norm(grad, 1.0) - opt_func = optimizer.apply_gradients(zip(grad, [train_points])) - init_op = tf1.variables.global_variables_initializer() - with self.cached_session() as sess: - sess.run(init_op) - for _ in range(100): - sess.run([loss, opt_func]) + @tf.function + def train_step(): + with tf.GradientTape() as gt: + interpolator = interpolate_spline( + train_points, train_values, query_points, + interpolation_order, regularization) + loss = tf.reduce_mean( + tf.square(query_values - interpolator)) + grad = gt.gradient(loss, [train_points]) + grad, _ = tf.clip_by_global_norm(grad, 1.0) + opt_func = optimizer.apply_gradients(zip(grad, [train_points])) + + for epoch in range(100): + train_step() if __name__ == '__main__': From 1158a9081bade2b2dec62b9db086f1489479bd62 Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Thu, 6 Jun 2019 19:49:29 -0700 Subject: [PATCH 10/16] Forgot to unskip one last test --- tensorflow_addons/image/interpolate_spline_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow_addons/image/interpolate_spline_test.py b/tensorflow_addons/image/interpolate_spline_test.py index 608a1318d3..9a847f2f34 100644 --- a/tensorflow_addons/image/interpolate_spline_test.py +++ b/tensorflow_addons/image/interpolate_spline_test.py @@ -254,7 +254,6 @@ def test_nd_linear_interpolation(self): def test_nd_linear_interpolation_unspecified_shape(self): """Ensure that interpolation supports dynamic batch_size and num_points.""" - self.skipTest("TODO: port to tf2.0 / eager") tp = _QuadraticPlusSinProblemND() (query_points, _, train_points, train_values) = tp.get_problem(dtype='float64') From 1c5330ee1ddb87bf7d7a63214e5480c8a9e58312 Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Thu, 6 Jun 2019 21:08:21 -0700 Subject: [PATCH 11/16] Remove private functions from init --- tensorflow_addons/image/__init__.py | 3 --- tensorflow_addons/image/sparse_image_warp_test.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tensorflow_addons/image/__init__.py b/tensorflow_addons/image/__init__.py index f0e4b25c78..dc019fd559 100644 --- a/tensorflow_addons/image/__init__.py +++ b/tensorflow_addons/image/__init__.py @@ -27,7 +27,4 @@ from tensorflow_addons.image.transform_ops import rotate from tensorflow_addons.image.transform_ops import transform from tensorflow_addons.image.sparse_image_warp import sparse_image_warp -# partially expose two private functions needed by test suite. -from tensorflow_addons.image.sparse_image_warp import _get_grid_locations -from tensorflow_addons.image.sparse_image_warp import _get_boundary_locations from tensorflow_addons.image.interpolate_spline import interpolate_spline diff --git a/tensorflow_addons/image/sparse_image_warp_test.py b/tensorflow_addons/image/sparse_image_warp_test.py index 6e46746b7a..ab6b98ab92 100644 --- a/tensorflow_addons/image/sparse_image_warp_test.py +++ b/tensorflow_addons/image/sparse_image_warp_test.py @@ -20,9 +20,9 @@ import numpy as np import tensorflow as tf import tensorflow.compat.v1 as tf1 # TODO: port TF1 test files? -from tensorflow_addons.image import _get_boundary_locations +from tensorflow_addons.image.sparse_image_warp import _get_boundary_locations +from tensorflow_addons.image.sparse_image_warp import _get_grid_locations from tensorflow_addons.image import sparse_image_warp -from tensorflow_addons.image import _get_grid_locations from tensorflow.python.training import momentum from tensorflow_addons.utils.resource_loader import get_path_to_datafile From 745e0eeb8308f8f76949719954e2a5a59b1663bc Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Fri, 28 Jun 2019 22:59:39 -0700 Subject: [PATCH 12/16] Address reviewer comments --- tensorflow_addons/image/interpolate_spline.py | 2 +- tensorflow_addons/image/interpolate_spline_test.py | 5 ++--- tensorflow_addons/image/sparse_image_warp.py | 5 ++--- tensorflow_addons/image/sparse_image_warp_test.py | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tensorflow_addons/image/interpolate_spline.py b/tensorflow_addons/image/interpolate_spline.py index bcb1e121f4..84abf98209 100644 --- a/tensorflow_addons/image/interpolate_spline.py +++ b/tensorflow_addons/image/interpolate_spline.py @@ -1,4 +1,4 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 The TensorFlow Authors. 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. diff --git a/tensorflow_addons/image/interpolate_spline_test.py b/tensorflow_addons/image/interpolate_spline_test.py index 9a847f2f34..106edf8beb 100644 --- a/tensorflow_addons/image/interpolate_spline_test.py +++ b/tensorflow_addons/image/interpolate_spline_test.py @@ -1,4 +1,4 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 The TensorFlow Authors. 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. @@ -22,7 +22,6 @@ import tensorflow as tf import tensorflow.compat.v1 as tf1 # TODO: locate placeholder -from tensorflow.python.training import momentum from tensorflow_addons.utils import test_utils from tensorflow_addons.image import interpolate_spline @@ -343,7 +342,7 @@ def test_interpolation_gradient(self): regularization = 0.001 for interpolation_order in (1, 2, 3, 4): - optimizer = momentum.MomentumOptimizer(0.001, 0.9) + optimizer = tf1.train.MomentumOptimizer(0.001, 0.9) @tf.function def train_step(): diff --git a/tensorflow_addons/image/sparse_image_warp.py b/tensorflow_addons/image/sparse_image_warp.py index e5227adfd7..b1697eab0f 100644 --- a/tensorflow_addons/image/sparse_image_warp.py +++ b/tensorflow_addons/image/sparse_image_warp.py @@ -1,4 +1,4 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 The TensorFlow Authors. 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. @@ -19,7 +19,6 @@ import numpy as np import tensorflow as tf -from tensorflow.python.framework import tensor_shape # TODO: better import? from tensorflow_addons.image import dense_image_warp from tensorflow_addons.image import interpolate_spline @@ -73,7 +72,7 @@ def _add_zero_flow_controls_at_boundary(control_point_locations, merged_control_point_flows: augmented set of control point flows """ - batch_size = tensor_shape.dimension_value(control_point_locations.shape[0]) + batch_size = tf.compat.dimension_value(control_point_locations.shape[0]) boundary_point_locations = _get_boundary_locations( image_height, image_width, boundary_points_per_edge) diff --git a/tensorflow_addons/image/sparse_image_warp_test.py b/tensorflow_addons/image/sparse_image_warp_test.py index ab6b98ab92..ff15103933 100644 --- a/tensorflow_addons/image/sparse_image_warp_test.py +++ b/tensorflow_addons/image/sparse_image_warp_test.py @@ -1,4 +1,4 @@ -# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 The TensorFlow Authors. 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. From d4c06a4cf1fa2683f9b77e329d2f1fac83f33ccd Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Fri, 28 Jun 2019 23:20:48 -0700 Subject: [PATCH 13/16] One last review fix --- tensorflow_addons/image/sparse_image_warp_test.py | 3 +-- tensorflow_addons/seq2seq/attention_wrapper.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tensorflow_addons/image/sparse_image_warp_test.py b/tensorflow_addons/image/sparse_image_warp_test.py index ff15103933..4c2659c7d5 100644 --- a/tensorflow_addons/image/sparse_image_warp_test.py +++ b/tensorflow_addons/image/sparse_image_warp_test.py @@ -23,7 +23,6 @@ from tensorflow_addons.image.sparse_image_warp import _get_boundary_locations from tensorflow_addons.image.sparse_image_warp import _get_grid_locations from tensorflow_addons.image import sparse_image_warp -from tensorflow.python.training import momentum from tensorflow_addons.utils.resource_loader import get_path_to_datafile @@ -238,7 +237,7 @@ def testThatBackpropRuns(self): num_boundary_points=3) loss = tf.reduce_mean(tf.abs(warped_image - image)) - optimizer = momentum.MomentumOptimizer(0.001, 0.9) + optimizer = tf1.train.MomentumOptimizer(0.001, 0.9) grad = tf.gradients(loss, [image]) grad, _ = tf.clip_by_global_norm(grad, 1.0) opt_func = optimizer.apply_gradients(zip(grad, [image])) diff --git a/tensorflow_addons/seq2seq/attention_wrapper.py b/tensorflow_addons/seq2seq/attention_wrapper.py index 3abe62bd8c..54b1bf1be8 100644 --- a/tensorflow_addons/seq2seq/attention_wrapper.py +++ b/tensorflow_addons/seq2seq/attention_wrapper.py @@ -1899,8 +1899,8 @@ def call(self, inputs, state, **kwargs): # previous attention value. cell_inputs = self._cell_input_fn(inputs, state.attention) cell_state = state.cell_state - cell_output, next_cell_state = self._cell( - cell_inputs, cell_state, **kwargs) + cell_output, next_cell_state = self._cell(cell_inputs, cell_state, + **kwargs) cell_batch_size = (tf.compat.dimension_value(cell_output.shape[0]) or tf.shape(cell_output)[0]) From afee5068d4127f0fcfb48d2d1d13e163a028fc33 Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Sun, 30 Jun 2019 11:19:31 -0700 Subject: [PATCH 14/16] Temporarily add scipy to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index cb9cccd4d1..bf1dc9a86f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ tf-nightly-2.0-preview==2.0.0.dev20190625 +scipy \ No newline at end of file From 216373334c65e2ef7b5431721fe727ada639b6f5 Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Wed, 17 Jul 2019 21:17:24 -0700 Subject: [PATCH 15/16] Fix resolved merge issue --- tensorflow_addons/image/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tensorflow_addons/image/__init__.py b/tensorflow_addons/image/__init__.py index f1636cd660..d0d886735d 100644 --- a/tensorflow_addons/image/__init__.py +++ b/tensorflow_addons/image/__init__.py @@ -26,9 +26,6 @@ from tensorflow_addons.image.filters import median_filter2d from tensorflow_addons.image.transform_ops import rotate from tensorflow_addons.image.transform_ops import transform -<<<<<<< HEAD from tensorflow_addons.image.sparse_image_warp import sparse_image_warp from tensorflow_addons.image.interpolate_spline import interpolate_spline -======= from tensorflow_addons.image.translate_ops import translate ->>>>>>> upstream/master From 45510f6fd2e405b8de4179917f11e8600f9e12b8 Mon Sep 17 00:00:00 2001 From: Kyle Beauchamp Date: Wed, 17 Jul 2019 21:36:41 -0700 Subject: [PATCH 16/16] Fix lint --- tensorflow_addons/image/BUILD | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow_addons/image/BUILD b/tensorflow_addons/image/BUILD index 894e50c3cf..79f5b1fcd5 100644 --- a/tensorflow_addons/image/BUILD +++ b/tensorflow_addons/image/BUILD @@ -30,7 +30,6 @@ filegroup( srcs = glob(["test_data/*.png"]), ) - py_test( name = "dense_image_warp_test", size = "small", @@ -147,4 +146,4 @@ py_test( deps = [ ":image", ], -) \ No newline at end of file +)