3

I want to use scipy.signal.fftconvolve in Tensorflow/Keras, is there any way to do that?

Right now I am using the following code :

window = np.tile(window, (1, 1, 1, 3))
tf.nn.conv2d(img1, window, strides=[1,1,1,1], padding='VALID')

Are these lines equivalent to :

signal.fftconvolve(img1, window, mode='valid')
Jonas Adler
  • 10,365
  • 5
  • 46
  • 73
Hamdard
  • 265
  • 5
  • 18
  • The best I could find is: https://www.tensorflow.org/api_docs/python/tf/fft – Daniel Möller Nov 14 '17 at 11:27
  • fft is not important. I want convolve function. Can't I use Conv2d for this purpose? – Hamdard Nov 14 '17 at 20:26
  • Do you mean a convolution of the function with itself? Something like this: https://stackoverflow.com/questions/46803541/autocorrelation-of-the-input-in-tensorflow-keras – Daniel Möller Nov 15 '17 at 11:46
  • I don't think so. I am interested to convolve the function with pre-defined filter. Let Suppose, I have a 2D filter which I want to convolve with the input image. As a result, I will get the same size of output image as input but its each channel will be convolved with that filter. Here is [pytorch version](https://github.com/Po-Hsun-Su/pytorch-ssim/blob/master/pytorch_ssim/__init__.py#L18) where they used conv2d for it. – Hamdard Nov 15 '17 at 14:36

2 Answers2

7

Implementation

FFT convolution can be relatively easily implemented in tensorflow. The following follows scipy.signal.fftconvolve quite strictly

import tensorflow as tf

def _centered(arr, newshape):
    # Return the center newshape portion of the array.
    currshape = tf.shape(arr)[-2:]
    startind = (currshape - newshape) // 2
    endind = startind + newshape
    return arr[..., startind[0]:endind[0], startind[1]:endind[1]]

def fftconv(in1, in2, mode="full"):
    # Reorder channels to come second (needed for fft)
    in1 = tf.transpose(in1, perm=[0, 3, 1, 2])
    in2 = tf.transpose(in2, perm=[0, 3, 1, 2])

    # Extract shapes
    s1 = tf.convert_to_tensor(tf.shape(in1)[-2:])
    s2 = tf.convert_to_tensor(tf.shape(in2)[-2:])
    shape = s1 + s2 - 1

    # Compute convolution in fourier space
    sp1 = tf.spectral.rfft2d(in1, shape)
    sp2 = tf.spectral.rfft2d(in2, shape)
    ret = tf.spectral.irfft2d(sp1 * sp2, shape)

    # Crop according to mode
    if mode == "full":
        cropped = ret
    elif mode == "same":
        cropped = _centered(ret, s1)
    elif mode == "valid":
        cropped = _centered(ret, s1 - s2 + 1)
    else:
        raise ValueError("Acceptable mode flags are 'valid',"
                         " 'same', or 'full'.")

    # Reorder channels to last
    result = tf.transpose(cropped, perm=[0, 2, 3, 1])
    return result

Example

A quick example of applying a gaussian smoothing with width 20 pixels to the standard "face" image is as follows:

if __name__ == '__main__':
    from scipy import misc
    import matplotlib.pyplot as plt
    from tensorflow.python.ops import array_ops, math_ops
    session = tf.InteractiveSession()

    # Create gaussian
    std = 20
    grid_x, grid_y = array_ops.meshgrid(math_ops.range(3 * std),
                                        math_ops.range(3 * std))
    grid_x = tf.cast(grid_x[None, ..., None], 'float32')
    grid_y = tf.cast(grid_y[None, ..., None], 'float32')

    gaussian = tf.exp(-((grid_x - 1.5 * std) ** 2 + (grid_y - 1.5 * std) ** 2) / std ** 2)
    gaussian = gaussian / tf.reduce_sum(gaussian)

    face = misc.face(gray=False)[None, ...].astype('float32')

    # Apply convolution
    result = fftconv(face, gaussian, 'same')
    result_r = session.run(result)

    # Show results
    plt.figure('face')
    plt.imshow(face[0, ...] / 256.0)

    plt.figure('convolved')
    plt.imshow(result_r[0, ...] / 256.0)

enter image description here enter image description here

Jonas Adler
  • 10,365
  • 5
  • 46
  • 73
  • Nice answer. What changes would it be required in `fftconv()` if the input images were grayscale , i.e. with shape `(151, 346)` ? I tried commenting out the 3 `tf.transpose()` calls but the result is different from `scipy.signal.fftconvolve()` with `mode="full"`. – karlphillip Apr 21 '20 at 10:52
0

You want just a regular conv2d then...

If you want it somewhere in the model, add a Conv2D(...,name='myLayer') layer, and in the model use model.get_layer('myLayer').set_weights([filters,biases])

If you want it in a loss function, just create a loss function:

import keras.backend as K
def myLoss(y_true, y_pred):

    #where y_true is the true training data and y_pred is the model's output
    convResult = K.conv2d(y_pred, kernel = window, padding = 'same')
    anotherResult = K.depthwise_conv2d(y_pred, kernel = window, padding='same')

The regular conv2D will assume each output channel in the filter will process and sum all input channels.

The depthwise convolution will keep input channels separate.

Beware of the window, though. I don't know the format in tensorflow or scipy, but the kernel in keras should have this shape: (height, width, numberOfInputChannels, numberOfOutputChannels)

I believe, if I understand it right, it should be window = np.reshape(_FSpecialGauss(size, sigma), (size, size, 1, 1)), considering that "size" is the size of the kernel and you have only 1 input and output channels.

I used padding='same' to get the result image the same size of the input. If you use padding='valid', you will lose the borders (although in your case, your filter seems to have size (1,1), which won't remove borders).

You can use any tensorflow function inside the loss function as well:

def customLoss(yTrue,yPred):
    tf.anyFunction(yTrue)
    tf.anyFunction(yPred)

Using keras backend will let your code be portable to other backends later.

When compiling the model, give it your loss function:

model.compile(loss=myLoss, optimizer =....)
Daniel Möller
  • 84,878
  • 18
  • 192
  • 214
  • Like used [here](https://stackoverflow.com/questions/39051451/ssim-ms-ssim-for-tensorflow), Conv2d can only be used for grayscale. I am more interested in getting a single filter (2D) which I would apply on all 3 color channels. As a result, I should get back the 3 Color channel convoluted image. Right now, I am trying with [depthwise_conv2d](https://www.tensorflow.org/api_docs/python/tf/nn/depthwise_conv2d) as similar to pytorch approach. About Window size, you are right, I have already realized about that . – Hamdard Nov 15 '17 at 16:55
  • Hmmm, sounds like you want the `depthwise_conv2d` then. – Daniel Möller Nov 15 '17 at 16:57
  • Now, you got it. What about padding then ? Pytorch implementation `mu1 = F.conv2d(img1, window, padding = window_size//2, groups = channel)` shows, I should use valid padding with half of window size, on other end. I can't see such thing in Tensorflow implementation or better to say Scipy implementation. – Hamdard Nov 15 '17 at 17:03
  • This seems to be `padding='same'`. It pads the image so the final result is the same size as the input. – Daniel Möller Nov 15 '17 at 17:09