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): 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