From 74e9ccc7d5c888a8bd9236bcf0b291f64458ac66 Mon Sep 17 00:00:00 2001 From: Androbin Date: Tue, 2 May 2017 01:04:08 +0200 Subject: [PATCH] add "frames" op to "contrib/signal" (#9236) * add "frames" op Added op to slice a tensor into (overlapping) frames. May be used for signal processing purposes. Inspired by `python_speech_features.sigproc.framesig` * Update array_ops.py * Update array_ops.py * Update array_ops.py * Update array_ops.py * Update array_ops.py * Update array_ops.py * Update array_ops.py * Update array_ops.py * Update array_ops.py * Update array_ops.py * Update array_ops.py * Add files via upload * Update array_ops.py * Add files via upload * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Add files via upload * Delete __init__.py * Delete BUILD * Delete __init__.py * Delete shape_ops_test.py * Delete __init__.py * Delete shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops.py * Update shape_ops_test.py * Update shape_ops_test.py * Add files via upload * Update BUILD * Update shape_ops.py * Update BUILD * Update shape_ops.py * Update shape_ops.py * fix buildifier complaint * Update shape_ops.py * Fix some expected linting issues * fix todo colon * Remove public visibility of signal_py, since it is brought in via contrib API. * Fix reference to default package visibility to //tensorflow --- tensorflow/contrib/signal/BUILD | 46 +++++++++++ tensorflow/contrib/signal/__init__.py | 27 +++++++ tensorflow/contrib/signal/python/__init__.py | 18 +++++ .../python/kernel_tests/shape_ops_test.py | 68 ++++++++++++++++ .../contrib/signal/python/ops/__init__.py | 18 +++++ .../contrib/signal/python/ops/shape_ops.py | 81 +++++++++++++++++++ tensorflow/tools/pip_package/BUILD | 2 + 7 files changed, 260 insertions(+) create mode 100644 tensorflow/contrib/signal/BUILD create mode 100644 tensorflow/contrib/signal/__init__.py create mode 100644 tensorflow/contrib/signal/python/__init__.py create mode 100644 tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py create mode 100644 tensorflow/contrib/signal/python/ops/__init__.py create mode 100644 tensorflow/contrib/signal/python/ops/shape_ops.py diff --git a/tensorflow/contrib/signal/BUILD b/tensorflow/contrib/signal/BUILD new file mode 100644 index 00000000000..ddcd0a03cf2 --- /dev/null +++ b/tensorflow/contrib/signal/BUILD @@ -0,0 +1,46 @@ +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +package(default_visibility = ["//tensorflow:__subpackages__"]) + +load("//tensorflow:tensorflow.bzl", "cuda_py_tests") + +py_library( + name = "signal_py", + srcs = ["__init__.py"] + glob(["python/ops/*.py"]), + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + ], +) + +cuda_py_tests( + name = "shape_ops_test", + size = "small", + srcs = ["python/kernel_tests/shape_ops_test.py"], + additional_deps = [ + ":signal_py", + "//third_party/py/numpy", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform_test", + ], +) + +filegroup( + name = "all_files", + srcs = glob( + ["**/*"], + exclude = [ + "**/METADATA", + "**/OWNERS", + ], + ), + visibility = ["//tensorflow:__subpackages__"], +) diff --git a/tensorflow/contrib/signal/__init__.py b/tensorflow/contrib/signal/__init__.py new file mode 100644 index 00000000000..33639aa82a0 --- /dev/null +++ b/tensorflow/contrib/signal/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2016 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. +# ============================================================================== + +""" +@@frames +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.signal.python.ops.shape_ops import frames + +from tensorflow.python.util.all_util import remove_undocumented +remove_undocumented(__name__) diff --git a/tensorflow/contrib/signal/python/__init__.py b/tensorflow/contrib/signal/python/__init__.py new file mode 100644 index 00000000000..d2b5550aa61 --- /dev/null +++ b/tensorflow/contrib/signal/python/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2016 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. +# ============================================================================== + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function diff --git a/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py new file mode 100644 index 00000000000..ba6c67c7704 --- /dev/null +++ b/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py @@ -0,0 +1,68 @@ +# Copyright 2015 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 shape_ops.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.signal.python.ops import shape_ops +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import test + + +class FramesTest(test.TestCase): + + def test_mapping_of_indices_without_padding(self): + with self.test_session(): + tensor = constant_op.constant(np.arange(9152), dtypes.int32) + tensor = array_ops.expand_dims(tensor, 0) + + result = shape_ops.frames(tensor, 512, 180) + result = result.eval() + + expected = np.tile(np.arange(512), (49, 1)) + expected += np.tile(np.arange(49) * 180, (512, 1)).T + + expected = np.expand_dims(expected, axis=0) + expected = np.array(expected, dtype=np.int32) + + self.assertAllEqual(expected, result) + + def test_mapping_of_indices_with_padding(self): + with self.test_session(): + tensor = constant_op.constant(np.arange(10000), dtypes.int32) + tensor = array_ops.expand_dims(tensor, 0) + + result = shape_ops.frames(tensor, 512, 192) + result = result.eval() + + expected = np.tile(np.arange(512), (51, 1)) + expected += np.tile(np.arange(51) * 192, (512, 1)).T + + expected[expected >= 10000] = 0 + + expected = np.expand_dims(expected, axis=0) + expected = np.array(expected, dtype=np.int32) + + self.assertAllEqual(expected, result) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/signal/python/ops/__init__.py b/tensorflow/contrib/signal/python/ops/__init__.py new file mode 100644 index 00000000000..d2b5550aa61 --- /dev/null +++ b/tensorflow/contrib/signal/python/ops/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2016 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. +# ============================================================================== + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function diff --git a/tensorflow/contrib/signal/python/ops/shape_ops.py b/tensorflow/contrib/signal/python/ops/shape_ops.py new file mode 100644 index 00000000000..e1d0f977c15 --- /dev/null +++ b/tensorflow/contrib/signal/python/ops/shape_ops.py @@ -0,0 +1,81 @@ +# Copyright 2017 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. +# ============================================================================== + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops + +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import math_ops + +def frames(signal, frame_length, frame_step, name=None): + """Frame a signal into overlapping frames. + May be used in front of spectral functions. + + For example: + + ```python + pcm = tf.placeholder(tf.float32, [None, 9152]) + frames = tf.contrib.signal.frames(pcm, 512, 180) + magspec = tf.abs(tf.spectral.rfft(frames, [512])) + image = tf.expand_dims(magspec, 3) + ``` + + Args: + signal: A `Tensor` of shape `[batch_size, signal_length]`. + frame_length: An `int32` or `int64` `Tensor`. The length of each frame. + frame_step: An `int32` or `int64` `Tensor`. The step between frames. + name: A name for the operation (optional). + + Returns: + A `Tensor` of frames with shape `[batch_size, num_frames, frame_length]`. + """ + with ops.name_scope(name, "frames", [signal, frame_length, frame_step]): + signal = ops.convert_to_tensor(signal, name="signal") + frame_length = ops.convert_to_tensor(frame_length, name="frame_length") + frame_step = ops.convert_to_tensor(frame_step, name="frame_step") + + signal_rank = signal.shape.ndims + + if signal_rank != 2: + raise ValueError("expected signal to have rank 2 but was " + signal_rank) + + signal_length = array_ops.shape(signal)[1] + + num_frames = math_ops.ceil((signal_length - frame_length) / frame_step) + num_frames = 1 + math_ops.cast(num_frames, dtypes.int32) + + pad_length = (num_frames - 1) * frame_step + frame_length + pad_signal = array_ops.pad( + signal, [[0, 0], [0, pad_length - signal_length]]) + + indices_frame = array_ops.expand_dims(math_ops.range(frame_length), 0) + indices_frames = array_ops.tile(indices_frame, [num_frames, 1]) + + indices_step = array_ops.expand_dims( + math_ops.range(num_frames) * frame_step, 1) + indices_steps = array_ops.tile(indices_step, [1, frame_length]) + + indices = indices_frames + indices_steps + + # TODO(Androbin): remove `transpose` when `gather` gets `axis` support + pad_signal = array_ops.transpose(pad_signal) + frames = array_ops.gather(pad_signal, indices) + frames = array_ops.transpose(frames, perm=[2, 0, 1]) + + return frames diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index c17a7f7fb19..83be430e7d6 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -68,6 +68,7 @@ py_binary( ":included_headers", "//tensorflow/contrib/nn:nn_py", "//tensorflow/contrib/session_bundle:session_bundle_pip", + "//tensorflow/contrib/signal:signal_py", "//tensorflow/contrib/slim/python/slim/data:data_pip", "//tensorflow/python:util_example_parser_configuration", "//tensorflow/python/debug:debug_pip", @@ -141,6 +142,7 @@ sh_binary( "//tensorflow/contrib/ndlstm:ndlstm", "//tensorflow/contrib/nn:nn_py", "//tensorflow/contrib/session_bundle:session_bundle_pip", + "//tensorflow/contrib/signal:signal_py", "//tensorflow/contrib/slim:slim", "//tensorflow/contrib/slim/python/slim/data:data_pip", "//tensorflow/contrib/slim/python/slim/nets:nets_pip",