Repository: Theano/Theano

Issue:
Problem with neibs2images

Hi,
I am trying to use the images2neibs and neibs2images functions, but the neibs2images seems not to be working. My theano is updated. Here is the simplest example I've tryied

```python
import numpy as np
import theano
from theano import tensor as T

imagesP = T.tensor4('imagesP')
patches = T.nnet.neighbours.images2neibs(imagesP, [16,16])
extractPatches = theano.function([imagesP], patches,on_unused_input='ignore')

patsRecovery = T.matrix('patsRecovery')
original_size  = T.vector('original_size')
reconstructed_imgs = T.nnet.neighbours.neibs2images(patsRecovery, (16,16),original_size)
recoverImg = theano.function([patsRecovery,original_size],reconstructed_imgs,on_unused_input='ignore')

im_val = np.ones((1,3, 320, 320),dtype=np.float32)
neibs = extractPatches(im_val)    #shape = (1200,256)
recovered_img = recoverImg(neibs ,im_val.shape)

```

While the "images2neibs" works fine, the other one keep getting the following error "ValueError: ('You cannot drop a non-broadcastable dimension.', ((False, False, False, False), ('x', 'x', 0, 1)))"
Can someone help me?



Discussion:
The error message seems to indicate that `patsRecovery` is a 4D tensor, which does not seem to be the case from your code.
Another issue is that `original_size` should be a vector of integers, not floats.
If I correct that last issue, I find another error because Theano is not able to infer the length of `original_size`, even though it is supposed to be 4 for everything to make sense. I'll try to come up with a fix for that.

Cheating a little bit to make the shape 4 scalars instead of one vector, the following code works:
```python
import numpy as np
import theano
from theano import tensor as T
import theano.tensor.nnet.neighbours

imagesP = T.tensor4('imagesP')
patches = T.nnet.neighbours.images2neibs(imagesP, [16,16])
extractPatches = theano.function([imagesP], patches,on_unused_input='ignore')

patsRecovery = T.matrix('patsRecovery')
original_size  = [T.iscalar('original_size%i' % i) for i in range(4)]
reconstructed_imgs = T.nnet.neighbours.neibs2images(patsRecovery, (16,16),original_size)
recoverImg = theano.function([patsRecovery] + original_size, reconstructed_imgs, on_unused_input='ignore')

im_val = np.ones((1,3, 320, 320), dtype=np.float32)
neibs = extractPatches(im_val)
recovered_img = recoverImg(neibs, *im_val.shape)
```

Resolving patch:
diff --git a/theano/tensor/nnet/neighbours.py b/theano/tensor/nnet/neighbours.py
--- a/theano/tensor/nnet/neighbours.py
+++ b/theano/tensor/nnet/neighbours.py
@@ -152,7 +152,7 @@ def pos2map(pidx, pgz, prior_result, neib_shape, neib_step):
                 grad_undefined(self, 2, neib_step)]
 
     def c_code_cache_version(self):
-        return (5,)
+        return (7,)
 
     def perform(self, node, inp, out_):
         ten4, neib_shape, neib_step = inp
@@ -177,6 +177,12 @@ def CEIL_INTDIV(a, b):
         c, d = neib_shape
         step_x, step_y = neib_step
         mode = self.mode
+        if step_x <= 0 or step_y <= 0:
+            raise ValueError(
+                "neib_step wrong step ; values <= 0. Got " + str(neib_step))
+        if c <= 0 or d <= 0:
+            raise ValueError(
+                "neib_shape values <=0. Got " + str(neib_shape))
 
         if mode == "wrap_centered":
             if (c % 2 != 1) or (d % 2 != 1):
@@ -317,8 +323,24 @@ def c_code(self, node, name, inp, out, sub):
         const npy_intp c = (npy_intp) *(dtype_%(neib_shape)s*) PyArray_GETPTR1(%(neib_shape)s, 0);
         const npy_intp d = (npy_intp) *(dtype_%(neib_shape)s*) PyArray_GETPTR1(%(neib_shape)s, 1);
         // (step_x,step_y) = neib_step
-        const npy_intp step_x = (npy_intp) *(dtype_%(neib_step)s*) PyArray_GETPTR1(%(neib_step)s, 0);
-        const npy_intp step_y = (npy_intp) *(dtype_%(neib_step)s*) PyArray_GETPTR1(%(neib_step)s, 1);
+        const dtype_%(neib_step)s step_x = *(dtype_%(neib_step)s*) PyArray_GETPTR1(%(neib_step)s, 0);
+        const dtype_%(neib_step)s step_y = *(dtype_%(neib_step)s*) PyArray_GETPTR1(%(neib_step)s, 1);
+
+        if (step_x <=0 || step_y <=0)
+        {
+            PyErr_Format(PyExc_ValueError,
+                         "neib_step wrong step ; values <= 0. Got %%lld %%lld.",
+                         (long long) step_x, (long long) step_y);
+            %(fail)s;
+        }
+
+        if (c <=0 || d <=0)
+        {
+            PyErr_Format(PyExc_ValueError,
+                         "neib_shape values <= 0. Got %%lld %%lld.",
+                         (long long)c, (long long)d);
+            %(fail)s;
+        }
 
         if ( "%(mode)s" == "wrap_centered") {
             if (c%%2!=1 || d%%2!=1){
@@ -617,10 +639,17 @@ def neibs2images(neibs, neib_shape, original_shape, mode='valid'):
                              new_neib_shape, mode=mode)
 
     if mode == 'ignore_borders':
-        valid_shape = list(original_shape)
-        valid_shape[2] = (valid_shape[2] // neib_shape[0]) * neib_shape[0]
-        valid_shape[3] = (valid_shape[3] // neib_shape[1]) * neib_shape[1]
-        output_4d = output_2d.reshape(valid_shape)
+        # We use set_subtensor to accept original_shape we can't infer
+        # the shape and still raise error when it don't have the right
+        # shape.
+        valid_shape = original_shape
+        valid_shape = T.set_subtensor(
+            valid_shape[2],
+            (valid_shape[2] // neib_shape[0]) * neib_shape[0])
+        valid_shape = T.set_subtensor(
+            valid_shape[3],
+            (valid_shape[3] // neib_shape[1]) * neib_shape[1])
+        output_4d = output_2d.reshape(valid_shape, ndim=4)
         # padding the borders with zeros
         for d in [2, 3]:
             pad_shape = list(output_4d.shape)
@@ -629,7 +658,7 @@ def neibs2images(neibs, neib_shape, original_shape, mode='valid'):
     elif mode == 'valid':
         # TODO: we do not implement all mode with this code.
         # Add a check for the good cases.
-        output_4d = output_2d.reshape(original_shape)
+        output_4d = output_2d.reshape(original_shape, ndim=4)
     else:
         raise NotImplementedError("neibs2images do not support mode=%s" % mode)
 


Test patch:
diff --git a/theano/tensor/nnet/tests/test_neighbours.py b/theano/tensor/nnet/tests/test_neighbours.py
--- a/theano/tensor/nnet/tests/test_neighbours.py
+++ b/theano/tensor/nnet/tests/test_neighbours.py
@@ -144,8 +144,7 @@ def test_neibs_bad_shape(self):
         shape = (2, 3, 10, 10)
         for dtype in self.dtypes:
             images = shared(numpy.arange(
-                numpy.prod(shape), dtype=dtype
-                ).reshape(shape))
+                numpy.prod(shape), dtype=dtype).reshape(shape))
 
             for neib_shape in [(3, 2), (2, 3)]:
                 neib_shape = T.as_tensor_variable(neib_shape)
@@ -340,6 +339,33 @@ def fn(images):
                             mode=self.mode)
         self.assertRaises(TypeError, f, images_val)
 
+    def test_can_not_infer_nb_dim(self):
+        # Was reported in gh-5613. Test that we do not crash
+        # or that we crash in a few other case found while
+        # investigating that case
+
+        img = T.tensor4('img')
+        patches = T.nnet.neighbours.images2neibs(img, [16, 16])
+        extractPatches = theano.function([img], patches)
+
+        patsRecovery = T.matrix('patsRecovery')
+        original_size = T.ivector('original_size')
+
+        for mode in ['valid', 'ignore_borders']:
+            out = neibs2images(patsRecovery, (16, 16),
+                               original_size, mode=mode)
+            f = theano.function([patsRecovery, original_size], out)
+
+            im_val = numpy.ones((1, 3, 320, 320), dtype=numpy.float32)
+            neibs = extractPatches(im_val)
+            f(neibs, im_val.shape)
+            # Wrong number of dimensions
+            self.assertRaises(ValueError, f, neibs,
+                              (1, 1, 3, 320, 320))
+            # End up with a step of 0
+            self.assertRaises(ValueError, f, neibs,
+                              (3, 320, 320, 1))
+
     def speed_neibs(self):
         shape = (100, 40, 18, 18)
         images = shared(numpy.arange(numpy.prod(shape),