def test_linking_and_enabling(self): # Regression test for a bug that caused layers not not be correctly # enabled/disabled. self.viewer.add_data(self.image1) self.viewer.add_data(self.catalog) self.catalog.add_component([4, 5, 6], 'e') self.data_collection.new_subset_group(subset_state=self.catalog.id['e'] > 4) process_events() assert self.viewer.layers[0].enabled # image assert not self.viewer.layers[1].enabled # scatter assert not self.viewer.layers[2].enabled # image subset assert not self.viewer.layers[3].enabled # scatter subset link1 = LinkSame(self.catalog.id['c'], self.image1.pixel_component_ids[0]) link2 = LinkSame(self.catalog.id['d'], self.image1.pixel_component_ids[1]) self.data_collection.add_link(link1) self.data_collection.add_link(link2) process_events() assert self.viewer.layers[0].enabled # image assert self.viewer.layers[1].enabled # scatter assert not self.viewer.layers[2].enabled # image subset assert self.viewer.layers[3].enabled # scatter subset
def test_linking_and_enabling(self): # Regression test for a bug that caused layers not not be correctly # enabled/disabled. self.viewer.add_data(self.image1) self.viewer.add_data(self.catalog) self.catalog.add_component([4, 5, 6], 'e') self.data_collection.new_subset_group(subset_state=self.catalog.id['e'] > 4) process_events() assert self.viewer.layers[0].enabled # image assert not self.viewer.layers[1].enabled # scatter assert not self.viewer.layers[2].enabled # image subset assert not self.viewer.layers[3].enabled # scatter subset link1 = LinkSame(self.catalog.id['c'], self.image1.world_component_ids[0]) link2 = LinkSame(self.catalog.id['d'], self.image1.world_component_ids[1]) self.data_collection.add_link(link1) self.data_collection.add_link(link2) process_events() assert self.viewer.layers[0].enabled # image assert self.viewer.layers[1].enabled # scatter assert not self.viewer.layers[2].enabled # image subset assert self.viewer.layers[3].enabled # scatter subset
def test_graceful_close_after_invalid(capsys): # Regression test for a bug that caused an error if an invalid dataset # was added to the viewer after the user had acknowledged the error. d = Data(a=[[1, 2], [3, 4]], label='test') dc = DataCollection([d]) gapp = GlueApplication(dc) viewer = gapp.new_data_viewer(TableViewer) gapp.show() process_events() with pytest.raises(ValueError, match='Can only use Table widget for 1D data'): viewer.add_data(d) viewer.close() process_events() # We use capsys here because the # error is otherwise only apparent in stderr. out, err = capsys.readouterr() assert out.strip() == "" assert err.strip() == ""
def test_log_labels(self): # Regression test to make sure the labels are correctly changed to log # when the x-axis is in log space. viewer_state = self.viewer.state data = Data(x=np.logspace(-5, 5, 10000)) self.data_collection.append(data) self.viewer.add_data(data) viewer_state.x_log = True process_events() labels = [ x.get_text() for x in self.viewer.axes.xaxis.get_ticklabels() ] # Different Matplotlib versions return slightly different # labels, but the ones below should be present regardless # of Matplotlib version. expected_present = [ '$\\mathdefault{10^{-5}}$', '$\\mathdefault{10^{-3}}$', '$\\mathdefault{10^{-1}}$', '$\\mathdefault{10^{1}}$', '$\\mathdefault{10^{3}}$', '$\\mathdefault{10^{5}}$' ] for label in expected_present: assert label in labels
def test_select_previously_incompatible_layer(self): # Regression test for a bug that caused a selection in a previously disabled # layer to enable the layer without updating the subset view self.viewer.add_data(self.image1) self.viewer.add_data(self.catalog) self.catalog.add_component([4, 5, 6], 'e') link1 = LinkSame(self.catalog.id['c'], self.image1.world_component_ids[0]) link2 = LinkSame(self.catalog.id['d'], self.image1.world_component_ids[1]) self.data_collection.add_link(link1) self.data_collection.add_link(link2) self.data_collection.new_subset_group(subset_state=self.catalog.id['e'] > 4) process_events() assert self.viewer.layers[0].enabled # image assert self.viewer.layers[1].enabled # scatter assert not self.viewer.layers[2].enabled # image subset assert self.viewer.layers[3].enabled # scatter subset assert not self.viewer.layers[2].image_artist.get_visible() self.data_collection.subset_groups[0].subset_state = self.catalog.id['c'] > -1 process_events() assert self.viewer.layers[0].enabled # image assert self.viewer.layers[1].enabled # scatter assert self.viewer.layers[2].enabled # image subset assert self.viewer.layers[3].enabled # scatter subset assert self.viewer.layers[2].image_artist.get_visible()
def test_navigate_sync_image(self): self.viewer.add_data(self.data) image_viewer = self.app.new_data_viewer(ImageViewer) image_viewer.add_data(self.data) assert image_viewer.state.slices == (0, 0, 0) self.viewer.state.x_att = self.data.pixel_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[1, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) self.viewer.axes.figure.canvas.button_release_event(x, y, 1) assert image_viewer.state.slices == (1, 0, 0) self.viewer.state.x_att = self.data.world_component_ids[0] x, y = self.viewer.axes.transData.transform([[10, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) self.viewer.axes.figure.canvas.button_release_event(x, y, 1) assert image_viewer.state.slices == (5, 0, 0)
def test_density_map_incompatible_subset(self, capsys): # Regression test for a bug that caused the scatter viewer to crash # if subset for density map was incompatible. data2 = Data(label='d1', x=[3.4, 2.3, -1.1, 0.3], y=[3.2, 3.3, 3.4, 3.5], z=['a', 'b', 'c', 'a']) self.data_collection.append(data2) self.viewer.add_data(self.data) self.viewer.add_data(data2) self.data_collection.new_subset_group('test', self.data.id['x'] > 1) for layer in self.viewer.state.layers: layer.density_map = True self.viewer.figure.canvas.draw() process_events() assert self.viewer.layers[0].enabled assert not self.viewer.layers[1].enabled assert self.viewer.layers[2].enabled assert not self.viewer.layers[3].enabled
def test_datetime64_disabled(self, capsys): # Make sure that datetime components aren't options for the vector and # error markers. data = Data(label='test') data.add_component(np.array([100, 200, 300, 400], dtype='M8[D]'), 't1') data.add_component(np.array([200, 300, 400, 500], dtype='M8[D]'), 't2') data.add_component(np.array([200., 300., 400., 500.]), 'x') data.add_component(np.array([200., 300., 400., 500.]), 'y') self.data_collection.append(data) self.viewer.add_data(data) self.viewer.state.x_att = data.id['x'] self.viewer.state.y_att = data.id['y'] self.viewer.state.layers[0].cmap_mode = 'Linear' self.viewer.state.layers[0].cmap_att = data.id['x'] self.viewer.state.layers[0].size_mode = 'Linear' self.viewer.state.layers[0].size_att = data.id['y'] self.viewer.state.layers[0].vector_visible = True self.viewer.state.layers[0].xerr_visible = True self.viewer.state.layers[0].yerr_visible = True process_events() self.viewer.state.x_att = data.id['t1'] self.viewer.state.y_att = data.id['t2'] process_events() # We use capsys here because the # error is otherwise only apparent in stderr. out, err = capsys.readouterr() assert out.strip() == "" assert err.strip() == ""
def test_collapse_reverse(self, capsys): # Regression test for a bug that caused collapsing to fail if selecting # the range in the reverse direction. self.viewer.add_data(self.data) image_viewer = self.app.new_data_viewer(ImageViewer) image_viewer.add_data(self.data) self.profile_tools.ui.tabs.setCurrentIndex(2) self.viewer.state.x_att = self.data.pixel_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[15.1, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[0.9, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) self.profile_tools.ui.button_collapse.click() process_events() # We use capsys here because the # error is otherwise only apparent in stderr. out, err = capsys.readouterr() assert out.strip() == "" assert err.strip() == ""
def test_navigate_sync_image(self): self.viewer.add_data(self.data) image_viewer = self.app.new_data_viewer(ImageViewer) image_viewer.add_data(self.data) assert image_viewer.state.slices == (0, 0, 0) self.viewer.state.x_att = self.data.pixel_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[1, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) self.viewer.axes.figure.canvas.button_release_event(x, y, 1) assert image_viewer.state.slices == (1, 0, 0) self.viewer.state.x_att = self.data.world_component_ids[0] x, y = self.viewer.axes.transData.transform([[10, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) self.viewer.axes.figure.canvas.button_release_event(x, y, 1) assert image_viewer.state.slices == (5, 0, 0)
def wait(self): # Wait 0.5 seconds to make sure that the computation has properly started time.sleep(0.5) while self._worker.running: time.sleep(1 / 25) from glue.utils.qt import process_events process_events()
def test_select_previously_incompatible_layer(self): # Regression test for a bug that caused a selection in a previously disabled # layer to enable the layer without updating the subset view self.viewer.add_data(self.image1) self.viewer.add_data(self.catalog) self.catalog.add_component([4, 5, 6], 'e') link1 = LinkSame(self.catalog.id['c'], self.image1.pixel_component_ids[0]) link2 = LinkSame(self.catalog.id['d'], self.image1.pixel_component_ids[1]) self.data_collection.add_link(link1) self.data_collection.add_link(link2) self.data_collection.new_subset_group(subset_state=self.catalog.id['e'] > 4) process_events() assert self.viewer.layers[0].enabled # image assert self.viewer.layers[1].enabled # scatter assert not self.viewer.layers[2].enabled # image subset assert self.viewer.layers[3].enabled # scatter subset assert not self.viewer.layers[2].image_artist.get_visible() self.data_collection.subset_groups[0].subset_state = self.catalog.id['c'] > -1 process_events() assert self.viewer.layers[0].enabled # image assert self.viewer.layers[1].enabled # scatter assert self.viewer.layers[2].enabled # image subset assert self.viewer.layers[3].enabled # scatter subset assert self.viewer.layers[2].image_artist.get_visible()
def test_log_labels(self): # Regression test to make sure the labels are correctly changed to log # when the x-axis is in log space. viewer_state = self.viewer.state data = Data(x=np.logspace(-5, 5, 10000)) self.data_collection.append(data) self.viewer.add_data(data) viewer_state.x_log = True process_events() labels = [x.get_text() for x in self.viewer.axes.xaxis.get_ticklabels()] # Different Matplotlib versions return slightly different # labels, but the ones below should be present regardless # of Matplotlib version. expected_present = ['$\\mathdefault{10^{-5}}$', '$\\mathdefault{10^{-3}}$', '$\\mathdefault{10^{-1}}$', '$\\mathdefault{10^{1}}$', '$\\mathdefault{10^{3}}$', '$\\mathdefault{10^{5}}$'] for label in expected_present: assert label in labels
def __init__(self, figure): self.figure = figure # For recent versions of Matplotlib it seems that we need # to process events at least twice to really flush out any # unprocessed events process_events() process_events() self.start = self.figure.canvas._draw_count
def wait(self): if QT_INSTALLED: # Wait 0.5 seconds to make sure that the computation has properly started time.sleep(0.5) while self._worker.running: time.sleep(1 / 25) from glue.utils.qt import process_events process_events()
def test_logger_close(): # Regression test to make sure that when closing an application, sys.stderr # no longer points to GlueLogger. app = GlueApplication() app.close() process_events() assert not isinstance(sys.stderr, GlueLogger)
def test_logger_close(): # Regression test to make sure that when closing an application, sys.stderr # no longer points to GlueLogger. app = GlueApplication() app.close() process_events() assert not isinstance(sys.stderr, GlueLogger)
def test_collapse(self): self.viewer.add_data(self.data) image_viewer = self.app.new_data_viewer(ImageViewer) image_viewer.add_data(self.data) self.profile_tools.ui.tabs.setCurrentIndex(2) # First try in pixel coordinates self.viewer.state.x_att = self.data.pixel_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[0.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[15.1, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) self.profile_tools.ui.button_collapse.click() assert isinstance(image_viewer.state.slices[0], AggregateSlice) assert image_viewer.state.slices[0].slice.start == 1 assert image_viewer.state.slices[0].slice.stop == 15 assert image_viewer.state.slices[0].center == 0 assert image_viewer.state.slices[0].function is nanmean # Next, try in world coordinates self.viewer.state.x_att = self.data.world_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[1.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[30.1, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) self.profile_tools.ui.button_collapse.click() assert isinstance(image_viewer.state.slices[0], AggregateSlice) assert image_viewer.state.slices[0].slice.start == 1 assert image_viewer.state.slices[0].slice.stop == 15 assert image_viewer.state.slices[0].center == 0 assert image_viewer.state.slices[0].function is nanmean
def test_collapse(self): self.viewer.add_data(self.data) image_viewer = self.app.new_data_viewer(ImageViewer) image_viewer.add_data(self.data) self.profile_tools.ui.tabs.setCurrentIndex(2) # First try in pixel coordinates self.viewer.state.x_att = self.data.pixel_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[0.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[15.1, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) self.profile_tools.ui.button_collapse.click() assert isinstance(image_viewer.state.slices[0], AggregateSlice) assert image_viewer.state.slices[0].slice.start == 1 assert image_viewer.state.slices[0].slice.stop == 15 assert image_viewer.state.slices[0].center == 0 assert image_viewer.state.slices[0].function is nanmean # Next, try in world coordinates self.viewer.state.x_att = self.data.world_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[1.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[30.1, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) self.profile_tools.ui.button_collapse.click() assert isinstance(image_viewer.state.slices[0], AggregateSlice) assert image_viewer.state.slices[0].slice.start == 1 assert image_viewer.state.slices[0].slice.stop == 15 assert image_viewer.state.slices[0].center == 0 assert image_viewer.state.slices[0].function is nanmean
def test_invisible_subset(self): # Regression test for a bug that caused a subset layer that started # off as invisible to have issues when made visible. We emulate the # initial invisible (but enabled) state by invalidating the cache. self.viewer.add_data(self.image1) self.data_collection.new_subset_group(subset_state=self.image1.id['x'] > 1, label='A') self.viewer.layers[1].visible = False self.viewer.layers[1].image_artist.invalidate_cache() self.viewer.layers[1].redraw() process_events() assert not np.any(self.viewer.layers[1].image_artist._A.mask) self.viewer.layers[1].visible = True assert not np.any(self.viewer.layers[1].image_artist._A.mask)
def test_invisible_subset(self): # Regression test for a bug that caused a subset layer that started # off as invisible to have issues when made visible. We emulate the # initial invisible (but enabled) state by invalidating the cache. self.viewer.add_data(self.image1) self.data_collection.new_subset_group(subset_state=self.image1.id['x'] > 1, label='A') self.viewer.layers[1].visible = False self.viewer.layers[1].image_artist.invalidate_cache() self.viewer.layers[1].redraw() process_events() assert not np.any(self.viewer.layers[1].image_artist._A.mask) self.viewer.layers[1].visible = True assert not np.any(self.viewer.layers[1].image_artist._A.mask)
def test_navigate_sync_image(self): self.viewer.add_data(self.data) self.viewer.toolbar.active_tool = 'solar:pixel_extraction' self.viewer.axes.figure.canvas.draw() process_events() x, y = self.viewer.axes.transData.transform([[1, 2]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) self.viewer.axes.figure.canvas.button_release_event(x, y, 1) assert len(self.data_collection) == 2 derived1 = self.data_collection[1] assert derived1.label == "d1[:,2,1]" assert derived1.shape == (3, ) assert_allclose(derived1['x'], self.data['x'][:, 2, 1]) x, y = self.viewer.axes.transData.transform([[1, 1]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) self.viewer.axes.figure.canvas.button_release_event(x, y, 1) assert len(self.data_collection) == 2 derived2 = self.data_collection[1] assert derived2 is derived1 assert derived2.label == "d1[:,1,1]" assert derived2.shape == (3, ) assert_allclose(derived2['x'], self.data['x'][:, 1, 1]) self.viewer.state.x_att = self.data.pixel_component_ids[0] self.viewer.axes.figure.canvas.draw() process_events() x, y = self.viewer.axes.transData.transform([[1, 0]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) self.viewer.axes.figure.canvas.button_release_event(x, y, 1) assert len(self.data_collection) == 2 derived3 = self.data_collection[1] assert derived3 is not derived1 assert derived3.label == "d1[1,0,:]" assert derived3.shape == (2, ) assert_allclose(derived3['x'], self.data['x'][1, 0, :])
def test_add_helper(self): dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 add_coordinate_link = get_action(link_widget, 'ICRS <-> Galactic') # Add a coordinate link add_coordinate_link.trigger() # Ensure that all events get processed process_events() assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text( ) == 'Link ICRS and Galactic coordinates' assert non_empty_rows_count(link_widget.combos1) == 2 assert non_empty_rows_count(link_widget.combos2) == 2 assert link_widget.combos1.itemAtPosition( 0, 1).widget().currentText() == 'x' assert link_widget.combos1.itemAtPosition( 1, 1).widget().currentText() == 'y' assert link_widget.combos2.itemAtPosition( 0, 1).widget().currentText() == 'a' assert link_widget.combos2.itemAtPosition( 1, 1).widget().currentText() == 'b' dialog.accept() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], ICRS_to_Galactic) assert links[0].cids1[0] is self.data1.id['x'] assert links[0].cids1[1] is self.data1.id['y'] assert links[0].cids2[0] is self.data2.id['a'] assert links[0].cids2[1] is self.data2.id['b']
def test_session_categorical(self, tmpdir): def visible_xaxis_labels(ax): # Due to a bug in Matplotlib the labels returned outside the field # of view may be incorrect: https://github.com/matplotlib/matplotlib/issues/9397 pos = ax.xaxis.get_ticklocs() labels = [tick.get_text() for tick in ax.xaxis.get_ticklabels()] xmin, xmax = ax.get_xlim() return [ labels[i] for i in range(len(pos)) if pos[i] >= xmin and pos[i] <= xmax ] # Regression test for a bug that caused a restored scatter viewer # with a categorical component to not show the categorical labels # as tick labels. filename = tmpdir.join('test_session_categorical.glu').strpath self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.id['z'] process_events() assert visible_xaxis_labels(self.viewer.axes) == ['a', 'b', 'c'] self.session.application.save_session(filename) with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') dc = ga.session.data_collection viewer = ga.viewers[0][0] assert viewer.state.x_att is dc[0].id['z'] assert visible_xaxis_labels(self.viewer.axes) == ['a', 'b', 'c'] ga.close()
def test_basic(self): self.viewer.toolbar.active_tool = 'slice' self.viewer.axes.figure.canvas.draw() process_events() x, y = self.viewer.axes.transData.transform([[0.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[7.2, 6.6]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) process_events() assert len(self.application.tab().subWindowList()) == 1 self.viewer.axes.figure.canvas.key_press_event('enter') process_events() assert len(self.application.tab().subWindowList()) == 2 pv_widget = self.application.tab().subWindowList()[1].widget() assert pv_widget._x.shape == (6, ) assert pv_widget._y.shape == (6, )
def test_indexed_data(capsys): # Make sure that the image viewer works properly with IndexedData objects data_4d = Data(label='hypercube_wcs', x=np.random.random((3, 5, 4, 3)), coords=WCS(naxis=4)) data_2d = IndexedData(data_4d, (2, None, 3, None)) application = GlueApplication() session = application.session hub = session.hub data_collection = session.data_collection data_collection.append(data_4d) data_collection.append(data_2d) viewer = application.new_data_viewer(ImageViewer) viewer.add_data(data_2d) assert viewer.state.x_att is data_2d.pixel_component_ids[1] assert viewer.state.y_att is data_2d.pixel_component_ids[0] assert viewer.state.x_att_world is data_2d.world_component_ids[1] assert viewer.state.y_att_world is data_2d.world_component_ids[0] process_events() application.close() # Some exceptions used to happen during callbacks, and these show up # in stderr but don't interrupt the code, so we make sure here that # nothing was printed to stdout nor stderr. out, err = capsys.readouterr() assert out.strip() == "" assert err.strip() == ""
def test_session_categorical(self, tmpdir): def visible_xaxis_labels(ax): # Due to a bug in Matplotlib the labels returned outside the field # of view may be incorrect: https://github.com/matplotlib/matplotlib/issues/9397 pos = ax.xaxis.get_ticklocs() labels = [tick.get_text() for tick in ax.xaxis.get_ticklabels()] xmin, xmax = ax.get_xlim() return [labels[i] for i in range(len(pos)) if pos[i] >= xmin and pos[i] <= xmax] # Regression test for a bug that caused a restored scatter viewer # with a categorical component to not show the categorical labels # as tick labels. filename = tmpdir.join('test_session_categorical.glu').strpath self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.id['z'] process_events() assert visible_xaxis_labels(self.viewer.axes) == ['a', 'b', 'c'] self.session.application.save_session(filename) with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') dc = ga.session.data_collection viewer = ga.viewers[0][0] assert viewer.state.x_att is dc[0].id['z'] assert visible_xaxis_labels(self.viewer.axes) == ['a', 'b', 'c'] ga.close()
def test_fit_polynomial(self): # TODO: need to deterministically set to polynomial fitter self.viewer.add_data(self.data) self.profile_tools.ui.tabs.setCurrentIndex(1) # First try in pixel coordinates self.viewer.state.x_att = self.data.pixel_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[0.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[15.1, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) assert_allclose(self.profile_tools.rng_mode.state.x_range, (0.9, 15.1)) self.profile_tools.ui.button_fit.click() self.profile_tools.wait_for_fit() # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) process_events() pixel_log = self.profile_tools.text_log.toPlainText().splitlines() assert pixel_log[0] == 'd1' assert pixel_log[1] == 'Coefficients:' assert pixel_log[-2] == '8.000000e+00' assert pixel_log[-1] == '3.500000e+00' self.profile_tools.ui.button_clear.click() assert self.profile_tools.text_log.toPlainText() == '' # Next, try in world coordinates self.viewer.state.x_att = self.data.world_component_ids[0] x, y = self.viewer.axes.transData.transform([[1.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[30.1, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) assert_allclose(self.profile_tools.rng_mode.state.x_range, (1.9, 30.1)) self.profile_tools.ui.button_fit.click() self.profile_tools.wait_for_fit() process_events() world_log = self.profile_tools.text_log.toPlainText().splitlines() assert world_log[0] == 'd1' assert world_log[1] == 'Coefficients:' assert world_log[-2] == '4.000000e+00' assert world_log[-1] == '3.500000e+00'
def test_fit_polynomial(self): # TODO: need to deterministically set to polynomial fitter self.viewer.add_data(self.data) self.profile_tools.ui.tabs.setCurrentIndex(1) # First try in pixel coordinates self.viewer.state.x_att = self.data.pixel_component_ids[0] # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) self.viewer.layers[0].wait() process_events() x, y = self.viewer.axes.transData.transform([[0.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[15.1, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) assert_allclose(self.profile_tools.rng_mode.state.x_range, (0.9, 15.1)) self.profile_tools.ui.button_fit.click() self.profile_tools.wait_for_fit() # Force events to be processed to make sure that the callback functions # for the computation thread are executed (since they rely on signals) process_events() pixel_log = self.profile_tools.text_log.toPlainText().splitlines() assert pixel_log[0] == 'd1' assert pixel_log[1] == 'Coefficients:' assert pixel_log[-2] == '8.000000e+00' assert pixel_log[-1] == '3.500000e+00' self.profile_tools.ui.button_clear.click() assert self.profile_tools.text_log.toPlainText() == '' # Next, try in world coordinates self.viewer.state.x_att = self.data.world_component_ids[0] x, y = self.viewer.axes.transData.transform([[1.9, 4]])[0] self.viewer.axes.figure.canvas.button_press_event(x, y, 1) x, y = self.viewer.axes.transData.transform([[30.1, 4]])[0] self.viewer.axes.figure.canvas.motion_notify_event(x, y, 1) assert_allclose(self.profile_tools.rng_mode.state.x_range, (1.9, 30.1)) self.profile_tools.ui.button_fit.click() self.profile_tools.wait_for_fit() process_events() world_log = self.profile_tools.text_log.toPlainText().splitlines() assert world_log[0] == 'd1' assert world_log[1] == 'Coefficients:' assert world_log[-2] == '4.000000e+00' assert world_log[-1] == '3.500000e+00'
def test_add_helper(self): dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 add_coordinate_link = get_action(link_widget, 'ICRS <-> Galactic') # Add a coordinate link add_coordinate_link.trigger() # Ensure that all events get processed process_events() assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link ICRS and Galactic coordinates' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'a' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b' dialog.accept() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], ICRS_to_Galactic) assert links[0].cids1[0] is self.data1.id['x'] assert links[0].cids1[1] is self.data1.id['y'] assert links[0].cids2[0] is self.data2.id['a'] assert links[0].cids2[1] is self.data2.id['b']
def test_incompatible_subset(): # Regression test for a bug that caused the table to be refreshed in an # infinite loop if incompatible subsets were present. data1 = Data(a=[1, 2, 3, 4, 5], label='test1') data2 = Data(a=[1, 2, 3, 4, 5], label='test2') dc = DataCollection([data1, data2]) gapp = GlueApplication(dc) viewer = gapp.new_data_viewer(TableViewer) viewer.add_data(data1) dc.new_subset_group('test subset', data2.id['a'] > 2) gapp.show() process_events(0.5) with patch.object(viewer.layers[0], '_refresh') as refresh1: with patch.object(viewer.layers[1], '_refresh') as refresh2: process_events(0.5) assert refresh1.call_count == 0 assert refresh2.call_count == 0
def test_close_on_last_layer_remove(self): # regression test for 391 d1 = Data(x=np.random.random((2,) * self.ndim)) d2 = Data(y=np.random.random((2,) * self.ndim)) dc = DataCollection([d1, d2]) app = GlueApplication(dc) w = app.new_data_viewer(self.widget_cls, data=d1) w.add_data(d2) process_events() assert len(app.viewers[0]) == 1 dc.remove(d1) process_events() assert len(app.viewers[0]) == 1 dc.remove(d2) process_events() assert len(app.viewers[0]) == 0 app.close()
def test_close_on_last_layer_remove(self): # regression test for 391 d1 = Data(x=np.random.random((2, ) * self.ndim)) d2 = Data(y=np.random.random((2, ) * self.ndim)) dc = DataCollection([d1, d2]) app = GlueApplication(dc) w = app.new_data_viewer(self.widget_cls, data=d1) w.add_data(d2) process_events() assert len(app.viewers[0]) == 1 dc.remove(d1) process_events() assert len(app.viewers[0]) == 1 dc.remove(d2) process_events() assert len(app.viewers[0]) == 0 app.close()
def test_ui_behavior(self): # This is a bit more detailed test that checks that things update # correctly as we change various settings dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 add_identity_link = get_action(link_widget, 'identity') add_lengths_volume_link = get_action(link_widget, 'lengths_to_volume') # At this point, there should be no links in the main list widget # and nothing on the right. assert link_widget.listsel_current_link.count() == 0 assert link_widget.link_details.text() == '' assert non_empty_rows_count(link_widget.combos1) == 0 assert non_empty_rows_count(link_widget.combos2) == 0 # Let's add an identity link add_identity_link.trigger() # Ensure that all events get processed process_events() # Now there should be one link in the main list and content in the # right hand panel. assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text( ) == 'Link conceptually identical components' assert non_empty_rows_count(link_widget.combos1) == 1 assert non_empty_rows_count(link_widget.combos2) == 1 assert link_widget.combos1.itemAtPosition( 0, 1).widget().currentText() == 'x' assert link_widget.combos2.itemAtPosition( 0, 1).widget().currentText() == 'a' # Let's change the current components for the link link_widget.state.current_link.x = self.data1.id['y'] link_widget.state.current_link.y = self.data2.id['b'] # and make sure the UI gets updated assert link_widget.combos1.itemAtPosition( 0, 1).widget().currentText() == 'y' assert link_widget.combos2.itemAtPosition( 0, 1).widget().currentText() == 'b' # We now add another link of a different type add_lengths_volume_link.trigger() # Ensure that all events get processed process_events() # and make sure the UI has updated assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text( ) == 'Convert between linear measurements and volume' assert non_empty_rows_count(link_widget.combos1) == 3 assert non_empty_rows_count(link_widget.combos2) == 1 assert link_widget.combos1.itemAtPosition( 0, 1).widget().currentText() == 'x' assert link_widget.combos1.itemAtPosition( 1, 1).widget().currentText() == 'y' assert link_widget.combos1.itemAtPosition( 2, 1).widget().currentText() == 'z' assert link_widget.combos2.itemAtPosition( 0, 1).widget().currentText() == 'a' # Try swapping the order of the data, the current link should stay the same link_widget.state.flip_data() assert link_widget.link_details.text( ) == 'Convert between linear measurements and volume' # And flip it back link_widget.state.flip_data() assert link_widget.link_details.text( ) == 'Convert between linear measurements and volume' # Now switch back to the first link link_widget.state.current_link = type( link_widget.state).current_link.get_choices(link_widget.state)[0] # and make sure the UI updates and has preserved the correct settings assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text( ) == 'Link conceptually identical components' assert non_empty_rows_count(link_widget.combos1) == 1 assert non_empty_rows_count(link_widget.combos2) == 1 assert link_widget.combos1.itemAtPosition( 0, 1).widget().currentText() == 'y' assert link_widget.combos2.itemAtPosition( 0, 1).widget().currentText() == 'b' # Next up, we try changing the data link_widget.state.data1 = self.data3 # At this point there should be no links in the list assert link_widget.listsel_current_link.count() == 0 assert link_widget.link_details.text() == '' assert non_empty_rows_count(link_widget.combos1) == 0 assert non_empty_rows_count(link_widget.combos2) == 0 # Add another identity link add_identity_link.trigger() # Ensure that all events get processed process_events() # Now there should be one link in the main list assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text( ) == 'Link conceptually identical components' assert non_empty_rows_count(link_widget.combos1) == 1 assert non_empty_rows_count(link_widget.combos2) == 1 assert link_widget.combos1.itemAtPosition( 0, 1).widget().currentText() == 'i' assert link_widget.combos2.itemAtPosition( 0, 1).widget().currentText() == 'a' # Switch back to the original data link_widget.state.data1 = self.data1 # And check the output is as before assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text( ) == 'Link conceptually identical components' assert non_empty_rows_count(link_widget.combos1) == 1 assert non_empty_rows_count(link_widget.combos2) == 1 assert link_widget.combos1.itemAtPosition( 0, 1).widget().currentText() == 'y' assert link_widget.combos2.itemAtPosition( 0, 1).widget().currentText() == 'b' # Let's now remove this link link_widget.button_remove_link.click() # Ensure that all events get processed process_events() # We should now see the lengths/volume link assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text( ) == 'Convert between linear measurements and volume' assert non_empty_rows_count(link_widget.combos1) == 3 assert non_empty_rows_count(link_widget.combos2) == 1 assert link_widget.combos1.itemAtPosition( 0, 1).widget().currentText() == 'x' assert link_widget.combos1.itemAtPosition( 1, 1).widget().currentText() == 'y' assert link_widget.combos1.itemAtPosition( 2, 1).widget().currentText() == 'z' assert link_widget.combos2.itemAtPosition( 0, 1).widget().currentText() == 'a' dialog.accept() links = self.data_collection.external_links assert len(links) == 2 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_from_ids()[1] is self.data1.id['y'] assert links[0].get_from_ids()[2] is self.data1.id['z'] assert links[0].get_to_id() is self.data2.id['a'] assert isinstance(links[1], ComponentLink) assert links[1].get_from_ids()[0] is self.data3.id['i'] assert links[1].get_to_id() is self.data2.id['a']
def set_status(self, text, color): self.ui.text_status.setText(text) self.ui.text_status.setStyleSheet("color: {0}".format(color)) process_events()
def __init__(self, figure): self.figure = figure process_events() self.start = self.figure.canvas._draw_count
def draw_count(self): process_events() process_events() return self.figure.canvas._draw_count - self.start
def test_aspect_resize(self): # Make sure that the limits are adjusted appropriately when resizing # depending on the aspect ratio mode. Note that we don't add any data # here since it isn't needed for this test. # This test works with Matplotlib 2.0 and 2.2 but not 2.1, hence we # skip it with Matplotlib 2.1 above. # Note that we need to explicitly call draw() below because otherwise # draw_idle is used, which has no guarantee of being effective. # Set initial limits to deterministic values self.viewer.state.aspect = 'auto' self.viewer.state.x_min = 0. self.viewer.state.x_max = 1. self.viewer.state.y_min = 0. self.viewer.state.y_max = 1. self.viewer.state.aspect = 'equal' # Resize events only work if widget is visible self.viewer.show() self.viewer.figure.canvas.draw() process_events(wait=0.1) def limits(viewer): return (viewer.state.x_min, viewer.state.x_max, viewer.state.y_min, viewer.state.y_max) # Set viewer to an initial size and save limits self.viewer.viewer_size = (800, 400) self.viewer.figure.canvas.draw() process_events(wait=0.1) initial_limits = limits(self.viewer) # Change the viewer size, and make sure the limits are adjusted self.viewer.viewer_size = (400, 400) self.viewer.figure.canvas.draw() process_events(wait=0.1) with pytest.raises(AssertionError): assert_allclose(limits(self.viewer), initial_limits) # Now change the viewer size a number of times and make sure if we # return to the original size, the limits match the initial ones. self.viewer.viewer_size = (350, 800) self.viewer.figure.canvas.draw() process_events(wait=0.1) self.viewer.viewer_size = (900, 300) self.viewer.figure.canvas.draw() process_events(wait=0.1) self.viewer.viewer_size = (600, 600) self.viewer.figure.canvas.draw() process_events(wait=0.1) self.viewer.viewer_size = (800, 400) self.viewer.figure.canvas.draw() process_events(wait=0.1) assert_allclose(limits(self.viewer), initial_limits) # Now check that the limits don't change in 'auto' mode self.viewer.state.aspect = 'auto' self.viewer.viewer_size = (900, 300) self.viewer.figure.canvas.draw() process_events(wait=0.1) assert_allclose(limits(self.viewer), initial_limits)
def viewer_count(self): process_events() obj = objgraph.by_type(self.viewer_cls.__name__) return len(obj)
def test_disable_incompatible(self): # Test to make sure that image and image subset layers are disabled if # their pixel coordinates are not compatible with the ones of the # reference data. self.viewer.add_data(self.image1) self.viewer.add_data(self.image2) assert self.viewer.state.reference_data is self.image1 self.data_collection.new_subset_group() process_events() assert len(self.viewer.layers) == 4 # Only the two layers associated with the reference data should be enabled for layer_artist in self.viewer.layers: if layer_artist.layer in (self.image1, self.image1.subsets[0]): assert layer_artist.enabled else: assert not layer_artist.enabled py1, px1 = self.image1.pixel_component_ids py2, px2 = self.image2.pixel_component_ids link1 = LinkSame(px1, px2) self.data_collection.add_link(link1) process_events() # One link isn't enough, second dataset layers are still not enabled for layer_artist in self.viewer.layers: if layer_artist.layer in (self.image1, self.image1.subsets[0]): assert layer_artist.enabled else: assert not layer_artist.enabled link2 = LinkSame(py1, py2) self.data_collection.add_link(link2) process_events() # All layers should now be enabled for layer_artist in self.viewer.layers: assert layer_artist.enabled self.data_collection.remove_link(link2) process_events() # We should now be back to the original situation for layer_artist in self.viewer.layers: if layer_artist.layer in (self.image1, self.image1.subsets[0]): assert layer_artist.enabled else: assert not layer_artist.enabled
def test_table_widget(tmpdir): # Start off by creating a glue application instance with a table viewer and # some data pre-loaded. app = get_qapp() d = Data(a=[1, 2, 3, 4, 5], b=[3.2, 1.2, 4.5, 3.3, 2.2], c=['e', 'b', 'c', 'a', 'f']) dc = DataCollection([d]) gapp = GlueApplication(dc) widget = gapp.new_data_viewer(TableViewer) widget.add_data(d) subset_mode = gapp._session.edit_subset_mode # Create two subsets sg1 = dc.new_subset_group('D <= 3', d.id['a'] <= 3) sg1.style.color = '#aa0000' sg2 = dc.new_subset_group('1 < D < 4', (d.id['a'] > 1) & (d.id['a'] < 4)) sg2.style.color = '#0000cc' model = widget.ui.table.model() # We now check what the data and colors of the table are, and try various # sorting methods to make sure that things are still correct. data = {'a': [1, 2, 3, 4, 5], 'b': [3.2, 1.2, 4.5, 3.3, 2.2], 'c': ['e', 'b', 'c', 'a', 'f']} colors = ['#aa0000', '#380088', '#380088', None, None] check_values_and_color(model, data, colors) model.sort(1, Qt.AscendingOrder) data = {'a': [2, 5, 1, 4, 3], 'b': [1.2, 2.2, 3.2, 3.3, 4.5], 'c': ['b', 'f', 'e', 'a', 'c']} colors = ['#380088', None, '#aa0000', None, '#380088'] check_values_and_color(model, data, colors) model.sort(2, Qt.AscendingOrder) data = {'a': [4, 2, 3, 1, 5], 'b': [3.3, 1.2, 4.5, 3.2, 2.2], 'c': ['a', 'b', 'c', 'e', 'f']} colors = [None, '#380088', '#380088', '#aa0000', None] check_values_and_color(model, data, colors) model.sort(0, Qt.DescendingOrder) data = {'a': [5, 4, 3, 2, 1], 'b': [2.2, 3.3, 4.5, 1.2, 3.2], 'c': ['f', 'a', 'c', 'b', 'e']} colors = [None, None, '#380088', '#380088', '#aa0000'] check_values_and_color(model, data, colors) model.sort(0, Qt.AscendingOrder) # We now modify the subsets using the table. selection = widget.ui.table.selectionModel() widget.toolbar.actions['table:rowselect'].toggle() def press_key(key): event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key, Qt.NoModifier) app.postEvent(widget.ui.table, event) app.processEvents() process_events() # We now use key presses to navigate down to the third row press_key(Qt.Key_Tab) press_key(Qt.Key_Down) press_key(Qt.Key_Down) process_events() indices = selection.selectedRows() # We make sure that the third row is selected assert len(indices) == 1 assert indices[0].row() == 2 # At this point, the subsets haven't changed yet np.testing.assert_equal(d.subsets[0].to_mask(), [1, 1, 1, 0, 0]) np.testing.assert_equal(d.subsets[1].to_mask(), [0, 1, 1, 0, 0]) # We specify that we are editing the second subset, and use a 'not' logical # operation to remove the currently selected line from the second subset. subset_mode.edit_subset = [d.subsets[1]] subset_mode.mode = AndNotMode press_key(Qt.Key_Enter) np.testing.assert_equal(d.subsets[0].to_mask(), [1, 1, 1, 0, 0]) np.testing.assert_equal(d.subsets[1].to_mask(), [0, 1, 0, 0, 0]) # At this point, the selection should be cleared indices = selection.selectedRows() assert len(indices) == 0 # We move to the fourth row and now do an 'or' selection with the first # subset. press_key(Qt.Key_Down) subset_mode.mode = OrMode subset_mode.edit_subset = [d.subsets[0]] press_key(Qt.Key_Enter) np.testing.assert_equal(d.subsets[0].to_mask(), [1, 1, 1, 1, 0]) np.testing.assert_equal(d.subsets[1].to_mask(), [0, 1, 0, 0, 0]) # Finally we move to the fifth row and deselect all subsets so that # pressing enter now creates a new subset. press_key(Qt.Key_Down) subset_mode.mode = ReplaceMode subset_mode.edit_subset = None press_key(Qt.Key_Enter) np.testing.assert_equal(d.subsets[0].to_mask(), [1, 1, 1, 1, 0]) np.testing.assert_equal(d.subsets[1].to_mask(), [0, 1, 0, 0, 0]) np.testing.assert_equal(d.subsets[2].to_mask(), [0, 0, 0, 0, 1]) # Make the color for the new subset deterministic dc.subset_groups[2].style.color = '#bababa' # Now finally check saving and restoring session session_file = tmpdir.join('table.glu').strpath gapp.save_session(session_file) gapp2 = GlueApplication.restore_session(session_file) gapp2.show() d = gapp2.data_collection[0] widget2 = gapp2.viewers[0][0] model2 = widget2.ui.table.model() data = {'a': [1, 2, 3, 4, 5], 'b': [3.2, 1.2, 4.5, 3.3, 2.2], 'c': ['e', 'b', 'c', 'a', 'f']} # Need to take into account new selections above colors = ['#aa0000', '#380088', '#aa0000', "#aa0000", "#bababa"] check_values_and_color(model2, data, colors)
def test_ui_behavior(self): # This is a bit more detailed test that checks that things update # correctly as we change various settings dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 add_identity_link = get_action(link_widget, 'identity') add_lengths_volume_link = get_action(link_widget, 'lengths_to_volume') # At this point, there should be no links in the main list widget # and nothing on the right. assert link_widget.listsel_current_link.count() == 0 assert link_widget.link_details.text() == '' assert link_widget.link_io.itemAt(0) is None # Let's add an identity link add_identity_link.trigger() # Ensure that all events get processed process_events() # Now there should be one link in the main list and content in the # right hand panel. assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a' # Let's change the current components for the link link_widget.state.current_link.x = self.data1.id['y'] link_widget.state.current_link.y = self.data2.id['b'] # and make sure the UI gets updated assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # We now add another link of a different type add_lengths_volume_link.trigger() # Ensure that all events get processed process_events() # and make sure the UI has updated assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Convert between linear measurements and volume' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a' # Now switch back to the first link link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[0] # and make sure the UI updates and has preserved the correct settings assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # Next up, we try changing the data link_widget.state.data1 = self.data3 # At this point there should be no links in the list assert link_widget.listsel_current_link.count() == 0 assert link_widget.link_details.text() == '' assert link_widget.link_io.itemAt(0) is None # Add another identity link add_identity_link.trigger() # Ensure that all events get processed process_events() # Now there should be one link in the main list assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a' # Switch back to the original data link_widget.state.data1 = self.data1 # And check the output is as before assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # Let's now remove this link link_widget.button_remove_link.click() # Ensure that all events get processed process_events() # We should now see the lengths/volume link assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Convert between linear measurements and volume' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a' dialog.accept() links = self.data_collection.external_links assert len(links) == 2 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_from_ids()[1] is self.data1.id['y'] assert links[0].get_from_ids()[2] is self.data1.id['z'] assert links[0].get_to_id() is self.data2.id['a'] assert isinstance(links[1], ComponentLink) assert links[1].get_from_ids()[0] is self.data3.id['i'] assert links[1].get_to_id() is self.data2.id['a']
def test_table_widget(tmpdir): # Start off by creating a glue application instance with a table viewer and # some data pre-loaded. app = get_qapp() d = Data(a=[1, 2, 3, 4, 5], b=[3.2, 1.2, 4.5, 3.3, 2.2], c=['e', 'b', 'c', 'a', 'f']) dc = DataCollection([d]) gapp = GlueApplication(dc) widget = gapp.new_data_viewer(TableViewer) widget.add_data(d) subset_mode = gapp._session.edit_subset_mode # Create two subsets sg1 = dc.new_subset_group('D <= 3', d.id['a'] <= 3) sg1.style.color = '#aa0000' sg2 = dc.new_subset_group('1 < D < 4', (d.id['a'] > 1) & (d.id['a'] < 4)) sg2.style.color = '#0000cc' model = widget.ui.table.model() # We now check what the data and colors of the table are, and try various # sorting methods to make sure that things are still correct. data = { 'a': [1, 2, 3, 4, 5], 'b': [3.2, 1.2, 4.5, 3.3, 2.2], 'c': ['e', 'b', 'c', 'a', 'f'] } colors = ['#aa0000', '#380088', '#380088', None, None] check_values_and_color(model, data, colors) model.sort(1, Qt.AscendingOrder) data = { 'a': [2, 5, 1, 4, 3], 'b': [1.2, 2.2, 3.2, 3.3, 4.5], 'c': ['b', 'f', 'e', 'a', 'c'] } colors = ['#380088', None, '#aa0000', None, '#380088'] check_values_and_color(model, data, colors) model.sort(2, Qt.AscendingOrder) data = { 'a': [4, 2, 3, 1, 5], 'b': [3.3, 1.2, 4.5, 3.2, 2.2], 'c': ['a', 'b', 'c', 'e', 'f'] } colors = [None, '#380088', '#380088', '#aa0000', None] check_values_and_color(model, data, colors) model.sort(0, Qt.DescendingOrder) data = { 'a': [5, 4, 3, 2, 1], 'b': [2.2, 3.3, 4.5, 1.2, 3.2], 'c': ['f', 'a', 'c', 'b', 'e'] } colors = [None, None, '#380088', '#380088', '#aa0000'] check_values_and_color(model, data, colors) model.sort(0, Qt.AscendingOrder) # We now modify the subsets using the table. selection = widget.ui.table.selectionModel() widget.toolbar.actions['table:rowselect'].toggle() def press_key(key): event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key, Qt.NoModifier) app.postEvent(widget.ui.table, event) app.processEvents() process_events() # We now use key presses to navigate down to the third row press_key(Qt.Key_Tab) press_key(Qt.Key_Down) press_key(Qt.Key_Down) process_events() indices = selection.selectedRows() # We make sure that the third row is selected assert len(indices) == 1 assert indices[0].row() == 2 # At this point, the subsets haven't changed yet np.testing.assert_equal(d.subsets[0].to_mask(), [1, 1, 1, 0, 0]) np.testing.assert_equal(d.subsets[1].to_mask(), [0, 1, 1, 0, 0]) # We specify that we are editing the second subset, and use a 'not' logical # operation to remove the currently selected line from the second subset. subset_mode.edit_subset = [d.subsets[1]] subset_mode.mode = AndNotMode press_key(Qt.Key_Enter) np.testing.assert_equal(d.subsets[0].to_mask(), [1, 1, 1, 0, 0]) np.testing.assert_equal(d.subsets[1].to_mask(), [0, 1, 0, 0, 0]) # At this point, the selection should be cleared indices = selection.selectedRows() assert len(indices) == 0 # We move to the fourth row and now do an 'or' selection with the first # subset. press_key(Qt.Key_Down) subset_mode.mode = OrMode subset_mode.edit_subset = [d.subsets[0]] press_key(Qt.Key_Enter) np.testing.assert_equal(d.subsets[0].to_mask(), [1, 1, 1, 1, 0]) np.testing.assert_equal(d.subsets[1].to_mask(), [0, 1, 0, 0, 0]) # Finally we move to the fifth row and deselect all subsets so that # pressing enter now creates a new subset. press_key(Qt.Key_Down) subset_mode.mode = ReplaceMode subset_mode.edit_subset = None press_key(Qt.Key_Enter) np.testing.assert_equal(d.subsets[0].to_mask(), [1, 1, 1, 1, 0]) np.testing.assert_equal(d.subsets[1].to_mask(), [0, 1, 0, 0, 0]) np.testing.assert_equal(d.subsets[2].to_mask(), [0, 0, 0, 0, 1]) # Make the color for the new subset deterministic dc.subset_groups[2].style.color = '#bababa' # Now finally check saving and restoring session session_file = tmpdir.join('table.glu').strpath gapp.save_session(session_file) gapp2 = GlueApplication.restore_session(session_file) gapp2.show() d = gapp2.data_collection[0] widget2 = gapp2.viewers[0][0] model2 = widget2.ui.table.model() data = { 'a': [1, 2, 3, 4, 5], 'b': [3.2, 1.2, 4.5, 3.3, 2.2], 'c': ['e', 'b', 'c', 'a', 'f'] } # Need to take into account new selections above colors = ['#aa0000', '#380088', '#aa0000', "#aa0000", "#bababa"] check_values_and_color(model2, data, colors)
def test_disable_incompatible(self): # Test to make sure that image and image subset layers are disabled if # their pixel coordinates are not compatible with the ones of the # reference data. self.viewer.add_data(self.image1) self.viewer.add_data(self.image2) assert self.viewer.state.reference_data is self.image1 self.data_collection.new_subset_group() process_events() assert len(self.viewer.layers) == 4 # Only the two layers associated with the reference data should be enabled for layer_artist in self.viewer.layers: if layer_artist.layer in (self.image1, self.image1.subsets[0]): assert layer_artist.enabled else: assert not layer_artist.enabled py1, px1 = self.image1.pixel_component_ids py2, px2 = self.image2.pixel_component_ids link1 = LinkSame(px1, px2) self.data_collection.add_link(link1) process_events() # One link isn't enough, second dataset layers are still not enabled for layer_artist in self.viewer.layers: if layer_artist.layer in (self.image1, self.image1.subsets[0]): assert layer_artist.enabled else: assert not layer_artist.enabled link2 = LinkSame(py1, py2) self.data_collection.add_link(link2) process_events() # All layers should now be enabled for layer_artist in self.viewer.layers: assert layer_artist.enabled self.data_collection.remove_link(link2) process_events() # We should now be back to the original situation for layer_artist in self.viewer.layers: if layer_artist.layer in (self.image1, self.image1.subsets[0]): assert layer_artist.enabled else: assert not layer_artist.enabled