Repository: pyqtgraph/pyqtgraph

Issue:
Possible bug with version 0.11.0
<!-- In the following, please describe your issue in detail! -->
<!-- If some of the sections do not apply, just remove them. -->

### Short description
Possible bug with creating ImageItem in version 0.11.0

### Code to reproduce
<!-- Please provide a minimal working example that reproduces the issue in the code block below.
     Ideally, this should be a full example someone else could run without additional setup. -->
```python
import sys
import pyqtgraph as pg
from PyQt5.QtWidgets import QApplication, QWidget
 
class TestWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
 
        # setting property causes an error with 0.11.0 but works well with 0.10.0
        self._imitem = pg.ImageItem(autoDownsample=True)
        # without setting works well with 0.11.0
        # self._imitem = pg.ImageItem()
 
        self._viewbox = pg.ViewBox()
        # the following line rises the error
        self._viewbox.addItem(self._imitem)
 
class MainWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.testWidget = TestWidget(self)
 
def main():
    app = QApplication(sys.argv)
    mw = MainWidget()
    mw.show()
    return app.exec()
 
if __name__ == '__main__':
    main()
```

### Expected behavior
the code works in all previous versions of pyqtgraph.

### Real behavior
In version 0.11.0 the error rises.
Please see comments around the line
`self._imitem = pg.ImageItem(autoDownsample=True)`

```
Traceback (most recent call last):
  File "C:\_RT\OpenNFT\venv\lib\site-packages\pyqtgraph\graphicsItems\GraphicsObject.py", line 23, in itemChange
    self.parentChanged()
  File "C:\_RT\OpenNFT\venv\lib\site-packages\pyqtgraph\graphicsItems\GraphicsItem.py", line 458, in parentChanged
    self._updateView()
  File "C:\_RT\OpenNFT\venv\lib\site-packages\pyqtgraph\graphicsItems\GraphicsItem.py", line 515, in _updateView
    self.viewTransformChanged()
  File "C:\_RT\OpenNFT\venv\lib\site-packages\pyqtgraph\graphicsItems\ImageItem.py", line 558, in viewTransformChanged
    w = Point(x-o).length()
TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
Traceback (most recent call last):
  File "C:\_RT\OpenNFT\venv\lib\site-packages\pyqtgraph\graphicsItems\ImageItem.py", line 558, in viewTransformChanged
    w = Point(x-o).length()
TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
Traceback (most recent call last):
  File "C:\_RT\OpenNFT\venv\lib\site-packages\pyqtgraph\graphicsItems\ImageItem.py", line 558, in viewTransformChanged
    w = Point(x-o).length()
TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
Traceback (most recent call last):
  File "C:\_RT\OpenNFT\venv\lib\site-packages\pyqtgraph\graphicsItems\ImageItem.py", line 558, in viewTransformChanged
    w = Point(x-o).length()
TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
```

### Tested environment(s)

 * PyQtGraph version: 0.11.0
 * Qt Python binding: PyQt5 5.13.2 Qt 5.13.2
 * Python version: 3.6.3
 * NumPy version: 1.17.4
 * Operating system: Windows 7/10
 * Installation method: pip

### Additional context

Initially, we get this bug in our project OpenNFT after upgrading pyqtgraph from 0.10.0 to 0.11.0,
the error rises in this line:
https://github.com/OpenNFT/OpenNFT/blob/master/opennft/opennft.py#L154

P.S. Actually, all works well for 0.11.0.dev0 too.


Discussion:
Introduced with #755. I think the solution is pretty straightforward, just checking if `o`/`x`/`y` is None:

https://github.com/pyqtgraph/pyqtgraph/blob/9fdaffaf7dbdd1aaac7ab482afb5ceab21e81a31/pyqtgraph/graphicsItems/ImageItem.py#L553-L559

...as is done in `render()`:

https://github.com/pyqtgraph/pyqtgraph/blob/9fdaffaf7dbdd1aaac7ab482afb5ceab21e81a31/pyqtgraph/graphicsItems/ImageItem.py#L378-L380

For what it's worth, this shouldn't really have any negative consequences aside from the error messages.

I'm not sure off hand why `viewTransformChanged` is getting called when `ImageItem.scene()` is still None. It could be worth digging into that a bit more.

Thanks for reporting.
It looks like a solution.
Thank you for answer.

Resolving patch:
diff --git a/pyqtgraph/graphicsItems/ImageItem.py b/pyqtgraph/graphicsItems/ImageItem.py
--- a/pyqtgraph/graphicsItems/ImageItem.py
+++ b/pyqtgraph/graphicsItems/ImageItem.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 from __future__ import division
 
 from ..Qt import QtGui, QtCore
@@ -370,22 +371,11 @@ def render(self):
             lut = None
 
         if self.autoDownsample:
-            # reduce dimensions of image based on screen resolution
-            o = self.mapToDevice(QtCore.QPointF(0,0))
-            x = self.mapToDevice(QtCore.QPointF(1,0))
-            y = self.mapToDevice(QtCore.QPointF(0,1))
-
-            # Check if graphics view is too small to render anything
-            if o is None or x is None or y is None:
-                return
-
-            w = Point(x-o).length()
-            h = Point(y-o).length()
-            if w == 0 or h == 0:
+            xds, yds = self._computeDownsampleFactors()
+            if xds is None:
                 self.qimage = None
                 return
-            xds = max(1, int(1.0 / w))
-            yds = max(1, int(1.0 / h))
+
             axes = [1, 0] if self.axisOrder == 'row-major' else [0, 1]
             image = fn.downsample(self.image, xds, axis=axes[0])
             image = fn.downsample(image, yds, axis=axes[1])
@@ -552,20 +542,28 @@ def pixelSize(self):
 
     def viewTransformChanged(self):
         if self.autoDownsample:
-            o = self.mapToDevice(QtCore.QPointF(0,0))
-            x = self.mapToDevice(QtCore.QPointF(1,0))
-            y = self.mapToDevice(QtCore.QPointF(0,1))
-            w = Point(x-o).length()
-            h = Point(y-o).length()
-            if w == 0 or h == 0:
+            xds, yds = self._computeDownsampleFactors()
+            if xds is None:
                 self.qimage = None
                 return
-            xds = max(1, int(1.0 / w))
-            yds = max(1, int(1.0 / h))
             if (xds, yds) != self._lastDownsample:
                 self.qimage = None
                 self.update()
 
+    def _computeDownsampleFactors(self):
+        # reduce dimensions of image based on screen resolution
+        o = self.mapToDevice(QtCore.QPointF(0, 0))
+        x = self.mapToDevice(QtCore.QPointF(1, 0))
+        y = self.mapToDevice(QtCore.QPointF(0, 1))
+        # scene may not be available yet
+        if o is None:
+            return None, None
+        w = Point(x - o).length()
+        h = Point(y - o).length()
+        if w == 0 or h == 0:
+            return None, None
+        return max(1, int(1.0 / w)), max(1, int(1.0 / h))
+
     def mouseDragEvent(self, ev):
         if ev.button() != QtCore.Qt.LeftButton:
             ev.ignore()


Test patch: