Repository: Quasars/orange-spectroscopy (language: python)

Issue:
Hyperspectra doesn't like to work with the SNR widget
The output of the SNR widget is not well handled in Hyperspectra. It might be related to the number of rows in the data table.


Discussion / hints:

Fix patch:
diff --git a/orangecontrib/spectroscopy/utils/__init__.py b/orangecontrib/spectroscopy/utils/__init__.py
index d707e7798..b5ea1f61b 100644
--- a/orangecontrib/spectroscopy/utils/__init__.py
+++ b/orangecontrib/spectroscopy/utils/__init__.py
@@ -43,11 +43,14 @@ def values_to_linspace(vals):
 
 def location_values(vals, linspace):
     vals = np.asarray(vals)
-    if linspace[2] == 1:  # everything is the same value
+    if linspace is None or linspace[2] == 1:  # everything is the same value
         width = 1
     else:
         width = (linspace[1] - linspace[0]) / (linspace[2] - 1)
-    return (vals - linspace[0]) / width
+    start = 0
+    if linspace is not None:
+        start = linspace[0]
+    return (vals - start) / width
 
 
 def index_values(vals, linspace):
diff --git a/orangecontrib/spectroscopy/widgets/owhyper.py b/orangecontrib/spectroscopy/widgets/owhyper.py
index 35c7823b6..9d04cc59b 100644
--- a/orangecontrib/spectroscopy/widgets/owhyper.py
+++ b/orangecontrib/spectroscopy/widgets/owhyper.py
@@ -1,4 +1,5 @@
 import collections.abc
+import math
 from collections import OrderedDict
 from xml.sax.saxutils import escape
 
@@ -60,6 +61,10 @@ class ImageTooBigException(Exception):
     pass
 
 
+class UndefinedImageException(Exception):
+    pass
+
+
 def refresh_integral_markings(dis, markings_list, curveplot):
     for m in markings_list:
         if m in curveplot.markings:
@@ -125,7 +130,7 @@ def get_levels(img):
     while img.size > 2 ** 16:
         img = img[::2, ::2]
     mn, mx = bottleneck.nanmin(img), bottleneck.nanmax(img)
-    if mn == mx:
+    if mn == mx or math.isnan(mx) or math.isnan(mn):
         mn = 0
         mx = 255
     return [mn, mx]
@@ -793,6 +798,8 @@ def set_data(self, data):
             self.data_ids = {}
 
     def refresh_img_selection(self):
+        if self.lsx is None or self.lsy is None:
+            return
         selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=np.uint8)
         selected_px[self.data_imagepixels[self.data_valid_positions, 0],
                     self.data_imagepixels[self.data_valid_positions, 1]] = \
@@ -867,12 +874,9 @@ def update_view(self):
         self.data_imagepixels = None
         self.data_valid_positions = None
 
-        if self.data and self.attr_x and self.attr_y:
-            self.start(self.compute_image, self.data, self.attr_x, self.attr_y,
-                       self.parent.image_values(),
-                       self.parent.image_values_fixed_levels())
-        else:
-            self.image_updated.emit()
+        self.start(self.compute_image, self.data, self.attr_x, self.attr_y,
+                    self.parent.image_values(),
+                    self.parent.image_values_fixed_levels())
 
     def set_visible_image(self, img: np.ndarray, rect: QRectF):
         self.vis_img.setImage(img)
@@ -896,6 +900,9 @@ def set_visible_image_comp_mode(self, comp_mode: QPainter.CompositionMode):
     def compute_image(data: Orange.data.Table, attr_x, attr_y,
                       image_values, image_values_fixed_levels, state: TaskState):
 
+        if data is None or attr_x is None or attr_y is None:
+            raise UndefinedImageException
+
         def progress_interrupt(i: float):
             if state.is_interruption_requested():
                 raise InterruptException
@@ -922,17 +929,22 @@ def extract_col(data, var):
         res.image_values_fixed_levels = image_values_fixed_levels
         progress_interrupt(0)
 
-        if lsx[-1] * lsy[-1] > IMAGE_TOO_BIG:
+        if lsx is not None and lsy is not None \
+                and lsx[-1] * lsy[-1] > IMAGE_TOO_BIG:
             raise ImageTooBigException((lsx[-1], lsy[-1]))
 
-        # the code below does this, but part-wise:
-        # d = image_values(data).X[:, 0]
-        parts = []
-        for slice in split_to_size(len(data), 10000):
-            part = image_values(data[slice]).X
-            parts.append(part)
-            progress_interrupt(0)
-        d = np.concatenate(parts)
+        if lsx is not None and lsy is not None:
+            # the code below does this, but part-wise:
+            # d = image_values(data).X[:, 0]
+            parts = []
+            for slice in split_to_size(len(data), 10000):
+                part = image_values(data[slice]).X
+                parts.append(part)
+                progress_interrupt(0)
+            d = np.concatenate(parts)
+        else:
+            # no need to compute integrals when nothing will be shown
+            d = np.full((data.X.shape[0], 1), float("nan"))
 
         res.d = d
         progress_interrupt(0)
@@ -957,25 +969,26 @@ def on_done(self, res):
         if invalid_positions:
             self.parent.Information.not_shown(invalid_positions)
 
-        imdata = np.ones((lsy[2], lsx[2], d.shape[1])) * float("nan")
-        imdata[yindex[valid], xindex[valid]] = d[valid]
-
-        self.data_values = d
-        self.data_imagepixels = np.vstack((yindex, xindex)).T
-        self.img.setImage(imdata, autoLevels=False)
-        self.update_levels()
-        self.update_rgb_levels()
-        self.update_color_schema()
-        self.update_legend_visible()
+        if lsx is not None and lsy is not None:
+            imdata = np.ones((lsy[2], lsx[2], d.shape[1])) * float("nan")
+            imdata[yindex[valid], xindex[valid]] = d[valid]
 
-        # shift centres of the pixels so that the axes are useful
-        shiftx = _shift(lsx)
-        shifty = _shift(lsy)
-        left = lsx[0] - shiftx
-        bottom = lsy[0] - shifty
-        width = (lsx[1]-lsx[0]) + 2*shiftx
-        height = (lsy[1]-lsy[0]) + 2*shifty
-        self.img.setRect(QRectF(left, bottom, width, height))
+            self.data_values = d
+            self.data_imagepixels = np.vstack((yindex, xindex)).T
+            self.img.setImage(imdata, autoLevels=False)
+            self.update_levels()
+            self.update_rgb_levels()
+            self.update_color_schema()
+            self.update_legend_visible()
+
+            # shift centres of the pixels so that the axes are useful
+            shiftx = _shift(lsx)
+            shifty = _shift(lsy)
+            left = lsx[0] - shiftx
+            bottom = lsy[0] - shifty
+            width = (lsx[1]-lsx[0]) + 2*shiftx
+            height = (lsy[1]-lsy[0]) + 2*shifty
+            self.img.setRect(QRectF(left, bottom, width, height))
 
         self.refresh_img_selection()
         self.image_updated.emit()
@@ -990,6 +1003,8 @@ def on_exception(self, ex: Exception):
         if isinstance(ex, ImageTooBigException):
             self.parent.Error.image_too_big(ex.args[0][0], ex.args[0][1])
             self.image_updated.emit()
+        elif isinstance(ex, UndefinedImageException):
+            self.image_updated.emit()
         else:
             raise ex
 


Test patch:
diff --git a/orangecontrib/spectroscopy/tests/test_owhyper.py b/orangecontrib/spectroscopy/tests/test_owhyper.py
index 354daf673..3a01899fc 100644
--- a/orangecontrib/spectroscopy/tests/test_owhyper.py
+++ b/orangecontrib/spectroscopy/tests/test_owhyper.py
@@ -116,8 +116,16 @@ def setUpClass(cls):
         irisunknown = Interpolate(np.arange(20))(cls.iris)
         # dataset without any attributes, but XY
         whitelight0 = cls.whitelight.transform(
-            Orange.data.Domain([], None, metas=cls.whitelight.domain.metas))
-        cls.strange_data = [None, cls.iris1, iris0, empty, irisunknown, whitelight0]
+            Orange.data.Domain([], None, metas=cls.whitelight.domain.metas))[:100]
+        unknowns = cls.iris.copy()
+        with unknowns.unlocked():
+            unknowns.X[:, :] = float("nan")
+        single_pixel = cls.iris[:50]  # all image coordinates map to one spot
+        unknown_pixel = single_pixel.copy()
+        with unknown_pixel.unlocked():
+            unknown_pixel.Y[:] = float("nan")
+        cls.strange_data = [None, cls.iris1, iris0, empty, irisunknown, whitelight0,
+                            unknowns, single_pixel, unknown_pixel]
 
     def setUp(self):
         self.widget = self.create_widget(OWHyper)  # type: OWHyper
@@ -159,7 +167,9 @@ def try_big_selection(self):
 
     def test_strange(self):
         for data in self.strange_data:
+            self.widget = self.create_widget(OWHyper)
             self.send_signal("Data", data)
+            wait_for_image(self.widget)
             self.try_big_selection()
 
     def test_context_not_open_invalid(self):