Repository: vispy/vispy

Issue:
Possible dimensions error in Isocurve  
Hi,
working by Vispy, that is great! I think I found a possible problem in
` vispy/vispy/visuals/isocurve.py.`
Isocurve works only if the data first dimension is equal to the second dimension. Taking a look at the code it seems that there is a wrong order of the arguments for the function np.meshgrid. In particular, if data has a shape, for instance (n,m), the result of the meshgrid function are two matrix (m,n).

https://github.com/vispy/vispy/blob/9d46fac28eac985fbe5d032d1581710fb74999ea/vispy/visuals/isocurve.py#L107-L109

my solution:

            if self._X is None or self._X.T.shape != data.shape:
                self._X, self._Y = np.meshgrid(np.arange(data.shape[1]), **_<- changed_**
                                               np.arange(data.shape[0])) **_<- change_**


Am I wrong? I hope it helps you.
Kind regards,
Luigi


Discussion:
@luigi1973 

Yes, there is something fishy there. I would consider this a bug. But we need to check, which dimension is x and which is y (or cols /rows). 

Bitter truth is,  that I didn't found that, when I was implementing the matplotlib contour algorithm back then. All examples where using equal-sized (x,y) dimensions, so this didn't surface.

@larsoner Do we have any assignment on the sequence or order of dimensions (x/y, rows/columns)? 
I agree and this needs to be handled carefully. There is always confusion surrounding rows/cols versus x/y, especially when meshgrid comes in to play.
I'm not sure what the requirement is. Probably best to do whatever `matplotlib` does
```python
        # if using matplotlib isoline algorithm we have to check for meshgrid
        # and we can setup the tracer object here
        if _HAS_MPL:
            if self._X is None or self._X.T.shape != data.shape:
                self._X, self._Y = np.meshgrid(np.arange(data.shape[0]),
                                               np.arange(data.shape[1]))
            self._iso = cntr.Cntr(self._X, self._Y, self._data.astype(float))
```

This is actually the code to use the matplotlib contour line tracing (directly from the c code)  in `IsocurveVisual`. Now doing this was/is not recommended from the matplotlib devs, we thought it would be good to have it here for performance reasons. The plain python approach is really slow.

IMHO the order is (rows/cols) what also @luigi1973 already suggested, but then we have to fix also those if-clause (otherwise the meshgrid will be recreated everytime same-shape new data is loaded):

```python
        # if using matplotlib isoline algorithm we have to check for meshgrid
        # and we can setup the tracer object here
        if _HAS_MPL:
            if self._X is None or self._X.shape != data.shape:
                self._X, self._Y = np.meshgrid(np.arange(data.shape[1]),
                                               np.arange(data.shape[0]))
            self._iso = cntr.Cntr(self._X, self._Y, self._data.astype(float))
```
@davidh-ssec Does that make sense to you? At least in the isocurve example the underlying image if sized (200,100) has 200 rows and 100 columns. So we should be on the safe side then, shouldn't we.

Side Note:
For those who do not need the isocurve vertices at all, they can use ContourVisual where the contour lines are created directly with shaders.
This makes sense to me. It definitely seemed odd to have the `.T` in there so I'm glad that is gone. We can even compare meshgrid usage to the contour code in mpl: https://github.com/matplotlib/matplotlib/blob/49d5ced86c4dd3b8ce4ec8db235a1c49b9ae2f0f/lib/matplotlib/contour.py#L1610

So yes, this all looks good to me. `shape = (num_rows, num_cols)` and `x, y = np.meshgrid(x, y)`

Edit: I ran the `isocurve.py` example and change the image size to `(300, 100)` with these changes and it all seems to make sense. Other than that I've never used this Visual before. Maybe the fixing PR should include a change to (200, 100) to make this more obvious?
Yes, absolutely. I'll issue a fix as suggested in near time. 

Resolving patch:
diff --git a/examples/basics/scene/isocurve.py b/examples/basics/scene/isocurve.py
--- a/examples/basics/scene/isocurve.py
+++ b/examples/basics/scene/isocurve.py
@@ -21,8 +21,8 @@
 view = canvas.central_widget.add_view()
 
 # Create the image
-img_data = np.empty((100, 100, 3), dtype=np.ubyte)
-noise = np.random.normal(size=(100, 100), loc=50, scale=150)
+img_data = np.empty((200, 100, 3), dtype=np.ubyte)
+noise = np.random.normal(size=(200, 100), loc=50, scale=150)
 noise = gaussian_filter(noise, (4, 4, 0))
 img_data[:] = noise[..., np.newaxis]
 image = scene.visuals.Image(img_data, parent=view.scene)
diff --git a/examples/basics/scene/isocurve_updates.py b/examples/basics/scene/isocurve_updates.py
--- a/examples/basics/scene/isocurve_updates.py
+++ b/examples/basics/scene/isocurve_updates.py
@@ -44,13 +44,13 @@
     box.camera.aspect = 1.0
 
 # Create random image
-img_data1 = np.empty((100, 100, 3), dtype=np.ubyte)
-noise = np.random.normal(size=(100, 100), loc=50, scale=150)
+img_data1 = np.empty((200, 100, 3), dtype=np.ubyte)
+noise = np.random.normal(size=(200, 100), loc=50, scale=150)
 noise = gaussian_filter(noise, (4, 4, 0))
 img_data1[:] = noise[..., np.newaxis]
 
 # create 2d array with some function
-x, y = np.mgrid[0:2*np.pi:101j, 0:2*np.pi:101j]
+x, y = np.mgrid[0:2*np.pi:201j, 0:2*np.pi:101j]
 myfunc = np.cos(2*x[:-1, :-1]) + np.sin(2*y[:-1, :-1])
 
 # add image to viewbox1
@@ -84,8 +84,8 @@
     myfunc, levels=levels2, color_lev='cubehelix', parent=vb4.scene)
 
 # set viewport
-vb3.camera.set_range((0, 100), (0, 100))
-vb4.camera.set_range((0, 100), (0, 100))
+vb3.camera.set_range((-100, 200), (0, 200))
+vb4.camera.set_range((0, 100), (0, 200))
 
 
 # setup update parameters
diff --git a/vispy/visuals/isocurve.py b/vispy/visuals/isocurve.py
--- a/vispy/visuals/isocurve.py
+++ b/vispy/visuals/isocurve.py
@@ -104,9 +104,9 @@ def set_data(self, data):
         # if using matplotlib isoline algorithm we have to check for meshgrid
         # and we can setup the tracer object here
         if _HAS_MPL:
-            if self._X is None or self._X.T.shape != data.shape:
-                self._X, self._Y = np.meshgrid(np.arange(data.shape[0]),
-                                               np.arange(data.shape[1]))
+            if self._X is None or self._X.shape != data.shape:
+                self._X, self._Y = np.meshgrid(np.arange(data.shape[1]),
+                                               np.arange(data.shape[0]))
             self._iso = cntr.Cntr(self._X, self._Y, self._data.astype(float))
 
         if self._clim is None:


Test patch: