class TestOpenSpaceViewer: def setup_method(self, method): self.data1 = 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.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.app = GlueApplication() self.data_collection = self.app.session.data_collection self.data_collection.append(self.data1) self.data_collection.append(self.data2) self.viewer = self.app.new_data_viewer(OpenSpaceDataViewer) def test_add_single_data(self, websocket_server): self.viewer.connect_to_openspace() assert len(websocket_server.messages) == 0 self.viewer.add_data(self.data1) assert len(websocket_server.messages) == 1
def test_table_widget_session_no_subset(tmpdir): # Regression test for a bug that caused table viewers with no subsets to # not be restored correctly and instead raise an exception. app = get_qapp() # noqa 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'], label='test') dc = DataCollection([d]) gapp = GlueApplication(dc) widget = gapp.new_data_viewer(TableViewer) widget.add_data(d) session_file = tmpdir.join('table.glu').strpath gapp.save_session(session_file) gapp2 = GlueApplication.restore_session(session_file) gapp2.show() gapp2.data_collection[0] gapp2.viewers[0][0]
class TestScatter3D: def setup_method(self, method): self.data = Data(x=[1, 2, 3], y=[4, 5, 6], z=[7, 8, 9], label='d1') self.app = GlueApplication() self.app.session.data_collection.append(self.data) self.viewer = self.app.new_data_viewer(VispyScatterViewer) self.viewer.add_data(self.data) for subtool in self.viewer.toolbar.tools['save'].subtools: if subtool.tool_id == 'save:plotly3d': self.tool = subtool break else: raise Exception("Could not find save:plotly2d tool in viewer") def teardown_method(self, method): self.viewer.close(warn=False) self.viewer = None self.app.close() self.app = None def test_default(self, tmpdir): output_file = tmpdir.join('test.html').strpath with patch('qtpy.compat.getsavefilename') as fd: fd.return_value = output_file, 'html' with patch.object(SaveHoverDialog, 'exec_', auto_accept()): self.tool.activate() assert os.path.exists(output_file)
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_single_draw_call_on_create(self): d = Data(x=np.random.random((2,) * self.ndim)) dc = DataCollection([d]) app = GlueApplication(dc) try: from glue.viewers.common.qt.mpl_widget import MplCanvas draw = MplCanvas.draw MplCanvas.draw = MagicMock() app.new_data_viewer(self.widget_cls, data=d) # each Canvas instance gives at most 1 draw call selfs = [c[0][0] for c in MplCanvas.draw.call_arg_list] assert len(set(selfs)) == len(selfs) finally: MplCanvas.draw = draw
def test_state_save_with_data_layers(): app = GlueApplication() dc = app.data_collection d = Data(x=[1, 2, 3], label='test') dc.append(d) w = app.new_data_viewer(viewer._widget_cls) w.add_data(d) check_clone_app(app)
def test_single_draw_call_on_create(self): d = Data(x=np.random.random((2,) * self.ndim)) dc = DataCollection([d]) app = GlueApplication(dc) try: from glue.viewers.matplotlib.qt.widget import MplCanvas draw = MplCanvas.draw MplCanvas.draw = MagicMock() app.new_data_viewer(self.widget_cls, data=d) # each Canvas instance gives at most 1 draw call selfs = [c[0][0] for c in MplCanvas.draw.call_arg_list] assert len(set(selfs)) == len(selfs) finally: MplCanvas.draw = draw
def test_save_load(self): app = GlueApplication(session=self.session) w = app.new_data_viewer(self.viewer._viewer_cls) v = w._coordinator roi = None s = CustomSubsetState(v, roi) app.data_collection.new_subset_group(subset_state=s, label='test') app2 = clone(app) s2 = app2.data_collection[0].subsets[0].subset_state assert_array_equal(s2.to_mask(self.data), [False, True, True])
def test_cube(self): d = core.Data(label='cube', x=np.zeros((2, 2, 2))) dc = core.DataCollection([d]) app = GlueApplication(dc) w = app.new_data_viewer(ImageWidget, d) w.slice = ('x', 'y', 1) assert w.slice == ('x', 'y', 1) c = self.check_clone(app) w2 = c.viewers[0][0] assert w2.ui.slice.slice == w.slice
def test_two_custom_viewer_classes(): class MyWidget1(CustomViewer): text_box1_Widget1 = '_Hello' def setup(self, text_box1_Widget1): pass class MyWidget2(CustomViewer): text_box1_Widget2 = '_Hello' text_box2_Widget2 = '_world' def setup(self, text_box1_Widget2, text_box2_Widget2): pass app = GlueApplication() dc = app.data_collection d = Data(x=[1, 2, 3], label='test') dc.append(d) app.new_data_viewer(MyWidget1._widget_cls) app.new_data_viewer(MyWidget2._widget_cls)
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) with patch.object(self.widget_cls, 'close') as close: w = app.new_data_viewer(self.widget_cls, data=d1) w.add_data(d2) dc.remove(d1) dc.remove(d2) assert close.call_count >= 1
def setup_class(self): data = Data(x=[1, 2, 3], y=[2, 3, 4], label='data') dc = DataCollection([data]) app = GlueApplication(dc) data.style.color = '#000000' v = app.new_data_viewer(HistogramWidget, data=data) v.component = data.id['y'] v.xmin = 0 v.xmax = 10 v.bins = 20 self.args, self.kwargs = build_plotly_call(app)
class TestDataTableModel(): def setup_method(self, method): self.gapp = GlueApplication() self.viewer = self.gapp.new_data_viewer(TableViewer) self.data = Data(x=[1, 2, 3, 4], y=[2, 3, 4, 5]) self.gapp.data_collection.append(self.data) self.viewer.add_data(self.data) self.model = DataTableModel(self.viewer) def teardown_method(self, method): self.gapp.close() self.gapp = None def test_column_count(self): assert self.model.columnCount() == 2 def test_column_count_hidden(self): self.model.show_coords = True assert self.model.columnCount() == 3 def test_header_data(self): for i, c in enumerate(self.data.main_components): result = self.model.headerData(i, Qt.Horizontal, Qt.DisplayRole) assert result == c.label for i in range(self.data.size): result = self.model.headerData(i, Qt.Vertical, Qt.DisplayRole) assert result == str(i) def test_row_count(self): assert self.model.rowCount() == 4 def test_data(self): for i, c in enumerate(self.data.main_components): for j in range(self.data.size): idx = self.model.index(j, i) result = self.model.data(idx, Qt.DisplayRole) assert float(result) == self.data[c, j] @pytest.mark.xfail def test_data_2d(self): self.data = Data(x=[[1, 2], [3, 4]], y=[[2, 3], [4, 5]]) self.model = DataTableModel(self.data) for i, c in enumerate(self.data.main_components): for j in range(self.data.size): idx = self.model.index(j, i) result = self.model.data(idx, Qt.DisplayRole) assert float(result) == self.data[c].ravel()[j]
class TestDataTableModel(): def setup_method(self, method): self.gapp = GlueApplication() self.viewer = self.gapp.new_data_viewer(TableViewer) self.data = Data(x=[1, 2, 3, 4], y=[2, 3, 4, 5]) self.gapp.data_collection.append(self.data) self.viewer.add_data(self.data) self.model = DataTableModel(self.viewer) def teardown_method(self, method): self.gapp.close() self.gapp = None def test_column_count(self): assert self.model.columnCount() == 2 def test_column_count_hidden(self): self.model.show_coords = True assert self.model.columnCount() == 4 def test_header_data(self): for i, c in enumerate(self.data.main_components): result = self.model.headerData(i, Qt.Horizontal, Qt.DisplayRole) assert result == c.label for i in range(self.data.size): result = self.model.headerData(i, Qt.Vertical, Qt.DisplayRole) assert result == str(i) def test_row_count(self): assert self.model.rowCount() == 4 def test_data(self): for i, c in enumerate(self.data.main_components): for j in range(self.data.size): idx = self.model.index(j, i) result = self.model.data(idx, Qt.DisplayRole) assert float(result) == self.data[c, j] @pytest.mark.xfail def test_data_2d(self): self.data = Data(x=[[1, 2], [3, 4]], y=[[2, 3], [4, 5]]) self.model = DataTableModel(self.data) for i, c in enumerate(self.data.main_components): for j in range(self.data.size): idx = self.model.index(j, i) result = self.model.data(idx, Qt.DisplayRole) assert float(result) == self.data[c].ravel()[j]
def test_rotate(capsys): app = GlueApplication() viewer = app.new_data_viewer(VispyScatterViewer) viewer.toolbar.actions['vispy:rotate'].toggle() assert viewer.toolbar.active_tool.tool_id == 'vispy:rotate' viewer.toolbar.actions['vispy:rotate'].toggle() assert viewer.toolbar.active_tool is None out, err = capsys.readouterr() assert out.strip() == "" assert err.strip() == "" app.close()
def test_options_widget(self): d1 = Data(x=np.random.random((2, ) * self.ndim)) d2 = Data(x=np.random.random((2, ) * self.ndim)) dc = DataCollection([d1, d2]) app = GlueApplication(dc) w = app.new_data_viewer(self.widget_cls, data=d1) w.state.x_stretch = 0.5 w.state.y_stretch = 1.0 w.state.z_stretch = 2.0 w.state.x_min = -0.1 w.state.x_max = 10.1 w.state.y_min = 0.1 w.state.y_max = 10.9 w.state.z_min = 0.2 w.state.z_max = 10.8 w.state.visible_axes = False
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_options_widget(self): d1 = Data(x=np.random.random((2,) * self.ndim)) d2 = Data(x=np.random.random((2,) * self.ndim)) dc = DataCollection([d1, d2]) app = GlueApplication(dc) w = app.new_data_viewer(self.widget_cls, data=d1) w.state.x_stretch = 0.5 w.state.y_stretch = 1.0 w.state.z_stretch = 2.0 w.state.x_min = -0.1 w.state.x_max = 10.1 w.state.y_min = 0.1 w.state.y_max = 10.9 w.state.z_min = 0.2 w.state.z_max = 10.8 w.state.visible_axes = False
def test_add_subset(): # Regression test for a bug that occurred when adding a subset # directly to the table viewer. data1 = Data(a=[1, 2, 3, 4, 5], label='test1') data2 = Data(a=[1, 2, 3, 4, 5], label='test2') dc = DataCollection([data1, data2]) dc.new_subset_group('test subset 1', data1.id['a'] > 2) gapp = GlueApplication(dc) viewer = gapp.new_data_viewer(TableViewer) viewer.add_subset(data1.subsets[0]) assert len(viewer.state.layers) == 2 assert not viewer.state.layers[0].visible assert viewer.state.layers[1].visible dc.new_subset_group('test subset 2', data1.id['a'] <= 2) assert len(viewer.state.layers) == 3 assert not viewer.state.layers[0].visible assert viewer.state.layers[1].visible assert viewer.state.layers[2].visible viewer.remove_subset(data1.subsets[1]) assert len(viewer.state.layers) == 2 assert not viewer.state.layers[0].visible assert viewer.state.layers[1].visible viewer.add_subset(data1.subsets[1]) assert len(viewer.state.layers) == 3 assert not viewer.state.layers[0].visible assert viewer.state.layers[1].visible assert viewer.state.layers[2].visible with pytest.raises(ValueError) as exc: viewer.add_subset(data2.subsets[1]) assert exc.value.args[ 0] == 'subset parent data does not match existing table data'
def test_change_components(): # Regression test for a bug that caused table viewers to not update when # adding/removing components. app = get_qapp() # noqa 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'], label='test') dc = DataCollection([d]) gapp = GlueApplication(dc) viewer = gapp.new_data_viewer(TableViewer) viewer.add_data(d) data_changed = MagicMock() viewer.model.dataChanged.connect(data_changed) # layoutChanged needs to be emitted for the new/removed columns to be # registered (dataChanged is not enough) layout_changed = MagicMock() viewer.model.layoutChanged.connect(layout_changed) assert data_changed.call_count == 0 assert layout_changed.call_count == 0 viewer.model.columnCount() == 2 d.add_component([3, 4, 5, 6, 2], 'z') assert data_changed.call_count == 1 assert layout_changed.call_count == 1 viewer.model.columnCount() == 3 d.remove_component(d.id['z']) assert data_changed.call_count == 2 assert layout_changed.call_count == 2 viewer.model.columnCount() == 2
def test_add_viewer(self, tmpdir): d1 = Data(x=np.random.random((2,) * self.ndim)) d2 = Data(x=np.random.random((2,) * self.ndim)) dc = DataCollection([d1, d2]) app = GlueApplication(dc) w = app.new_data_viewer(self.widget_cls, data=d1) w.viewer_size = (300, 400) filename = tmpdir.join('session.glu').strpath app.save_session(filename, include_data=True) app2 = GlueApplication.restore_session(filename) # test session is restored correctly for viewer in app2.viewers: assert viewer[0].viewer_size == (300, 400) app.close() app2.close()
def test_add_viewer(self, tmpdir): d1 = Data(x=np.random.random((2, ) * self.ndim)) d2 = Data(x=np.random.random((2, ) * self.ndim)) dc = DataCollection([d1, d2]) app = GlueApplication(dc) w = app.new_data_viewer(self.widget_cls, data=d1) w.viewer_size = (300, 400) filename = tmpdir.join('session.glu').strpath app.save_session(filename, include_data=True) app2 = GlueApplication.restore_session(filename) # test session is restored correctly for viewer in app2.viewers: assert viewer[0].viewer_size == (300, 400) app.close() app2.close()
def setup_method(self, method): LinkSame = core.link_helpers.LinkSame d = core.Data(label='im', x=[[1, 2], [2, 3]], y=[[2, 3], [4, 5]]) d2 = core.Data(label='cat', x=[0, 1, 0, 1], y=[0, 0, 1, 1], z=[1, 2, 3, 4]) dc = core.DataCollection([d, d2]) dc.add_link(LinkSame(d.get_pixel_component_id(0), d2.id['x'])) dc.add_link(LinkSame(d.get_pixel_component_id(1), d2.id['y'])) app = GlueApplication(dc) w = app.new_data_viewer(ImageWidget, data=d) self.d = d self.app = app self.w = w self.d2 = d2 self.dc = dc
def test_change_components(): # Regression test for a bug that caused table viewers to not update when # adding/removing components. For now, this does not work with Qt 5.7. app = get_qapp() # noqa 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'], label='test') dc = DataCollection([d]) gapp = GlueApplication(dc) viewer = gapp.new_data_viewer(TableViewer) viewer.add_data(d) data_changed = MagicMock() viewer.model.dataChanged.connect(data_changed) # layoutChanged needs to be emitted for the new/removed columns to be # registered (dataChanged is not enough) layout_changed = MagicMock() viewer.model.layoutChanged.connect(layout_changed) assert data_changed.call_count == 0 assert layout_changed.call_count == 0 viewer.model.columnCount() == 2 d.add_component([3, 4, 5, 6, 2], 'z') assert data_changed.call_count == 1 assert layout_changed.call_count == 1 viewer.model.columnCount() == 3 d.remove_component(d.id['z']) assert data_changed.call_count == 2 assert layout_changed.call_count == 2 viewer.model.columnCount() == 2
class TestPVSliceTool(object): def setup_method(self, method): self.cube = Data(label='cube', x=np.arange(1000).reshape((5, 10, 20))) self.application = GlueApplication() self.application.data_collection.append(self.cube) self.viewer = self.application.new_data_viewer(ImageViewer) self.viewer.add_data(self.cube) def teardown_method(self, method): self.viewer.close() self.viewer = None self.application.close() self.application = None @requires_astropy @requires_scipy 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_table_with_dask_column(): da = pytest.importorskip('dask.array') app = get_qapp() d = Data(d=da.asarray([1, 2, 3, 4, 5]), e=np.arange(5) + 1) dc = DataCollection([d]) gapp = GlueApplication(dc) widget = gapp.new_data_viewer(TableViewer) widget.add_data(d) sg1 = dc.new_subset_group('D <= 3', d.id['d'] <= 3) sg1.style.color = '#aa0000' sg2 = dc.new_subset_group('1 < E < 4', (d.id['e'] > 1) & (d.id['e'] < 4)) sg2.style.color = '#0000cc' assert widget.state.layers[0].visible assert widget.state.layers[1].visible assert widget.state.layers[2].visible 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 = {'d': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5]} colors = ['#aa0000', '#380088', '#380088', None, None] check_values_and_color(model, data, colors) widget.state.layers[2].visible = False colors = ['#aa0000', '#aa0000', '#aa0000', None, None] check_values_and_color(model, data, colors)
def test_reset(tmpdir, capsys): app = GlueApplication() viewer = app.new_data_viewer(VispyScatterViewer) data = Data(x=[1, 2, 3], label='Data') app.data_collection.append(data) app.show() viewer.add_data(data) assert viewer.state.x_min == 1. assert viewer.state.y_min == 1. assert viewer.state.z_min == 1. assert viewer.state.x_max == 3. assert viewer.state.y_max == 3. assert viewer.state.z_max == 3. viewer.state.x_min = 2 viewer.state.y_min = 3 viewer.state.z_min = 5 viewer.state.x_max = 6 viewer.state.y_max = 7 viewer.state.z_max = 8 viewer.toolbar.actions['vispy:reset'].trigger() assert viewer.state.x_min == 1. assert viewer.state.y_min == 1. assert viewer.state.z_min == 1. assert viewer.state.x_max == 3. assert viewer.state.y_max == 3. assert viewer.state.z_max == 3. out, err = capsys.readouterr() assert out.strip() == "" assert err.strip() == "" app.close()
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_table_title(): app = get_qapp() # noqa 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) assert viewer.windowTitle() == 'Table' viewer.add_data(data1) assert viewer.windowTitle() == 'Table: test1' viewer.add_data(data2) assert viewer.windowTitle() == 'Table: test2'
def test_viewer_size(self, tmpdir): # regression test for #781 # viewers were not restored with the right size d1 = Data(x=np.random.random((2,) * self.ndim)) d2 = Data(x=np.random.random((2,) * self.ndim)) dc = DataCollection([d1, d2]) app = GlueApplication(dc) w = app.new_data_viewer(self.widget_cls, data=d1) w.viewer_size = (300, 400) filename = tmpdir.join('session.glu').strpath app.save_session(filename, include_data=True) app2 = GlueApplication.restore_session(filename) for viewer in app2.viewers: assert viewer[0].viewer_size == (300, 400) app.close() app2.close()
def test_close_on_last_layer_remove(self): # regression test for 391 # Note: processEvents is needed for things to work correctly with PySide2 qtapp = get_qapp() 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) qtapp.processEvents() assert len(app.viewers[0]) == 1 dc.remove(d1) qtapp.processEvents() assert len(app.viewers[0]) == 1 dc.remove(d2) qtapp.processEvents() assert len(app.viewers[0]) == 0 app.close()
def test_save(tmpdir, capsys): app = GlueApplication() viewer = app.new_data_viewer(VispyScatterViewer) data = Data(x=[1, 2, 3], label='Data') app.data_collection.append(data) app.show() viewer.add_data(data) filename = tmpdir.join('test.png').strpath with patch('qtpy.compat.getsavefilename') as fd: fd.return_value = filename, 'png' viewer.toolbar.tools['save'].subtools[0].activate() assert os.path.exists(filename) out, err = capsys.readouterr() assert out.strip() == "" assert err.strip() == "" app.close()
def test_close_on_last_layer_remove(self): # regression test for 391 # Note: processEvents is needed for things to work correctly with PySide2 qtapp = get_qapp() 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) qtapp.processEvents() assert len(app.viewers[0]) == 1 dc.remove(d1) qtapp.processEvents() assert len(app.viewers[0]) == 1 dc.remove(d2) qtapp.processEvents() assert len(app.viewers[0]) == 0 app.close()
def test_record(tmpdir, capsys): app = GlueApplication() viewer = app.new_data_viewer(VispyScatterViewer) filename = tmpdir.join('test.gif').strpath with patch('qtpy.compat.getsavefilename') as fd: fd.return_value = filename, 'gif' viewer.toolbar.actions['vispy:record'].toggle() assert viewer.toolbar.active_tool.tool_id == 'vispy:record' viewer.toolbar.actions['vispy:record'].toggle() assert viewer.toolbar.active_tool is None assert os.path.exists(filename) out, err = capsys.readouterr() assert out.strip() == "" assert err.strip() == "" app.close()
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
class TestPlotly(object): def setup_method(self, method): d = Data(x=[1, 2, 3], y=[2, 3, 4], z=['a', 'b', 'c'], label='data') dc = DataCollection([d]) self.app = GlueApplication(dc) self.data = d def teardown_method(self, method): self.app.close() self.app = None def test_scatter(self): d = self.data d.style.markersize = 6 d.style.color = '#ff0000' d.style.alpha = .4 viewer = self.app.new_data_viewer(ScatterViewer, data=d) viewer.state.x_att = d.id['y'] viewer.state.y_att = d.id['x'] args, kwargs = build_plotly_call(self.app) data = args[0]['data'][0] expected = dict(type='scatter', mode='markers', name=d.label, marker=dict(size=6, color='rgba(255, 0, 0, 0.4)', symbol='circle')) for k, v in expected.items(): assert data[k] == v np.testing.assert_array_equal(data['x'], d['y']) np.testing.assert_array_equal(data['y'], d['x']) layout = args[0]['layout'] assert layout['showlegend'] viewer.close() def test_scatter_subset(self): d = self.data s = d.new_subset(label='subset') s.subset_state = d.id['x'] > 1 s.style.marker = 's' viewer = self.app.new_data_viewer(ScatterViewer, data=d) viewer.state.x_att = d.id['x'] viewer.state.y_att = d.id['x'] args, kwargs = build_plotly_call(self.app) data = args[0]['data'] # check that subset is on Top assert len(data) == 2 assert data[0]['name'] == 'data' assert data[1]['name'] == 'subset' viewer.close() def test_axes(self): viewer = self.app.new_data_viewer(ScatterViewer, data=self.data) viewer.state.x_log = True viewer.state.x_min = 10 viewer.state.x_max = 100 viewer.state.x_att = self.data.id['x'] viewer.state.y_log = False viewer.state.y_min = 2 viewer.state.y_max = 4 viewer.state.y_att = self.data.id['y'] args, kwargs = build_plotly_call(self.app) xaxis = dict(type='log', rangemode='normal', range=[1, 2], title='x', zeroline=False) yaxis = dict(type='linear', rangemode='normal', range=[2, 4], title='y', zeroline=False) layout = args[0]['layout'] for k, v in layout['xaxis'].items(): assert xaxis.get(k, v) == v for k, v in layout['yaxis'].items(): assert yaxis.get(k, v) == v viewer.close() def test_histogram(self): d = self.data d.style.color = '#000000' viewer = self.app.new_data_viewer(HistogramViewer, data=d) viewer.state.x_att = d.id['y'] viewer.state.hist_x_min = 0 viewer.state.hist_x_max = 10 viewer.state.hist_n_bin = 20 args, kwargs = build_plotly_call(self.app) expected = dict( name='data', type='bar', marker=dict( color='rgba(0, 0, 0, {0:0.1f})'.format(float(settings.DATA_ALPHA)) ), ) data = args[0]['data'] for k in expected: assert expected[k] == data[0][k] assert args[0]['layout']['barmode'] == 'overlay' viewer.close() def test_scatter_categorical(self): viewer = self.app.new_data_viewer(ScatterViewer, data=self.data) viewer.state.x_att = self.data.id['x'] viewer.state.y_att = self.data.id['z'] args, kwargs = build_plotly_call(self.app) xaxis = dict(type='linear', rangemode='normal', range=[0.92, 3.08], title='x', zeroline=False) yaxis = dict(type='linear', rangemode='normal', range=[-0.62, 2.62], title='z', zeroline=False) layout = args[0]['layout'] for k, v in layout['xaxis'].items(): assert xaxis.get(k, v) == v for k, v in layout['yaxis'].items(): assert yaxis.get(k, v) == v viewer.close() def test_histogram_categorical(self): viewer = self.app.new_data_viewer(HistogramViewer, data=self.data) viewer.state.x_att = self.data.id['z'] args, kwargs = build_plotly_call(self.app) xaxis = dict(type='linear', rangemode='normal', range=[-0.5, 2.5], title='z', zeroline=False) yaxis = dict(type='linear', rangemode='normal', range=[0, 1.05], title='', zeroline=False) layout = args[0]['layout'] for k, v in layout['xaxis'].items(): assert xaxis.get(k, v) == v for k, v in layout['yaxis'].items(): assert yaxis.get(k, v) == v viewer.close()
class TestDendrogramViewer(): def setup_method(self, method): self.data = Data(label='d1', parent=[-1, 0, 1, 1], height=[1.3, 2.2, 3.2, 4.4]) self.app = GlueApplication() self.session = self.app.session self.hub = self.session.hub self.data_collection = self.session.data_collection self.data_collection.append(self.data) self.viewer = self.app.new_data_viewer(DendrogramViewer) self.data_collection.register_to_hub(self.hub) self.viewer.register_to_hub(self.hub) def teardown_method(self, method): self.viewer.close() self.viewer = None self.app.close() self.app = None def test_point_select(self): self.viewer.add_data(self.data) # By default selecting a structure selects all substructures roi = PointROI(0.5, 1.5) self.viewer.apply_roi(roi) assert len(self.data.subsets) == 1 mask1 = self.data.subsets[0].subset_state.to_mask(self.data) assert_equal(mask1, [0, 1, 1, 1]) # But this option can be turned off self.viewer.state.select_substruct = False self.viewer.apply_roi(roi) assert len(self.data.subsets) == 1 mask1 = self.data.subsets[0].subset_state.to_mask(self.data) assert_equal(mask1, [0, 1, 0, 0]) self.viewer.state.select_substruct = True # Try selecting a leaf roi = PointROI(0.2, 2.8) self.viewer.apply_roi(roi) assert len(self.data.subsets) == 1 mask1 = self.data.subsets[0].subset_state.to_mask(self.data) assert_equal(mask1, [0, 0, 1, 0]) # Try selecting another leaf roi = PointROI(0.7, 2.8) self.viewer.apply_roi(roi) assert len(self.data.subsets) == 1 mask1 = self.data.subsets[0].subset_state.to_mask(self.data) assert_equal(mask1, [0, 0, 0, 1]) def test_attribute_change_triggers_relayout(self): self.data.add_component([4, 5, 6, 7], 'flux') self.viewer.add_data(self.data) l = self.viewer.state._layout self.viewer.state.height_att = self.data.id['flux'] assert self.viewer.state._layout is not l
class TestScatterViewer(object): def setup_method(self, method): self.data = 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_2d = Data(label='d2', a=[[1, 2], [3, 4]], b=[[5, 6], [7, 8]], x=[[3, 5], [5.4, 1]], y=[[1.2, 4], [7, 8]]) self.app = GlueApplication() self.session = self.app.session self.hub = self.session.hub self.data_collection = self.session.data_collection self.data_collection.append(self.data) self.data_collection.append(self.data_2d) self.viewer = self.app.new_data_viewer(ScatterViewer) def teardown_method(self, method): self.viewer.close() self.viewer = None self.app.close() self.app = None def test_basic(self): viewer_state = self.viewer.state # Check defaults when we add data self.viewer.add_data(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' assert combo_as_string(self.viewer.options_widget().ui.combosel_y_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' assert viewer_state.x_att is self.data.id['x'] assert_allclose(viewer_state.x_min, -1.1 - 0.18) assert_allclose(viewer_state.x_max, 3.4 + 0.18) assert viewer_state.y_att is self.data.id['y'] assert_allclose(viewer_state.y_min, 3.2 - 0.012) assert_allclose(viewer_state.y_max, 3.5 + 0.012) assert not viewer_state.x_log assert not viewer_state.y_log assert len(viewer_state.layers) == 1 # Change to categorical component and check new values viewer_state.y_att = self.data.id['z'] assert viewer_state.x_att is self.data.id['x'] assert_allclose(viewer_state.x_min, -1.1 - 0.18) assert_allclose(viewer_state.x_max, 3.4 + 0.18) assert viewer_state.y_att is self.data.id['z'] assert_allclose(viewer_state.y_min, -0.5 - 0.12) assert_allclose(viewer_state.y_max, 2.5 + 0.12) assert not viewer_state.x_log assert not viewer_state.y_log def test_flip(self): viewer_state = self.viewer.state self.viewer.add_data(self.data) assert_allclose(viewer_state.x_min, -1.1 - 0.18) assert_allclose(viewer_state.x_max, 3.4 + 0.18) self.viewer.options_widget().button_flip_x.click() assert_allclose(viewer_state.x_max, -1.1 - 0.18) assert_allclose(viewer_state.x_min, 3.4 + 0.18) assert_allclose(viewer_state.y_min, 3.2 - 0.012) assert_allclose(viewer_state.y_max, 3.5 + 0.012) self.viewer.options_widget().button_flip_y.click() assert_allclose(viewer_state.y_max, 3.2 - 0.012) assert_allclose(viewer_state.y_min, 3.5 + 0.012) def test_remove_data(self): self.viewer.add_data(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' assert combo_as_string(self.viewer.options_widget().ui.combosel_y_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' self.data_collection.remove(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == '' assert combo_as_string(self.viewer.options_widget().ui.combosel_y_att) == '' def test_update_component_updates_title(self): self.viewer.add_data(self.data) assert self.viewer.windowTitle() == '2D Scatter' self.viewer.state.x_att = self.data.id['y'] assert self.viewer.windowTitle() == '2D Scatter' def test_combo_updates_with_component_add(self): self.viewer.add_data(self.data) self.data.add_component([3, 4, 1, 2], 'a') assert self.viewer.state.x_att is self.data.id['x'] assert self.viewer.state.y_att is self.data.id['y'] assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:z:a:Coordinate components:Pixel Axis 0 [x]:World 0' assert combo_as_string(self.viewer.options_widget().ui.combosel_y_att) == 'Main components:x:y:z:a:Coordinate components:Pixel Axis 0 [x]:World 0' def test_nonnumeric_first_component(self): # regression test for #208. Shouldn't complain if # first component is non-numerical data = core.Data() data.add_component(['a', 'b', 'c'], label='c1') data.add_component([1, 2, 3], label='c2') self.data_collection.append(data) self.viewer.add_data(data) def test_apply_roi(self): self.viewer.add_data(self.data) roi = RectangularROI(0, 3, 3.25, 3.45) assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert len(self.data.subsets) == 1 assert_allclose(self.data.subsets[0].to_mask(), [0, 1, 0, 0]) state = self.data.subsets[0].subset_state assert isinstance(state, RoiSubsetState) def test_apply_roi_categorical(self): viewer_state = self.viewer.state self.viewer.add_data(self.data) viewer_state.y_att = self.data.id['z'] roi = RectangularROI(0, 3, -0.4, 0.3) assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert len(self.data.subsets) == 1 assert_allclose(self.data.subsets[0].to_mask(), [0, 0, 0, 1]) state = self.data.subsets[0].subset_state assert isinstance(state, AndState) def test_apply_roi_empty(self): # Make sure that doing an ROI selection on an empty viewer doesn't # produce error messsages roi = XRangeROI(-0.2, 0.1) self.viewer.apply_roi(roi) def test_axes_labels(self): viewer_state = self.viewer.state self.viewer.add_data(self.data) assert self.viewer.axes.get_xlabel() == 'x' assert self.viewer.axes.get_ylabel() == 'y' viewer_state.x_log = True assert self.viewer.axes.get_xlabel() == 'Log x' assert self.viewer.axes.get_ylabel() == 'y' viewer_state.x_att = self.data.id['y'] assert self.viewer.axes.get_xlabel() == 'y' assert self.viewer.axes.get_ylabel() == 'y' viewer_state.y_log = True assert self.viewer.axes.get_xlabel() == 'y' assert self.viewer.axes.get_ylabel() == 'Log y' def test_component_replaced(self): # regression test for 508 - if a component ID is replaced, we should # make sure that the component ID is selected if the old component ID # was selected self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.id['x'] test = ComponentID('test') self.data.update_id(self.viewer.state.x_att, test) assert self.viewer.state.x_att is test assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:test:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' def test_nan_component(self): # regression test for case when all values are NaN in a component data = core.Data() data.add_component([np.nan, np.nan, np.nan], label='c1') self.data_collection.append(data) self.viewer.add_data(data) def test_density_map(self): kwargs = dict(range=[(-5, 5), (-5, 5)], bins=(2, 2)) self.viewer.add_data(self.data) self.viewer.state.layers[0].points_mode = 'auto' assert self.viewer.layers[0].state.compute_density_map(**kwargs).sum() == 0 self.viewer.state.layers[0].points_mode = 'density' assert self.viewer.layers[0].state.compute_density_map(**kwargs).sum() == 4 self.viewer.state.layers[0].points_mode = 'markers' assert self.viewer.layers[0].state.compute_density_map(**kwargs).sum() == 0 def test_density_map_color(self): # Regression test to make sure things don't crash when changing # back to markers if the color mode is cmap self.viewer.add_data(self.data) self.viewer.state.layers[0].points_mode = 'density' self.viewer.state.layers[0].cmap_mode = 'Linear' self.viewer.state.layers[0].size_mode = 'Linear' self.viewer.state.layers[0].points_mode = 'markers' self.viewer.state.layers[0].points_mode = 'density' @pytest.mark.parametrize('protocol', [0, 1]) def test_session_back_compat(self, protocol): filename = os.path.join(DATA, 'scatter_v{0}.glu'.format(protocol)) with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') dc = ga.session.data_collection assert len(dc) == 1 assert dc[0].label == 'basic' viewer1 = ga.viewers[0][0] assert len(viewer1.state.layers) == 3 assert viewer1.state.x_att is dc[0].id['a'] assert viewer1.state.y_att is dc[0].id['b'] assert_allclose(viewer1.state.x_min, -1.04) assert_allclose(viewer1.state.x_max, 1.04) assert_allclose(viewer1.state.y_min, 1.98) assert_allclose(viewer1.state.y_max, 3.02) assert not viewer1.state.x_log assert not viewer1.state.y_log assert viewer1.state.layers[0].visible assert viewer1.state.layers[1].visible assert viewer1.state.layers[2].visible viewer2 = ga.viewers[0][1] assert len(viewer2.state.layers) == 3 assert viewer2.state.x_att is dc[0].id['a'] assert viewer2.state.y_att is dc[0].id['c'] assert_allclose(viewer2.state.x_min, 9.5e-6) assert_allclose(viewer2.state.x_max, 1.05) assert_allclose(viewer2.state.y_min, 0.38) assert_allclose(viewer2.state.y_max, 5.25) assert viewer2.state.x_log assert viewer2.state.y_log assert viewer2.state.layers[0].visible assert not viewer2.state.layers[1].visible assert viewer2.state.layers[2].visible viewer3 = ga.viewers[0][2] assert len(viewer3.state.layers) == 3 assert viewer3.state.x_att is dc[0].id['b'] assert viewer3.state.y_att is dc[0].id['a'] assert_allclose(viewer3.state.x_min, 0) assert_allclose(viewer3.state.x_max, 5) assert_allclose(viewer3.state.y_min, -5) assert_allclose(viewer3.state.y_max, 5) assert not viewer3.state.x_log assert not viewer3.state.y_log assert viewer3.state.layers[0].visible assert viewer3.state.layers[1].visible assert not viewer3.state.layers[2].visible ga.close() def test_session_line_back_compat(self): # Backward-compatibility for v0.11 files in which the line and scatter # plots were defined as separate styles. filename = os.path.join(DATA, 'scatter_and_line_v1.glu') with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') dc = ga.session.data_collection assert len(dc) == 1 assert dc[0].label == 'table' viewer1 = ga.viewers[0][0] assert len(viewer1.state.layers) == 1 assert viewer1.state.x_att is dc[0].id['a'] assert viewer1.state.y_att is dc[0].id['b'] assert viewer1.state.layers[0].markers_visible assert not viewer1.state.layers[0].line_visible viewer1 = ga.viewers[0][1] assert len(viewer1.state.layers) == 1 assert viewer1.state.x_att is dc[0].id['a'] assert viewer1.state.y_att is dc[0].id['b'] assert not viewer1.state.layers[0].markers_visible assert viewer1.state.layers[0].line_visible ga.close() def test_save_svg(self, tmpdir): # Regression test for a bug in AxesCache that caused SVG saving to # fail (because renderer.buffer_rgba did not exist) self.viewer.add_data(self.data) filename = tmpdir.join('test.svg').strpath self.viewer.axes.figure.savefig(filename) def test_2d(self): viewer_state = self.viewer.state self.viewer.add_data(self.data_2d) assert viewer_state.x_att is self.data_2d.id['a'] assert_allclose(viewer_state.x_min, 1 - 0.12) assert_allclose(viewer_state.x_max, 4 + 0.12) assert viewer_state.y_att is self.data_2d.id['b'] assert_allclose(viewer_state.y_min, 5 - 0.12) assert_allclose(viewer_state.y_max, 8 + 0.12) assert self.viewer.layers[0].plot_artist.get_xdata().shape == (4,) def test_apply_roi_single(self): # Regression test for a bug that caused mode.update to be called # multiple times and resulted in all other viewers receiving many # messages regarding subset updates (this occurred when multiple) # datasets were present. layer_tree = LayerTreeWidget(session=self.session) layer_tree.set_checkable(False) layer_tree.setup(self.data_collection) layer_tree.bind_selection_to_edit_subset() class Client(HubListener): def __init__(self, *args, **kwargs): super(Client, self).__init__(*args, **kwargs) self.count = Counter() def ping(self, message): self.count[message.sender] += 1 def register_to_hub(self, hub): hub.subscribe(self, SubsetUpdateMessage, handler=self.ping) d1 = Data(a=[1, 2, 3], label='d3') d2 = Data(b=[1, 2, 3], label='d4') d3 = Data(c=[1, 2, 3], label='d5') d4 = Data(d=[1, 2, 3], label='d6') self.data_collection.append(d1) self.data_collection.append(d2) self.data_collection.append(d3) self.data_collection.append(d4) client = Client() client.register_to_hub(self.hub) self.viewer.add_data(d1) self.viewer.add_data(d3) roi = XRangeROI(2.5, 3.5) self.viewer.apply_roi(roi) for subset in client.count: assert client.count[subset] == 1 @pytest.mark.parametrize('ndim', [1, 2]) def test_all_options(self, ndim): # This test makes sure that all the code for the different scatter modes # gets run, though does not check the result. viewer_state = self.viewer.state if ndim == 1: data = self.data elif ndim == 2: data = self.data_2d self.viewer.add_data(data) layer_state = viewer_state.layers[0] layer_state.style = 'Scatter' layer_state.size_mode = 'Linear' layer_state.size_att = data.id['y'] layer_state.size_vmin = 1.2 layer_state.size_vmax = 4. layer_state.size_scaling = 2 layer_state.cmap_mode = 'Linear' layer_state.cmap_att = data.id['x'] layer_state.cmap_vmin = -1 layer_state.cmap_vmax = 2. layer_state.cmap = colormaps.members[3][1] # Check inverting works layer_state.cmap_vmin = 3. layer_state.size_mode = 'Fixed' layer_state.xerr_visible = True layer_state.xerr_att = data.id['x'] layer_state.yerr_visible = True layer_state.yerr_att = data.id['y'] layer_state.style = 'Line' layer_state.linewidth = 3 layer_state.linestyle = 'dashed' 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_enable_disable_components_combo(self): # Regression test for a bug that caused an error when turning off pixel # components from combo boxes. self.viewer.add_data(self.data) self.data['a'] = self.data.id['x'] + 5 self.viewer.state.x_att_helper.pixel_coord = True self.viewer.state.x_att = self.data.pixel_component_ids[0] self.viewer.state.x_att_helper.pixel_coord = False def test_datetime64_support(self, tmpdir): self.data.add_component(np.array([100, 200, 300, 400], dtype='M8[D]'), 't1') self.data.add_component(np.array([200, 300, 400, 500], dtype='M8[D]'), 't2') self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.id['t1'] self.viewer.state.y_att = self.data.id['y'] # Matplotlib deals with dates by converting them to the number of days # since 01-01-0001, so we can check that the limits are correctly # converted (and not 100 to 400) assert self.viewer.axes.get_xlim() == (719251.0, 719575.0) assert self.viewer.axes.get_ylim() == (3.2 - 0.012, 3.5 + 0.012) # Apply an ROI selection in plotting coordinates roi = RectangularROI(xmin=719313, xmax=719513, ymin=3, ymax=4) self.viewer.apply_roi(roi) # Check that the two middle elements are selected assert_equal(self.data.subsets[0].to_mask(), [0, 1, 1, 0]) # Now do the same with the y axis self.viewer.state.y_att = self.data.id['t2'] assert self.viewer.axes.get_xlim() == (719251.0, 719575.0) assert self.viewer.axes.get_ylim() == (719351.0, 719675.0) # Apply an ROI selection in plotting coordinates edit = self.session.edit_subset_mode edit.edit_subset = [] roi = CircularROI(xc=719463, yc=719563, radius=200) self.viewer.apply_roi(roi) assert_equal(self.data.subsets[1].to_mask(), [0, 1, 1, 1]) # Make sure that the Qt labels look ok self.viewer.state.y_att = self.data.id['y'] options = self.viewer.options_widget().ui assert options.valuetext_x_min.text() == '1970-03-30' assert options.valuetext_x_max.text() == '1971-02-17' assert options.valuetext_y_min.text() == '3.188' assert options.valuetext_y_max.text() == '3.512' # Make sure that we can set the xmin/xmax to a string date assert_equal(self.viewer.state.x_min, np.datetime64('1970-03-30', 'D')) options.valuetext_x_min.setText('1970-04-14') options.valuetext_x_min.editingFinished.emit() assert self.viewer.axes.get_xlim() == (719266.0, 719575.0) assert_equal(self.viewer.state.x_min, np.datetime64('1970-04-14', 'D')) # Make sure that everything works fine after saving/reloading filename = tmpdir.join('test_datetime64.glu').strpath self.session.application.save_session(filename) with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') viewer = ga.viewers[0][0] options = viewer.options_widget().ui assert_equal(self.viewer.state.x_min, np.datetime64('1970-04-14', 'D')) assert options.valuetext_x_min.text() == '1970-04-14' assert options.valuetext_x_max.text() == '1971-02-17' assert options.valuetext_y_min.text() == '3.188' assert options.valuetext_y_max.text() == '3.512' ga.close()
class TestProfileTools(object): def setup_method(self, method): self.data = Data(label='d1') self.data.coords = SimpleCoordinates() self.data['x'] = np.arange(240).reshape((30, 4, 2)).astype(float) self.app = GlueApplication() self.session = self.app.session self.hub = self.session.hub self.data_collection = self.session.data_collection self.data_collection.append(self.data) self.viewer = self.app.new_data_viewer(ProfileViewer) self.viewer.state.function = 'mean' self.viewer.toolbar.active_tool = 'profile-analysis' self.profile_tools = self.viewer.toolbar.tools['profile-analysis']._profile_tools def teardown_method(self, method): self.viewer.close() self.viewer = None self.app.close() self.app = None 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) @pytest.mark.skipif('PYSIDE2_INSTALLED') 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_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
class TestImageViewer(object): def setup_method(self, method): self.coords = MyCoords() self.image1 = Data(label='image1', x=[[1, 2], [3, 4]], y=[[4, 5], [2, 3]]) self.image2 = Data(label='image2', a=[[3, 3], [2, 2]], b=[[4, 4], [3, 2]], coords=self.coords) self.catalog = Data(label='catalog', c=[1, 3, 2], d=[4, 3, 3]) self.hypercube = Data(label='hypercube', x=np.arange(120).reshape((2, 3, 4, 5))) # Create data versions with WCS coordinates self.image1_wcs = Data(label='image1_wcs', x=self.image1['x'], coords=WCSCoordinates(wcs=WCS(naxis=2))) self.hypercube_wcs = Data(label='hypercube_wcs', x=self.hypercube['x'], coords=WCSCoordinates(wcs=WCS(naxis=4))) self.application = GlueApplication() self.session = self.application.session self.hub = self.session.hub self.data_collection = self.session.data_collection self.data_collection.append(self.image1) self.data_collection.append(self.image2) self.data_collection.append(self.catalog) self.data_collection.append(self.hypercube) self.data_collection.append(self.image1_wcs) self.data_collection.append(self.hypercube_wcs) self.viewer = self.application.new_data_viewer(ImageViewer) self.data_collection.register_to_hub(self.hub) self.viewer.register_to_hub(self.hub) self.options_widget = self.viewer.options_widget() def teardown_method(self, method): self.viewer.close() self.viewer = None self.application.close() self.application = None def test_basic(self): # Check defaults when we add data self.viewer.add_data(self.image1) assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:World 0:World 1' assert combo_as_string(self.options_widget.ui.combosel_y_att_world) == 'Coordinate components:World 0:World 1' assert self.viewer.axes.get_xlabel() == 'World 1' assert self.viewer.state.x_att_world is self.image1.id['World 1'] assert self.viewer.state.x_att is self.image1.pixel_component_ids[1] # TODO: make sure limits are deterministic then update this # assert self.viewer.state.x_min == -0.5 # assert self.viewer.state.x_max == +1.5 assert self.viewer.axes.get_ylabel() == 'World 0' assert self.viewer.state.y_att_world is self.image1.id['World 0'] assert self.viewer.state.y_att is self.image1.pixel_component_ids[0] # TODO: make sure limits are deterministic then update this # assert self.viewer.state.y_min == -0.5 # assert self.viewer.state.y_max == +1.5 assert not self.viewer.state.x_log assert not self.viewer.state.y_log assert len(self.viewer.state.layers) == 1 def test_custom_coords(self): # Check defaults when we add data with coordinates self.viewer.add_data(self.image2) assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:Banana:Apple' assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:Banana:Apple' assert self.viewer.axes.get_xlabel() == 'Apple' assert self.viewer.state.x_att_world is self.image2.id['Apple'] assert self.viewer.state.x_att is self.image2.pixel_component_ids[1] assert self.viewer.axes.get_ylabel() == 'Banana' assert self.viewer.state.y_att_world is self.image2.id['Banana'] assert self.viewer.state.y_att is self.image2.pixel_component_ids[0] def test_flip(self): self.viewer.add_data(self.image1) x_min_start = self.viewer.state.x_min x_max_start = self.viewer.state.x_max self.options_widget.button_flip_x.click() assert self.viewer.state.x_min == x_max_start assert self.viewer.state.x_max == x_min_start y_min_start = self.viewer.state.y_min y_max_start = self.viewer.state.y_max self.options_widget.button_flip_y.click() assert self.viewer.state.y_min == y_max_start assert self.viewer.state.y_max == y_min_start def test_combo_updates_with_component_add(self): self.viewer.add_data(self.image1) self.image1.add_component([[9, 9], [8, 8]], 'z') assert self.viewer.state.x_att_world is self.image1.id['World 1'] assert self.viewer.state.y_att_world is self.image1.id['World 0'] # TODO: there should be an easier way to do this layer_style_editor = self.viewer._view.layout_style_widgets[self.viewer.layers[0]] assert combo_as_string(layer_style_editor.ui.combosel_attribute) == 'x:y:z' def test_apply_roi(self): self.viewer.add_data(self.image1) roi = RectangularROI(0.4, 1.6, -0.6, 0.6) assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert len(self.image1.subsets) == 1 assert_allclose(self.image1.subsets[0].to_mask(), [[0, 1], [0, 0]]) state = self.image1.subsets[0].subset_state assert isinstance(state, RoiSubsetState) def test_apply_roi_empty(self): # Make sure that doing an ROI selection on an empty viewer doesn't # produce error messsages roi = XRangeROI(-0.2, 0.1) self.viewer.apply_roi(roi) def test_identical(self): # Check what happens if we set both attributes to the same coordinates self.viewer.add_data(self.image2) assert self.viewer.state.x_att_world is self.image2.id['Apple'] assert self.viewer.state.y_att_world is self.image2.id['Banana'] self.viewer.state.y_att_world = self.image2.id['Apple'] assert self.viewer.state.x_att_world is self.image2.id['Banana'] assert self.viewer.state.y_att_world is self.image2.id['Apple'] self.viewer.state.x_att_world = self.image2.id['Apple'] assert self.viewer.state.x_att_world is self.image2.id['Apple'] assert self.viewer.state.y_att_world is self.image2.id['Banana'] def test_duplicate_subsets(self): # Regression test: make sure that when adding a seconda layer for the # same dataset, we don't add the subsets all over again. self.viewer.add_data(self.image1) self.data_collection.new_subset_group(subset_state=self.image1.id['x'] > 1, label='A') assert len(self.viewer.layers) == 2 self.viewer.add_data(self.image1) assert len(self.viewer.layers) == 3 def test_aspect_subset(self): self.viewer.add_data(self.image1) assert self.viewer.state.aspect == 'equal' assert self.viewer.axes.get_aspect() == 'equal' self.viewer.state.aspect = 'auto' self.data_collection.new_subset_group('s1', self.image1.id['x'] > 0.) assert len(self.viewer.state.layers) == 2 assert self.viewer.state.aspect == 'auto' assert self.viewer.axes.get_aspect() == 'auto' self.viewer.state.aspect = 'equal' self.data_collection.new_subset_group('s2', self.image1.id['x'] > 1.) assert len(self.viewer.state.layers) == 3 assert self.viewer.state.aspect == 'equal' assert self.viewer.axes.get_aspect() == 'equal' def test_hypercube(self): # Check defaults when we add data self.viewer.add_data(self.hypercube) assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:World 0:World 1:World 2:World 3' assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:World 0:World 1:World 2:World 3' assert self.viewer.axes.get_xlabel() == 'World 3' assert self.viewer.state.x_att_world is self.hypercube.id['World 3'] assert self.viewer.state.x_att is self.hypercube.pixel_component_ids[3] # TODO: make sure limits are deterministic then update this # assert self.viewer.state.x_min == -0.5 # assert self.viewer.state.x_max == +1.5 assert self.viewer.axes.get_ylabel() == 'World 2' assert self.viewer.state.y_att_world is self.hypercube.id['World 2'] assert self.viewer.state.y_att is self.hypercube.pixel_component_ids[2] # TODO: make sure limits are deterministic then update this # assert self.viewer.state.y_min == -0.5 # assert self.viewer.state.y_max == +1.5 assert not self.viewer.state.x_log assert not self.viewer.state.y_log assert len(self.viewer.state.layers) == 1 def test_hypercube_world(self): # Check defaults when we add data wcs = WCS(naxis=4) hypercube2 = Data() hypercube2.coords = WCSCoordinates(wcs=wcs) hypercube2.add_component(np.random.random((2, 3, 4, 5)), 'a') self.data_collection.append(hypercube2) self.viewer.add_data(hypercube2) def test_incompatible_subset(self): self.viewer.add_data(self.image1) self.data_collection.new_subset_group(subset_state=self.catalog.id['c'] > 1, label='A') 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() 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_apply_roi_single(self): # Regression test for a bug that caused mode.update to be called # multiple times and resulted in all other viewers receiving many # messages regarding subset updates (this occurred when multiple) # datasets were present. layer_tree = LayerTreeWidget(session=self.session) layer_tree.set_checkable(False) layer_tree.setup(self.data_collection) layer_tree.bind_selection_to_edit_subset() class Client(HubListener): def __init__(self, *args, **kwargs): super(Client, self).__init__(*args, **kwargs) self.count = Counter() def ping(self, message): self.count[message.sender] += 1 def register_to_hub(self, hub): hub.subscribe(self, SubsetUpdateMessage, handler=self.ping) d1 = Data(a=[[1, 2], [3, 4]], label='d1') d2 = Data(b=[[1, 2], [3, 4]], label='d2') d3 = Data(c=[[1, 2], [3, 4]], label='d3') d4 = Data(d=[[1, 2], [3, 4]], label='d4') self.data_collection.append(d1) self.data_collection.append(d2) self.data_collection.append(d3) self.data_collection.append(d4) client = Client() client.register_to_hub(self.hub) self.viewer.add_data(d1) self.viewer.add_data(d3) roi = XRangeROI(2.5, 3.5) self.viewer.apply_roi(roi) for subset in client.count: assert client.count[subset] == 1 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() 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) # 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) # All layers should now be enabled for layer_artist in self.viewer.layers: assert layer_artist.enabled self.data_collection.remove_link(link2) # 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_change_reference_data(self, capsys): # Test to make sure everything works fine if we change the reference data. self.viewer.add_data(self.image1) self.viewer.add_data(self.image2) assert self.viewer.state.reference_data is self.image1 assert self.viewer.state.x_att_world is self.image1.world_component_ids[-1] assert self.viewer.state.y_att_world is self.image1.world_component_ids[-2] assert self.viewer.state.x_att is self.image1.pixel_component_ids[-1] assert self.viewer.state.y_att is self.image1.pixel_component_ids[-2] self.viewer.state.reference_data = self.image2 assert self.viewer.state.reference_data is self.image2 assert self.viewer.state.x_att_world is self.image2.world_component_ids[-1] assert self.viewer.state.y_att_world is self.image2.world_component_ids[-2] assert self.viewer.state.x_att is self.image2.pixel_component_ids[-1] assert self.viewer.state.y_att is self.image2.pixel_component_ids[-2] self.viewer.state.reference_data = self.image1 assert self.viewer.state.reference_data is self.image1 assert self.viewer.state.x_att_world is self.image1.world_component_ids[-1] assert self.viewer.state.y_att_world is self.image1.world_component_ids[-2] assert self.viewer.state.x_att is self.image1.pixel_component_ids[-1] assert self.viewer.state.y_att is self.image1.pixel_component_ids[-2] # 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() == "" @pytest.mark.parametrize('wcs', [False, True]) def test_change_reference_data_dimensionality(self, capsys, wcs): # Regression test for a bug that caused an exception when changing # the dimensionality of the reference data if wcs: first = self.image1_wcs second = self.hypercube_wcs else: first = self.image1 second = self.hypercube self.viewer.add_data(first) self.viewer.add_data(second) assert self.viewer.state.reference_data is first assert self.viewer.state.x_att_world is first.world_component_ids[-1] assert self.viewer.state.y_att_world is first.world_component_ids[-2] assert self.viewer.state.x_att is first.pixel_component_ids[-1] assert self.viewer.state.y_att is first.pixel_component_ids[-2] self.viewer.state.reference_data = second assert self.viewer.state.reference_data is second assert self.viewer.state.x_att_world is second.world_component_ids[-1] assert self.viewer.state.y_att_world is second.world_component_ids[-2] assert self.viewer.state.x_att is second.pixel_component_ids[-1] assert self.viewer.state.y_att is second.pixel_component_ids[-2] self.viewer.state.reference_data = first assert self.viewer.state.reference_data is first assert self.viewer.state.x_att_world is first.world_component_ids[-1] assert self.viewer.state.y_att_world is first.world_component_ids[-2] assert self.viewer.state.x_att is first.pixel_component_ids[-1] assert self.viewer.state.y_att is first.pixel_component_ids[-2] # 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_scatter_overlay(self): self.viewer.add_data(self.image1) self.viewer.add_data(self.catalog) def test_removed_subset(self): # Regression test for a bug in v0.11.0 that meant that if a subset # was removed, the image viewer would then crash when changing view # (e.g. zooming in). The bug was caused by undeleted references to # ModestImage due to circular references. We therefore check in this # test how many ModestImage objects exist. def get_modest_images(): mi = [] gc.collect() for obj in gc.get_objects(): try: if isinstance(obj, ModestImage): mi.append(obj) except ReferenceError: pass return mi # The viewer starts off with one ModestImage. This is also a good test # that other ModestImages in other tests have been removed. assert len(get_modest_images()) == 1 large_image = Data(x=np.random.random((2048, 2048))) self.data_collection.append(large_image) # The subset group can be made from any dataset subset_group = self.data_collection.new_subset_group(subset_state=self.image1.id['x'] > 1, label='A') self.viewer.add_data(large_image) # Since the dataset added has a subset, and each subset has its own # ModestImage, this increases the count. assert len(get_modest_images()) == 2 assert len(self.viewer.layers) == 2 self.data_collection.remove_subset_group(subset_group) # Removing the subset should bring the count back to 1 again assert len(get_modest_images()) == 1 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) 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 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_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) 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) 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_save_aggregate_slice(self, tmpdir): # Regression test to make sure that image viewers that include # aggregate slice objects in the slices can be saved/restored self.viewer.add_data(self.hypercube) self.viewer.state.slices = AggregateSlice(slice(1, 3), 10, np.sum), 3, 0, 0 filename = tmpdir.join('session.glu').strpath self.application.save_session(filename) self.application.close() app2 = GlueApplication.restore_session(filename) viewer_state = app2.viewers[0][0].state slices = viewer_state.slices assert isinstance(slices[0], AggregateSlice) assert slices[0].slice == slice(1, 3) assert slices[0].center == 10 assert slices[0].function is np.sum assert slices[1:] == (3, 0, 0) app2.close() def test_subset_cube_image(self): # Regression test to make sure that if an image and cube are present # in an image viewer and a subset is also present, we don't get an # error when trying to access the subset shape self.viewer.add_data(self.image1) self.data_collection.new_subset_group(label='subset', subset_state=self.image1.id['x'] > 1.5) self.viewer.add_data(self.hypercube) self.viewer.state.reference_data = self.hypercube assert self.viewer.layers[1].subset_array.shape == (4, 5) assert self.viewer.layers[3].subset_array.shape == (4, 5) def test_preserve_slice(self): # Regression test to make sure that when adding a second dataset to # an image viewer, the current slice in a cube does not change. self.viewer.add_data(self.hypercube) self.viewer.state.slices = (1, 2, 3, 4) self.viewer.add_data(self.image1) assert self.viewer.state.slices == (1, 2, 3, 4) def test_close(self): # Regression test for a bug that caused an error related to the toolbar # and _mpl_nav not being present when closing the viewer. self.viewer.toolbar.active_tool = self.viewer.toolbar.tools['mpl:zoom'] self.viewer.close(warn=False)
def test_foreground_background_settings(): d_1d = Data(x=np.random.random(100), y=np.random.random(100), label='Data 1d') d_2d = Data(x=np.random.random((100, 100)), y=np.random.random((100, 100)), label='Data 2d') dc = DataCollection([d_1d, d_2d]) app = GlueApplication(dc) # Make sure that settings change existing viewers, so we create a bunch of # viewers here. scatter1 = app.new_data_viewer(ScatterWidget) scatter1.add_data(d_1d) image1 = app.new_data_viewer(ImageWidget) image1.add_data(d_2d) histogram1 = app.new_data_viewer(HistogramWidget) histogram1.add_data(d_1d) dendrogram1 = app.new_data_viewer(DendroWidget) example_custom = _generate_custom_viewer() custom1 = app.new_data_viewer(example_custom) RED = (1, 0, 0, 0.5) GREEN = (0, 1, 0, 0.6) app.show() with patch('glue.config.settings') as settings: settings.FOREGROUND_COLOR = 'black' settings.BACKGROUND_COLOR = 'white' settings.DATA_COLOR = '0.5' settings.DATA_ALPHA = 0.5 dialog = PreferencesDialog(app) dialog.show() dialog.background = RED dialog.foreground = GREEN dialog.accept() assert_axes_background(scatter1.axes, RED) assert_axes_background(image1.axes, RED) assert_axes_background(histogram1.axes, RED) assert_axes_background(dendrogram1.axes, RED) assert_axes_background(custom1.axes, RED) assert_axes_foreground(scatter1.axes, GREEN) assert_axes_foreground(image1.axes, GREEN) assert_axes_foreground(histogram1.axes, GREEN) assert_axes_foreground(dendrogram1.axes, GREEN) assert_axes_foreground(custom1.axes, GREEN) # Now make sure that new viewers also inherit these settings scatter2 = app.new_data_viewer(ScatterWidget) scatter2.add_data(d_1d) image2 = app.new_data_viewer(ImageWidget) image2.add_data(d_2d) histogram2 = app.new_data_viewer(HistogramWidget) histogram2.add_data(d_1d) dendrogram2 = app.new_data_viewer(DendroWidget) custom2 = app.new_data_viewer(example_custom) assert_axes_background(scatter2.axes, RED) assert_axes_background(image2.axes, RED) assert_axes_background(histogram2.axes, RED) assert_axes_background(dendrogram2.axes, RED) assert_axes_background(custom2.axes, RED) assert_axes_foreground(scatter2.axes, GREEN) assert_axes_foreground(image2.axes, GREEN) assert_axes_foreground(histogram2.axes, GREEN) assert_axes_foreground(dendrogram2.axes, GREEN) assert_axes_foreground(custom2.axes, GREEN)
class TestProfileViewer(object): def setup_method(self, method): self.data = Data(label='d1') self.data.coords = SimpleCoordinates() self.data['x'] = np.arange(24).reshape((3, 4, 2)) self.app = GlueApplication() self.session = self.app.session self.hub = self.session.hub self.data_collection = self.session.data_collection self.data_collection.append(self.data) self.viewer = self.app.new_data_viewer(ProfileViewer) def teardown_method(self, method): self.viewer.close() self.viewer = None self.app.close() self.app = None def test_functions(self): self.viewer.add_data(self.data) self.viewer.state.function = 'mean' assert len(self.viewer.layers) == 1 layer_artist = self.viewer.layers[0] assert_allclose(layer_artist.state.profile[0], [0, 2, 4]) assert_allclose(layer_artist.state.profile[1], [3.5, 11.5, 19.5]) def test_incompatible(self): self.viewer.add_data(self.data) data2 = Data(y=np.random.random((3, 4, 2))) self.data_collection.append(data2) self.viewer.add_data(data2) assert len(self.viewer.layers) == 2 assert self.viewer.layers[0].enabled assert not self.viewer.layers[1].enabled def test_selection(self): self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.pixel_component_ids[0] roi = XRangeROI(0.9, 2.1) self.viewer.apply_roi(roi) assert len(self.data.subsets) == 1 assert_equal(self.data.subsets[0].to_mask()[:, 0, 0], [0, 1, 1]) self.viewer.state.x_att = self.data.world_component_ids[0] roi = XRangeROI(1.9, 3.1) self.viewer.apply_roi(roi) assert len(self.data.subsets) == 1 assert_equal(self.data.subsets[0].to_mask()[:, 0, 0], [0, 1, 0]) def test_enabled_layers(self): data2 = Data(label='d1', y=np.arange(24).reshape((3, 4, 2))) self.data_collection.append(data2) self.viewer.add_data(self.data) self.viewer.add_data(data2) assert self.viewer.layers[0].enabled assert not self.viewer.layers[1].enabled self.data_collection.add_link(ComponentLink([data2.world_component_ids[1]], self.data.world_component_ids[0], using=lambda x: 2 * x)) assert self.viewer.layers[0].enabled assert self.viewer.layers[1].enabled def test_slice_subset_state(self): self.viewer.add_data(self.data) subset = self.data.new_subset() subset.subset_state = SliceSubsetState(self.data, [slice(1, 2), slice(None)]) assert self.viewer.layers[0].enabled assert self.viewer.layers[1].enabled def test_clone(self): # Regression test for a bug that meant that deserializing a profile # viewer resulted in disabled layers self.viewer.add_data(self.data) subset = self.data.new_subset() subset.subset_state = SliceSubsetState(self.data, [slice(1, 2), slice(None)]) app = clone(self.app) assert app.viewers[0][0].layers[0].enabled assert app.viewers[0][0].layers[1].enabled app.close() def test_incompatible_on_add(self): # Regression test for a bug when adding a dataset to a profile viewer # with a single incompatible subset. subset_state = SliceSubsetState(self.data, [slice(1, 2), slice(None)]) self.data_collection.new_subset_group(subset_state=subset_state, label='s1') data2 = Data(x=[[2, 3], [4, 3]], label='d2') self.data_collection.append(data2) self.viewer.add_data(data2)
from glue.core.link_helpers import LinkSame ga = GlueApplication() ga.resize(1230, 900) ga.show() ga.app.processEvents() ga.screenshot('main_window1.png') image = ga.load_data('w5.fits') image.label = 'W5' ga.app.processEvents() ga.screenshot('data_open.png') image_viewer = ga.new_data_viewer(ImageViewer, data=image) image_viewer._mdi_wrapper.resize(450, 400) image_viewer.state.layers[0].v_min = 440 image_viewer.state.layers[0].v_max = 900 image_viewer.state.layers[0].stretch = 'sqrt' image_viewer.state.reset_limits() ga.app.processEvents() ga.screenshot('main_window2.png') py, px = image.pixel_component_ids subset_state = (px > 500) & (px < 900) & (py > 300) & (py < 800) ga.data_collection.new_subset_group(subset_state=subset_state, label='Subset 1')
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_state_save(): app = GlueApplication() w = app.new_data_viewer(viewer._widget_cls) check_clone_app(app)
class TestHistogramViewer(object): def setup_method(self, method): self.data = Data(label='d1', x=[3.4, 2.3, -1.1, 0.3], y=['a', 'b', 'c', 'a']) self.app = GlueApplication() self.session = self.app.session self.hub = self.session.hub self.data_collection = self.session.data_collection self.data_collection.append(self.data) self.viewer = self.app.new_data_viewer(HistogramViewer) def teardown_method(self, method): self.viewer.close() self.viewer = None self.app.close() self.app = None def test_basic(self): viewer_state = self.viewer.state # Check defaults when we add data self.viewer.add_data(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:Coordinate components:Pixel Axis 0 [x]:World 0' assert viewer_state.x_att is self.data.id['x'] assert viewer_state.x_min == -1.1 assert viewer_state.x_max == 3.4 assert viewer_state.y_min == 0.0 assert viewer_state.y_max == 1.2 assert viewer_state.hist_x_min == -1.1 assert viewer_state.hist_x_max == 3.4 assert viewer_state.hist_n_bin == 15 assert not viewer_state.cumulative assert not viewer_state.normalize assert not viewer_state.x_log assert not viewer_state.y_log assert len(viewer_state.layers) == 1 # Change to categorical component and check new values viewer_state.x_att = self.data.id['y'] assert viewer_state.x_min == -0.5 assert viewer_state.x_max == 2.5 assert viewer_state.y_min == 0.0 assert viewer_state.y_max == 2.4 assert viewer_state.hist_x_min == -0.5 assert viewer_state.hist_x_max == 2.5 assert viewer_state.hist_n_bin == 3 assert not viewer_state.cumulative assert not viewer_state.normalize assert not viewer_state.x_log assert not viewer_state.y_log 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 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_flip(self): viewer_state = self.viewer.state self.viewer.add_data(self.data) assert viewer_state.x_min == -1.1 assert viewer_state.x_max == 3.4 self.viewer.options_widget().button_flip_x.click() assert viewer_state.x_min == 3.4 assert viewer_state.x_max == -1.1 def test_remove_data(self): self.viewer.add_data(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:Coordinate components:Pixel Axis 0 [x]:World 0' self.data_collection.remove(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == '' def test_update_component_updates_title(self): self.viewer.add_data(self.data) assert self.viewer.windowTitle() == '1D Histogram' self.viewer.state.x_att = self.data.id['y'] assert self.viewer.windowTitle() == '1D Histogram' def test_combo_updates_with_component_add(self): self.viewer.add_data(self.data) self.data.add_component([3, 4, 1, 2], 'z') assert self.viewer.state.x_att is self.data.id['x'] assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' def test_nonnumeric_first_component(self): # regression test for #208. Shouldn't complain if # first component is non-numerical data = core.Data() data.add_component(['a', 'b', 'c'], label='c1') data.add_component([1, 2, 3], label='c2') self.data_collection.append(data) self.viewer.add_data(data) def test_nan_component(self): # regression test for case when all values are NaN in a component data = core.Data() data.add_component([np.nan, np.nan, np.nan], label='c1') self.data_collection.append(data) self.viewer.add_data(data) def test_histogram_values(self): # Check the actual values of the histograms viewer_state = self.viewer.state self.viewer.add_data(self.data) # Numerical attribute viewer_state.hist_x_min = -5 viewer_state.hist_x_max = 5 viewer_state.hist_n_bin = 4 assert_allclose(self.viewer.state.y_max, 2.4) assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 1, 2, 1]) assert_allclose(self.viewer.layers[0].state.histogram[0], [-5, -2.5, 0, 2.5, 5]) cid = self.data.main_components[0] self.data_collection.new_subset_group('subset 1', cid < 2) assert_allclose(self.viewer.layers[1].state.histogram[1], [0, 1, 1, 0]) assert_allclose(self.viewer.layers[1].state.histogram[0], [-5, -2.5, 0, 2.5, 5]) viewer_state.normalize = True assert_allclose(self.viewer.state.y_max, 0.24) assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 0.1, 0.2, 0.1]) assert_allclose(self.viewer.layers[0].state.histogram[0], [-5, -2.5, 0, 2.5, 5]) assert_allclose(self.viewer.layers[1].state.histogram[1], [0, 0.2, 0.2, 0]) assert_allclose(self.viewer.layers[1].state.histogram[0], [-5, -2.5, 0, 2.5, 5]) viewer_state.cumulative = True assert_allclose(self.viewer.state.y_max, 1.2) assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 0.25, 0.75, 1.0]) assert_allclose(self.viewer.layers[0].state.histogram[0], [-5, -2.5, 0, 2.5, 5]) assert_allclose(self.viewer.layers[1].state.histogram[1], [0, 0.5, 1.0, 1.0]) assert_allclose(self.viewer.layers[1].state.histogram[0], [-5, -2.5, 0, 2.5, 5]) viewer_state.normalize = False assert_allclose(self.viewer.state.y_max, 4.8) assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 1, 3, 4]) assert_allclose(self.viewer.layers[0].state.histogram[0], [-5, -2.5, 0, 2.5, 5]) assert_allclose(self.viewer.layers[1].state.histogram[1], [0, 1, 2, 2]) assert_allclose(self.viewer.layers[1].state.histogram[0], [-5, -2.5, 0, 2.5, 5]) viewer_state.cumulative = False # Categorical attribute viewer_state.x_att = self.data.id['y'] formatter = self.viewer.axes.xaxis.get_major_formatter() xlabels = [formatter.format_data(pos) for pos in range(3)] assert xlabels == ['a', 'b', 'c'] assert_allclose(self.viewer.state.y_max, 2.4) assert_allclose(self.viewer.layers[0].state.histogram[1], [2, 1, 1]) assert_allclose(self.viewer.layers[0].state.histogram[0], [-0.5, 0.5, 1.5, 2.5]) assert_allclose(self.viewer.layers[1].state.histogram[1], [1, 0, 1]) assert_allclose(self.viewer.layers[1].state.histogram[0], [-0.5, 0.5, 1.5, 2.5]) viewer_state.normalize = True assert_allclose(self.viewer.state.y_max, 0.6) assert_allclose(self.viewer.layers[0].state.histogram[1], [0.5, 0.25, 0.25]) assert_allclose(self.viewer.layers[0].state.histogram[0], [-0.5, 0.5, 1.5, 2.5]) assert_allclose(self.viewer.layers[1].state.histogram[1], [0.5, 0, 0.5]) assert_allclose(self.viewer.layers[1].state.histogram[0], [-0.5, 0.5, 1.5, 2.5]) viewer_state.cumulative = True assert_allclose(self.viewer.state.y_max, 1.2) assert_allclose(self.viewer.layers[0].state.histogram[1], [0.5, 0.75, 1]) assert_allclose(self.viewer.layers[0].state.histogram[0], [-0.5, 0.5, 1.5, 2.5]) assert_allclose(self.viewer.layers[1].state.histogram[1], [0.5, 0.5, 1]) assert_allclose(self.viewer.layers[1].state.histogram[0], [-0.5, 0.5, 1.5, 2.5]) viewer_state.normalize = False assert_allclose(self.viewer.state.y_max, 4.8) assert_allclose(self.viewer.layers[0].state.histogram[1], [2, 3, 4]) assert_allclose(self.viewer.layers[0].state.histogram[0], [-0.5, 0.5, 1.5, 2.5]) assert_allclose(self.viewer.layers[1].state.histogram[1], [1, 1, 2]) assert_allclose(self.viewer.layers[1].state.histogram[0], [-0.5, 0.5, 1.5, 2.5]) # TODO: add tests for log def test_apply_roi(self): # Check that when doing an ROI selection, the ROI clips to the bin edges # outside the selection viewer_state = self.viewer.state self.viewer.add_data(self.data) viewer_state.hist_x_min = -5 viewer_state.hist_x_max = 5 viewer_state.hist_n_bin = 4 roi = XRangeROI(-0.2, 0.1) assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 1, 2, 1]) assert_allclose(self.viewer.layers[1].state.histogram[1], [0, 1, 2, 0]) assert_allclose(self.data.subsets[0].to_mask(), [0, 1, 1, 1]) state = self.data.subsets[0].subset_state assert isinstance(state, RangeSubsetState) assert state.lo == -2.5 assert state.hi == 2.5 # TODO: add a similar test in log space def test_apply_roi_categorical(self): # Check that when doing an ROI selection, the ROI clips to the bin edges # outside the selection viewer_state = self.viewer.state self.viewer.add_data(self.data) viewer_state.x_att = self.data.id['y'] roi = XRangeROI(0.3, 0.9) assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert_allclose(self.viewer.layers[0].state.histogram[1], [2, 1, 1]) assert_allclose(self.viewer.layers[1].state.histogram[1], [2, 1, 0]) assert_allclose(self.data.subsets[0].to_mask(), [1, 1, 0, 1]) state = self.data.subsets[0].subset_state assert isinstance(state, CategoricalROISubsetState) assert_equal(state.roi.categories, ['a', 'b']) def test_apply_roi_empty(self): # Make sure that doing an ROI selection on an empty viewer doesn't # produce error messsages roi = XRangeROI(-0.2, 0.1) self.viewer.apply_roi(roi) def test_axes_labels(self): viewer_state = self.viewer.state self.viewer.add_data(self.data) assert self.viewer.axes.get_xlabel() == 'x' assert self.viewer.axes.get_ylabel() == 'Number' viewer_state.x_log = True assert self.viewer.axes.get_xlabel() == 'Log x' assert self.viewer.axes.get_ylabel() == 'Number' viewer_state.x_att = self.data.id['y'] assert self.viewer.axes.get_xlabel() == 'y' assert self.viewer.axes.get_ylabel() == 'Number' viewer_state.normalize = True assert self.viewer.axes.get_xlabel() == 'y' assert self.viewer.axes.get_ylabel() == 'Normalized number' viewer_state.normalize = False viewer_state.cumulative = True assert self.viewer.axes.get_xlabel() == 'y' assert self.viewer.axes.get_ylabel() == 'Number' def test_y_min_y_max(self): # Regression test for a bug that caused y_max to not be set correctly # when multiple subsets were present and after turning on normalization # after switching to a different attribute from that used to make the # selection. viewer_state = self.viewer.state self.viewer.add_data(self.data) self.data.add_component([3.4, 3.5, 10.2, 20.3], 'z') viewer_state.x_att = self.data.id['x'] cid = self.data.main_components[0] self.data_collection.new_subset_group('subset 1', cid < 1) cid = self.data.main_components[0] self.data_collection.new_subset_group('subset 2', cid < 2) cid = self.data.main_components[0] self.data_collection.new_subset_group('subset 3', cid < 3) assert_allclose(self.viewer.state.y_min, 0) assert_allclose(self.viewer.state.y_max, 1.2) viewer_state.x_att = self.data.id['z'] assert_allclose(self.viewer.state.y_min, 0) assert_allclose(self.viewer.state.y_max, 2.4) viewer_state.normalize = True assert_allclose(self.viewer.state.y_min, 0) assert_allclose(self.viewer.state.y_max, 0.5325443786982249) def test_update_when_limits_unchanged(self): # Regression test for glue-viz/glue#1010 - this bug caused histograms # to not be recomputed if the attribute changed but the limits and # number of bins did not. viewer_state = self.viewer.state self.viewer.add_data(self.data) viewer_state.x_att = self.data.id['y'] viewer_state.hist_x_min = -10.1 viewer_state.hist_x_max = +10 viewer_state.hist_n_bin = 5 assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 0, 3, 1, 0]) viewer_state.x_att = self.data.id['x'] viewer_state.hist_x_min = -10.1 viewer_state.hist_x_max = +10 viewer_state.hist_n_bin = 5 assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 0, 2, 2, 0]) viewer_state.x_att = self.data.id['y'] assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 0, 3, 1, 0]) viewer_state.x_att = self.data.id['x'] assert_allclose(self.viewer.layers[0].state.histogram[1], [0, 0, 2, 2, 0]) def test_component_replaced(self): # regression test for 508 - if a component ID is replaced, we should # make sure that the component ID is selected if the old component ID # was selected self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.id['x'] test = ComponentID('test') self.data.update_id(self.viewer.state.x_att, test) assert self.viewer.state.x_att is test assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:test:y:Coordinate components:Pixel Axis 0 [x]:World 0' def test_nbin_override_persists_over_numerical_attribute_change(self): # regression test for #398 self.data.add_component([3, 4, 1, 2], 'z') self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.id['x'] self.viewer.state.hist_n_bin = 7 self.viewer.state.x_att = self.data.id['z'] assert self.viewer.state.hist_n_bin == 7 @pytest.mark.parametrize('protocol', [0, 1]) def test_session_back_compat(self, protocol): filename = os.path.join(DATA, 'histogram_v{0}.glu'.format(protocol)) with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') dc = ga.session.data_collection assert len(dc) == 1 assert dc[0].label == 'data' viewer1 = ga.viewers[0][0] assert len(viewer1.state.layers) == 2 assert viewer1.state.x_att is dc[0].id['a'] assert_allclose(viewer1.state.x_min, 0) assert_allclose(viewer1.state.x_max, 9) assert_allclose(viewer1.state.y_min, 0) assert_allclose(viewer1.state.y_max, 2.4) assert_allclose(viewer1.state.hist_x_min, 0) assert_allclose(viewer1.state.hist_x_max, 9) assert_allclose(viewer1.state.hist_n_bin, 6) assert not viewer1.state.x_log assert not viewer1.state.y_log assert viewer1.state.layers[0].visible assert not viewer1.state.layers[1].visible assert not viewer1.state.cumulative assert not viewer1.state.normalize viewer2 = ga.viewers[0][1] assert viewer2.state.x_att is dc[0].id['b'] assert_allclose(viewer2.state.x_min, 2) assert_allclose(viewer2.state.x_max, 16) assert_allclose(viewer2.state.y_min, 0) assert_allclose(viewer2.state.y_max, 1.2) assert_allclose(viewer2.state.hist_x_min, 2) assert_allclose(viewer2.state.hist_x_max, 16) assert_allclose(viewer2.state.hist_n_bin, 8) assert not viewer2.state.x_log assert not viewer2.state.y_log assert viewer2.state.layers[0].visible assert viewer2.state.layers[1].visible assert not viewer2.state.cumulative assert not viewer2.state.normalize viewer3 = ga.viewers[0][2] assert viewer3.state.x_att is dc[0].id['a'] assert_allclose(viewer3.state.x_min, 0) assert_allclose(viewer3.state.x_max, 9) assert_allclose(viewer3.state.y_min, 0.01111111111111111) assert_allclose(viewer3.state.y_max, 0.7407407407407407) assert_allclose(viewer3.state.hist_x_min, 0) assert_allclose(viewer3.state.hist_x_max, 9) assert_allclose(viewer3.state.hist_n_bin, 10) assert not viewer3.state.x_log assert viewer3.state.y_log assert viewer3.state.layers[0].visible assert viewer3.state.layers[1].visible assert not viewer3.state.cumulative assert viewer3.state.normalize viewer4 = ga.viewers[0][3] assert viewer4.state.x_att is dc[0].id['a'] assert_allclose(viewer4.state.x_min, -1) assert_allclose(viewer4.state.x_max, 10) assert_allclose(viewer4.state.y_min, 0) assert_allclose(viewer4.state.y_max, 12) assert_allclose(viewer4.state.hist_x_min, -1) assert_allclose(viewer4.state.hist_x_max, 10) assert_allclose(viewer4.state.hist_n_bin, 4) assert not viewer4.state.x_log assert not viewer4.state.y_log assert viewer4.state.layers[0].visible assert viewer4.state.layers[1].visible assert viewer4.state.cumulative assert not viewer4.state.normalize def test_apply_roi_single(self): # Regression test for a bug that caused mode.update to be called # multiple times and resulted in all other viewers receiving many # messages regarding subset updates (this occurred when multiple) # datasets were present. layer_tree = LayerTreeWidget(session=self.session) layer_tree.set_checkable(False) layer_tree.setup(self.data_collection) layer_tree.bind_selection_to_edit_subset() class Client(HubListener): def __init__(self, *args, **kwargs): super(Client, self).__init__(*args, **kwargs) self.count = Counter() def ping(self, message): self.count[message.sender] += 1 def register_to_hub(self, hub): hub.subscribe(self, SubsetUpdateMessage, handler=self.ping) d1 = Data(a=[1, 2, 3], label='d1') d2 = Data(b=[1, 2, 3], label='d2') d3 = Data(c=[1, 2, 3], label='d3') d4 = Data(d=[1, 2, 3], label='d4') self.data_collection.append(d1) self.data_collection.append(d2) self.data_collection.append(d3) self.data_collection.append(d4) client = Client() client.register_to_hub(self.hub) self.viewer.add_data(d1) self.viewer.add_data(d3) roi = XRangeROI(2.5, 3.5) self.viewer.apply_roi(roi) for subset in client.count: assert client.count[subset] == 1 def test_datetime64_support(self, tmpdir): self.data.add_component(np.array([100, 200, 300, 400], dtype='M8[D]'), 't1') self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.id['t1'] # Matplotlib deals with dates by converting them to the number of days # since 01-01-0001, so we can check that the limits are correctly # converted (and not 100 to 400) assert self.viewer.axes.get_xlim() == (719263.0, 719563.0) # Apply an ROI selection in plotting coordinates roi = XRangeROI(719313, 719513) self.viewer.apply_roi(roi) # Check that the two middle elements are selected assert_equal(self.data.subsets[0].to_mask(), [0, 1, 1, 0]) # Make sure that the Qt labels look ok options = self.viewer.options_widget().ui assert options.valuetext_x_min.text() == '1970-04-11' assert options.valuetext_x_max.text() == '1971-02-05' # Make sure that we can set the xmin/xmax to a string date assert_equal(self.viewer.state.x_min, np.datetime64('1970-04-11', 'D')) options.valuetext_x_min.setText('1970-04-14') options.valuetext_x_min.editingFinished.emit() assert self.viewer.axes.get_xlim() == (719266.0, 719563.0) assert_equal(self.viewer.state.x_min, np.datetime64('1970-04-14', 'D')) # Make sure that everything works fine after saving/reloading filename = tmpdir.join('test_datetime64.glu').strpath self.session.application.save_session(filename) with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') viewer = ga.viewers[0][0] options = viewer.options_widget().ui assert_equal(self.viewer.state.x_min, np.datetime64('1970-04-14', 'D')) assert options.valuetext_x_min.text() == '1970-04-14' assert options.valuetext_x_max.text() == '1971-02-05'
class TestQtPlotlyExporter(): def setup_class(self): data = Data(x=[1, 2, 3], y=[2, 3, 4], label='data') dc = DataCollection([data]) self.app = GlueApplication(dc) data.style.color = '#000000' v = self.app.new_data_viewer(HistogramViewer, data=data) v.component = data.id['y'] v.xmin = 0 v.xmax = 10 v.bins = 20 self.args, self.kwargs = build_plotly_call(self.app) def teardown_class(self): self.app.close() self.app = None def get_exporter(self): return QtPlotlyExporter(plotly_args=self.args, plotly_kwargs=self.kwargs) def test_default_no_credentials(self, tmpdir): credentials_file = tmpdir.join('.credentials').strpath make_credentials_file(credentials_file) with patch('plotly.tools.CREDENTIALS_FILE', credentials_file): exporter = self.get_exporter() assert not exporter.radio_account_config.isChecked() assert exporter.radio_account_manual.isChecked() assert exporter.radio_sharing_secret.isChecked() def test_default_with_credentials(self, tmpdir): credentials_file = tmpdir.join('.credentials').strpath make_credentials_file(credentials_file, username='******', api_key='batmobile') with patch('plotly.tools.CREDENTIALS_FILE', credentials_file): exporter = self.get_exporter() assert exporter.radio_account_config.isChecked() assert 'username: batman' in exporter.radio_account_config.text() assert exporter.radio_sharing_secret.isChecked() def test_edit_username_toggle_custom(self, tmpdir): credentials_file = tmpdir.join('.credentials').strpath make_credentials_file(credentials_file, username='******', api_key='batmobile') with patch('plotly.tools.CREDENTIALS_FILE', credentials_file): exporter = self.get_exporter() assert exporter.radio_account_config.isChecked() exporter.username = '******' assert exporter.radio_account_manual.isChecked() exporter.radio_account_config.setChecked(True) assert exporter.radio_account_config.isChecked() exporter.api_key = 'a' assert exporter.radio_account_manual.isChecked() def test_accept_default(self, tmpdir): credentials_file = tmpdir.join('.credentials').strpath make_credentials_file(credentials_file, username='******', api_key='batmobile') with patch('plotly.tools.CREDENTIALS_FILE', credentials_file): with patch('plotly.plotly.plot', mock.MagicMock()): with patch('plotly.plotly.sign_in', mock.MagicMock()): with patch('webbrowser.open_new_tab') as open_new_tab: exporter = self.get_exporter() exporter.accept() assert exporter.text_status.text() == 'Exporting succeeded' ERRORS = [ (PlotlyError(SIGN_IN_ERROR), 'Authentication failed'), (PlotlyError(MAX_PRIVATE_ERROR), 'Maximum number of private plots reached'), (PlotlyError('Oh noes!'), 'An unexpected error occurred'), (TypeError('A banana is not an apple'), 'An unexpected error occurred') ] @pytest.mark.parametrize(('error', 'status'), ERRORS) def test_accept_errors(self, tmpdir, error, status): credentials_file = tmpdir.join('.credentials').strpath make_credentials_file(credentials_file, username='******', api_key='batmobile') plot = mock.MagicMock(side_effect=error) sign_in = mock.MagicMock() with patch('plotly.tools.CREDENTIALS_FILE', credentials_file): with patch('plotly.plotly.sign_in', sign_in): with patch('plotly.plotly.plot', plot): with patch('webbrowser.open_new_tab'): exporter = self.get_exporter() exporter.accept() assert exporter.text_status.text() == status def test_fix_url(self, tmpdir): credentials_file = tmpdir.join('.credentials').strpath make_credentials_file(credentials_file, username='******', api_key='batmobile') plot = mock.MagicMock(return_value='https://plot.ly/~batman/6?share_key=rbkWvJQn6cyj3HMMGROiqI') sign_in = mock.MagicMock() with patch('plotly.tools.CREDENTIALS_FILE', credentials_file): with patch('plotly.plotly.sign_in', sign_in): with patch('plotly.plotly.plot', plot): with patch('webbrowser.open_new_tab') as open_new_tab: exporter = self.get_exporter() exporter.accept() assert open_new_tab.called_once_with('https://plot.ly/~batman/6/?share_key=rbkWvJQn6cyj3HMMGROiqI')
class TestImageViewer(object): def setup_method(self, method): self.coords = MyCoords() self.image1 = Data(label='image1', x=[[1, 2], [3, 4]], y=[[4, 5], [2, 3]]) self.image2 = Data(label='image2', a=[[3, 3], [2, 2]], b=[[4, 4], [3, 2]], coords=self.coords) self.catalog = Data(label='catalog', c=[1, 3, 2], d=[4, 3, 3]) self.hypercube = Data(label='hypercube', x=np.arange(120).reshape((2, 3, 4, 5))) # Create data versions with WCS coordinates self.image1_wcs = Data(label='image1_wcs', x=self.image1['x'], coords=WCSCoordinates(wcs=WCS(naxis=2))) self.hypercube_wcs = Data(label='hypercube_wcs', x=self.hypercube['x'], coords=WCSCoordinates(wcs=WCS(naxis=4))) self.application = GlueApplication() self.session = self.application.session self.hub = self.session.hub self.data_collection = self.session.data_collection self.data_collection.append(self.image1) self.data_collection.append(self.image2) self.data_collection.append(self.catalog) self.data_collection.append(self.hypercube) self.data_collection.append(self.image1_wcs) self.data_collection.append(self.hypercube_wcs) self.viewer = self.application.new_data_viewer(ImageViewer) self.data_collection.register_to_hub(self.hub) self.viewer.register_to_hub(self.hub) self.options_widget = self.viewer.options_widget() def teardown_method(self, method): self.viewer.close() self.viewer = None self.application.close() self.application = None def test_basic(self): # Check defaults when we add data self.viewer.add_data(self.image1) assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:World 0:World 1' assert combo_as_string(self.options_widget.ui.combosel_y_att_world) == 'Coordinate components:World 0:World 1' assert self.viewer.axes.get_xlabel() == 'World 1' assert self.viewer.state.x_att_world is self.image1.id['World 1'] assert self.viewer.state.x_att is self.image1.pixel_component_ids[1] assert_allclose(self.viewer.state.x_min, -0.8419913419913423) assert_allclose(self.viewer.state.x_max, +1.8419913419913423) assert self.viewer.axes.get_ylabel() == 'World 0' assert self.viewer.state.y_att_world is self.image1.id['World 0'] assert self.viewer.state.y_att is self.image1.pixel_component_ids[0] assert self.viewer.state.y_min == -0.5 assert self.viewer.state.y_max == +1.5 assert not self.viewer.state.x_log assert not self.viewer.state.y_log assert len(self.viewer.state.layers) == 1 def test_custom_coords(self): # Check defaults when we add data with coordinates self.viewer.add_data(self.image2) assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:Banana:Apple' assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:Banana:Apple' assert self.viewer.axes.get_xlabel() == 'Apple' assert self.viewer.state.x_att_world is self.image2.id['Apple'] assert self.viewer.state.x_att is self.image2.pixel_component_ids[1] assert self.viewer.axes.get_ylabel() == 'Banana' assert self.viewer.state.y_att_world is self.image2.id['Banana'] assert self.viewer.state.y_att is self.image2.pixel_component_ids[0] def test_flip(self): self.viewer.add_data(self.image1) x_min_start = self.viewer.state.x_min x_max_start = self.viewer.state.x_max self.options_widget.button_flip_x.click() assert self.viewer.state.x_min == x_max_start assert self.viewer.state.x_max == x_min_start y_min_start = self.viewer.state.y_min y_max_start = self.viewer.state.y_max self.options_widget.button_flip_y.click() assert self.viewer.state.y_min == y_max_start assert self.viewer.state.y_max == y_min_start def test_combo_updates_with_component_add(self): self.viewer.add_data(self.image1) self.image1.add_component([[9, 9], [8, 8]], 'z') assert self.viewer.state.x_att_world is self.image1.id['World 1'] assert self.viewer.state.y_att_world is self.image1.id['World 0'] # TODO: there should be an easier way to do this layer_style_editor = self.viewer._view.layout_style_widgets[self.viewer.layers[0]] assert combo_as_string(layer_style_editor.ui.combosel_attribute) == 'x:y:z' def test_apply_roi(self): self.viewer.add_data(self.image1) roi = RectangularROI(0.4, 1.6, -0.6, 0.6) assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert len(self.image1.subsets) == 1 assert_allclose(self.image1.subsets[0].to_mask(), [[0, 1], [0, 0]]) state = self.image1.subsets[0].subset_state assert isinstance(state, RoiSubsetState) def test_apply_roi_empty(self): # Make sure that doing an ROI selection on an empty viewer doesn't # produce error messsages roi = XRangeROI(-0.2, 0.1) self.viewer.apply_roi(roi) def test_identical(self): # Check what happens if we set both attributes to the same coordinates self.viewer.add_data(self.image2) assert self.viewer.state.x_att_world is self.image2.id['Apple'] assert self.viewer.state.y_att_world is self.image2.id['Banana'] self.viewer.state.y_att_world = self.image2.id['Apple'] assert self.viewer.state.x_att_world is self.image2.id['Banana'] assert self.viewer.state.y_att_world is self.image2.id['Apple'] self.viewer.state.x_att_world = self.image2.id['Apple'] assert self.viewer.state.x_att_world is self.image2.id['Apple'] assert self.viewer.state.y_att_world is self.image2.id['Banana'] def test_duplicate_subsets(self): # Regression test: make sure that when adding a seconda layer for the # same dataset, we don't add the subsets all over again. self.viewer.add_data(self.image1) self.data_collection.new_subset_group(subset_state=self.image1.id['x'] > 1, label='A') assert len(self.viewer.layers) == 2 self.viewer.add_data(self.image1) assert len(self.viewer.layers) == 3 def test_aspect_subset(self): self.viewer.add_data(self.image1) assert self.viewer.state.aspect == 'equal' self.viewer.state.aspect = 'auto' self.data_collection.new_subset_group('s1', self.image1.id['x'] > 0.) assert len(self.viewer.state.layers) == 2 assert self.viewer.state.aspect == 'auto' self.viewer.state.aspect = 'equal' self.data_collection.new_subset_group('s2', self.image1.id['x'] > 1.) assert len(self.viewer.state.layers) == 3 assert self.viewer.state.aspect == 'equal' def test_hypercube(self): # Check defaults when we add data self.viewer.add_data(self.hypercube) assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:World 0:World 1:World 2:World 3' assert combo_as_string(self.options_widget.ui.combosel_x_att_world) == 'Coordinate components:World 0:World 1:World 2:World 3' assert self.viewer.axes.get_xlabel() == 'World 3' assert self.viewer.state.x_att_world is self.hypercube.id['World 3'] assert self.viewer.state.x_att is self.hypercube.pixel_component_ids[3] assert_allclose(self.viewer.state.x_min, -0.6839826839826846) assert_allclose(self.viewer.state.x_max, +4.6839826839826846) assert self.viewer.axes.get_ylabel() == 'World 2' assert self.viewer.state.y_att_world is self.hypercube.id['World 2'] assert self.viewer.state.y_att is self.hypercube.pixel_component_ids[2] assert self.viewer.state.y_min == -0.5 assert self.viewer.state.y_max == +3.5 assert not self.viewer.state.x_log assert not self.viewer.state.y_log assert len(self.viewer.state.layers) == 1 def test_hypercube_world(self): # Check defaults when we add data wcs = WCS(naxis=4) hypercube2 = Data() hypercube2.coords = WCSCoordinates(wcs=wcs) hypercube2.add_component(np.random.random((2, 3, 4, 5)), 'a') self.data_collection.append(hypercube2) self.viewer.add_data(hypercube2) def test_incompatible_subset(self): self.viewer.add_data(self.image1) self.data_collection.new_subset_group(subset_state=self.catalog.id['c'] > 1, label='A') 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() 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_apply_roi_single(self): # Regression test for a bug that caused mode.update to be called # multiple times and resulted in all other viewers receiving many # messages regarding subset updates (this occurred when multiple) # datasets were present. layer_tree = LayerTreeWidget(session=self.session) layer_tree.set_checkable(False) layer_tree.setup(self.data_collection) layer_tree.bind_selection_to_edit_subset() class Client(HubListener): def __init__(self, *args, **kwargs): super(Client, self).__init__(*args, **kwargs) self.count = Counter() def ping(self, message): self.count[message.sender] += 1 def register_to_hub(self, hub): hub.subscribe(self, SubsetUpdateMessage, handler=self.ping) d1 = Data(a=[[1, 2], [3, 4]], label='d1') d2 = Data(b=[[1, 2], [3, 4]], label='d2') d3 = Data(c=[[1, 2], [3, 4]], label='d3') d4 = Data(d=[[1, 2], [3, 4]], label='d4') self.data_collection.append(d1) self.data_collection.append(d2) self.data_collection.append(d3) self.data_collection.append(d4) client = Client() client.register_to_hub(self.hub) self.viewer.add_data(d1) self.viewer.add_data(d3) roi = XRangeROI(2.5, 3.5) self.viewer.apply_roi(roi) for subset in client.count: assert client.count[subset] == 1 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() 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) # 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) # All layers should now be enabled for layer_artist in self.viewer.layers: assert layer_artist.enabled self.data_collection.remove_link(link2) # 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_change_reference_data(self, capsys): # Test to make sure everything works fine if we change the reference data. self.viewer.add_data(self.image1) self.viewer.add_data(self.image2) assert self.viewer.state.reference_data is self.image1 assert self.viewer.state.x_att_world is self.image1.world_component_ids[-1] assert self.viewer.state.y_att_world is self.image1.world_component_ids[-2] assert self.viewer.state.x_att is self.image1.pixel_component_ids[-1] assert self.viewer.state.y_att is self.image1.pixel_component_ids[-2] self.viewer.state.reference_data = self.image2 assert self.viewer.state.reference_data is self.image2 assert self.viewer.state.x_att_world is self.image2.world_component_ids[-1] assert self.viewer.state.y_att_world is self.image2.world_component_ids[-2] assert self.viewer.state.x_att is self.image2.pixel_component_ids[-1] assert self.viewer.state.y_att is self.image2.pixel_component_ids[-2] self.viewer.state.reference_data = self.image1 assert self.viewer.state.reference_data is self.image1 assert self.viewer.state.x_att_world is self.image1.world_component_ids[-1] assert self.viewer.state.y_att_world is self.image1.world_component_ids[-2] assert self.viewer.state.x_att is self.image1.pixel_component_ids[-1] assert self.viewer.state.y_att is self.image1.pixel_component_ids[-2] # 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() == "" @pytest.mark.parametrize('wcs', [False, True]) def test_change_reference_data_dimensionality(self, capsys, wcs): # Regression test for a bug that caused an exception when changing # the dimensionality of the reference data if wcs: first = self.image1_wcs second = self.hypercube_wcs else: first = self.image1 second = self.hypercube self.viewer.add_data(first) self.viewer.add_data(second) assert self.viewer.state.reference_data is first assert self.viewer.state.x_att_world is first.world_component_ids[-1] assert self.viewer.state.y_att_world is first.world_component_ids[-2] assert self.viewer.state.x_att is first.pixel_component_ids[-1] assert self.viewer.state.y_att is first.pixel_component_ids[-2] self.viewer.state.reference_data = second assert self.viewer.state.reference_data is second assert self.viewer.state.x_att_world is second.world_component_ids[-1] assert self.viewer.state.y_att_world is second.world_component_ids[-2] assert self.viewer.state.x_att is second.pixel_component_ids[-1] assert self.viewer.state.y_att is second.pixel_component_ids[-2] self.viewer.state.reference_data = first assert self.viewer.state.reference_data is first assert self.viewer.state.x_att_world is first.world_component_ids[-1] assert self.viewer.state.y_att_world is first.world_component_ids[-2] assert self.viewer.state.x_att is first.pixel_component_ids[-1] assert self.viewer.state.y_att is first.pixel_component_ids[-2] # 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_scatter_overlay(self): self.viewer.add_data(self.image1) self.viewer.add_data(self.catalog) def test_removed_subset(self): # Regression test for a bug in v0.11.0 that meant that if a subset # was removed, the image viewer would then crash when changing view # (e.g. zooming in). The bug was caused by undeleted references to # ModestImage due to circular references. We therefore check in this # test how many ModestImage objects exist. def get_modest_images(): mi = [] gc.collect() for obj in gc.get_objects(): try: if isinstance(obj, ModestImage): mi.append(obj) except ReferenceError: pass return mi # The viewer starts off with one ModestImage. This is also a good test # that other ModestImages in other tests have been removed. assert len(get_modest_images()) == 1 large_image = Data(x=np.random.random((2048, 2048))) self.data_collection.append(large_image) # The subset group can be made from any dataset subset_group = self.data_collection.new_subset_group(subset_state=self.image1.id['x'] > 1, label='A') self.viewer.add_data(large_image) # Since the dataset added has a subset, and each subset has its own # ModestImage, this increases the count. assert len(get_modest_images()) == 2 assert len(self.viewer.layers) == 2 self.data_collection.remove_subset_group(subset_group) # Removing the subset should bring the count back to 1 again assert len(get_modest_images()) == 1 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) 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 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_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) 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) 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_save_aggregate_slice(self, tmpdir): # Regression test to make sure that image viewers that include # aggregate slice objects in the slices can be saved/restored self.viewer.add_data(self.hypercube) self.viewer.state.slices = AggregateSlice(slice(1, 3), 10, np.sum), 3, 0, 0 filename = tmpdir.join('session.glu').strpath self.application.save_session(filename) self.application.close() app2 = GlueApplication.restore_session(filename) viewer_state = app2.viewers[0][0].state slices = viewer_state.slices assert isinstance(slices[0], AggregateSlice) assert slices[0].slice == slice(1, 3) assert slices[0].center == 10 assert slices[0].function is np.sum assert slices[1:] == (3, 0, 0) app2.close() def test_subset_cube_image(self): # Regression test to make sure that if an image and cube are present # in an image viewer and a subset is also present, we don't get an # error when trying to access the subset shape self.viewer.add_data(self.image1) self.data_collection.new_subset_group(label='subset', subset_state=self.image1.id['x'] > 1.5) self.viewer.add_data(self.hypercube) self.viewer.state.reference_data = self.hypercube assert self.viewer.layers[1].subset_array.shape == (4, 5) assert self.viewer.layers[3].subset_array.shape == (4, 5) def test_preserve_slice(self): # Regression test to make sure that when adding a second dataset to # an image viewer, the current slice in a cube does not change. self.viewer.add_data(self.hypercube) self.viewer.state.slices = (1, 2, 3, 4) self.viewer.add_data(self.image1) assert self.viewer.state.slices == (1, 2, 3, 4) def test_close(self): # Regression test for a bug that caused an error related to the toolbar # and _mpl_nav not being present when closing the viewer. self.viewer.toolbar.active_tool = self.viewer.toolbar.tools['mpl:zoom'] self.viewer.close(warn=False)
class TestScatterViewer(object): def setup_method(self, method): self.data = 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_2d = Data(label='d2', a=[[1, 2], [3, 4]], b=[[5, 6], [7, 8]], x=[[3, 5], [5.4, 1]], y=[[1.2, 4], [7, 8]]) self.app = GlueApplication() self.session = self.app.session self.hub = self.session.hub self.data_collection = self.session.data_collection self.data_collection.append(self.data) self.data_collection.append(self.data_2d) self.viewer = self.app.new_data_viewer(ScatterViewer) self.data_collection.register_to_hub(self.hub) self.viewer.register_to_hub(self.hub) def teardown_method(self, method): self.viewer.close() def test_basic(self): viewer_state = self.viewer.state # Check defaults when we add data self.viewer.add_data(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' assert combo_as_string(self.viewer.options_widget().ui.combosel_y_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' assert viewer_state.x_att is self.data.id['x'] assert viewer_state.x_min == -1.1 assert viewer_state.x_max == 3.4 assert viewer_state.y_att is self.data.id['y'] assert viewer_state.y_min == 3.2 assert viewer_state.y_max == 3.5 assert not viewer_state.x_log assert not viewer_state.y_log assert len(viewer_state.layers) == 1 # Change to categorical component and check new values viewer_state.y_att = self.data.id['z'] assert viewer_state.x_att is self.data.id['x'] assert viewer_state.x_min == -1.1 assert viewer_state.x_max == 3.4 assert viewer_state.y_att is self.data.id['z'] assert viewer_state.y_min == -0.5 assert viewer_state.y_max == 2.5 assert not viewer_state.x_log assert not viewer_state.y_log def test_flip(self): viewer_state = self.viewer.state self.viewer.add_data(self.data) assert viewer_state.x_min == -1.1 assert viewer_state.x_max == 3.4 self.viewer.options_widget().button_flip_x.click() assert viewer_state.x_min == 3.4 assert viewer_state.x_max == -1.1 assert viewer_state.y_min == 3.2 assert viewer_state.y_max == 3.5 self.viewer.options_widget().button_flip_y.click() assert viewer_state.y_min == 3.5 assert viewer_state.y_max == 3.2 def test_remove_data(self): self.viewer.add_data(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' assert combo_as_string(self.viewer.options_widget().ui.combosel_y_att) == 'Main components:x:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' self.data_collection.remove(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == '' assert combo_as_string(self.viewer.options_widget().ui.combosel_y_att) == '' def test_update_component_updates_title(self): self.viewer.add_data(self.data) assert self.viewer.windowTitle() == '2D Scatter' self.viewer.state.x_att = self.data.id['y'] assert self.viewer.windowTitle() == '2D Scatter' def test_combo_updates_with_component_add(self): self.viewer.add_data(self.data) self.data.add_component([3, 4, 1, 2], 'a') assert self.viewer.state.x_att is self.data.id['x'] assert self.viewer.state.y_att is self.data.id['y'] assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:z:a:Coordinate components:Pixel Axis 0 [x]:World 0' assert combo_as_string(self.viewer.options_widget().ui.combosel_y_att) == 'Main components:x:y:z:a:Coordinate components:Pixel Axis 0 [x]:World 0' def test_nonnumeric_first_component(self): # regression test for #208. Shouldn't complain if # first component is non-numerical data = core.Data() data.add_component(['a', 'b', 'c'], label='c1') data.add_component([1, 2, 3], label='c2') self.data_collection.append(data) self.viewer.add_data(data) def test_apply_roi(self): self.viewer.add_data(self.data) roi = RectangularROI(0, 3, 3.25, 3.45) assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert len(self.data.subsets) == 1 assert_allclose(self.data.subsets[0].to_mask(), [0, 1, 0, 0]) state = self.data.subsets[0].subset_state assert isinstance(state, RoiSubsetState) def test_apply_roi_categorical(self): viewer_state = self.viewer.state self.viewer.add_data(self.data) viewer_state.y_att = self.data.id['z'] roi = RectangularROI(0, 3, -0.4, 0.3) assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert len(self.data.subsets) == 1 assert_allclose(self.data.subsets[0].to_mask(), [0, 0, 0, 1]) state = self.data.subsets[0].subset_state assert isinstance(state, AndState) def test_apply_roi_empty(self): # Make sure that doing an ROI selection on an empty viewer doesn't # produce error messsages roi = XRangeROI(-0.2, 0.1) self.viewer.apply_roi(roi) def test_axes_labels(self): viewer_state = self.viewer.state self.viewer.add_data(self.data) assert self.viewer.axes.get_xlabel() == 'x' assert self.viewer.axes.get_ylabel() == 'y' viewer_state.x_log = True assert self.viewer.axes.get_xlabel() == 'Log x' assert self.viewer.axes.get_ylabel() == 'y' viewer_state.x_att = self.data.id['y'] assert self.viewer.axes.get_xlabel() == 'y' assert self.viewer.axes.get_ylabel() == 'y' viewer_state.y_log = True assert self.viewer.axes.get_xlabel() == 'y' assert self.viewer.axes.get_ylabel() == 'Log y' def test_component_replaced(self): # regression test for 508 - if a component ID is replaced, we should # make sure that the component ID is selected if the old component ID # was selected self.viewer.add_data(self.data) self.viewer.state.x_att = self.data.id['x'] test = ComponentID('test') self.data.update_id(self.viewer.state.x_att, test) assert self.viewer.state.x_att is test assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:test:y:z:Coordinate components:Pixel Axis 0 [x]:World 0' def test_density_map(self): self.viewer.add_data(self.data) self.viewer.state.layers[0].points_mode = 'auto' assert len(self.viewer.layers[0].density_artist._x) == 0 self.viewer.state.layers[0].points_mode = 'density' assert len(self.viewer.layers[0].density_artist._x) == 4 self.viewer.state.layers[0].points_mode = 'markers' assert len(self.viewer.layers[0].density_artist._x) == 0 @pytest.mark.parametrize('protocol', [0, 1]) def test_session_back_compat(self, protocol): filename = os.path.join(DATA, 'scatter_v{0}.glu'.format(protocol)) with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') dc = ga.session.data_collection assert len(dc) == 1 assert dc[0].label == 'basic' viewer1 = ga.viewers[0][0] assert len(viewer1.state.layers) == 3 assert viewer1.state.x_att is dc[0].id['a'] assert viewer1.state.y_att is dc[0].id['b'] assert_allclose(viewer1.state.x_min, -1.04) assert_allclose(viewer1.state.x_max, 1.04) assert_allclose(viewer1.state.y_min, 1.98) assert_allclose(viewer1.state.y_max, 3.02) assert not viewer1.state.x_log assert not viewer1.state.y_log assert viewer1.state.layers[0].visible assert viewer1.state.layers[1].visible assert viewer1.state.layers[2].visible viewer2 = ga.viewers[0][1] assert len(viewer2.state.layers) == 3 assert viewer2.state.x_att is dc[0].id['a'] assert viewer2.state.y_att is dc[0].id['c'] assert_allclose(viewer2.state.x_min, 9.5e-6) assert_allclose(viewer2.state.x_max, 1.05) assert_allclose(viewer2.state.y_min, 0.38) assert_allclose(viewer2.state.y_max, 5.25) assert viewer2.state.x_log assert viewer2.state.y_log assert viewer2.state.layers[0].visible assert not viewer2.state.layers[1].visible assert viewer2.state.layers[2].visible viewer3 = ga.viewers[0][2] assert len(viewer3.state.layers) == 3 assert viewer3.state.x_att is dc[0].id['b'] assert viewer3.state.y_att is dc[0].id['a'] assert_allclose(viewer3.state.x_min, 0) assert_allclose(viewer3.state.x_max, 5) assert_allclose(viewer3.state.y_min, -5) assert_allclose(viewer3.state.y_max, 5) assert not viewer3.state.x_log assert not viewer3.state.y_log assert viewer3.state.layers[0].visible assert viewer3.state.layers[1].visible assert not viewer3.state.layers[2].visible def test_session_line_back_compat(self): # Backward-compatibility for v0.11 files in which the line and scatter # plots were defined as separate styles. filename = os.path.join(DATA, 'scatter_and_line_v1.glu') with open(filename, 'r') as f: session = f.read() state = GlueUnSerializer.loads(session) ga = state.object('__main__') dc = ga.session.data_collection assert len(dc) == 1 assert dc[0].label == 'table' viewer1 = ga.viewers[0][0] assert len(viewer1.state.layers) == 1 assert viewer1.state.x_att is dc[0].id['a'] assert viewer1.state.y_att is dc[0].id['b'] assert viewer1.state.layers[0].markers_visible assert not viewer1.state.layers[0].line_visible viewer1 = ga.viewers[0][1] assert len(viewer1.state.layers) == 1 assert viewer1.state.x_att is dc[0].id['a'] assert viewer1.state.y_att is dc[0].id['b'] assert not viewer1.state.layers[0].markers_visible assert viewer1.state.layers[0].line_visible def test_save_svg(self, tmpdir): # Regression test for a bug in AxesCache that caused SVG saving to # fail (because renderer.buffer_rgba did not exist) self.viewer.add_data(self.data) filename = tmpdir.join('test.svg').strpath self.viewer.axes.figure.savefig(filename) def test_2d(self): viewer_state = self.viewer.state self.viewer.add_data(self.data_2d) assert viewer_state.x_att is self.data_2d.id['a'] assert viewer_state.x_min == 1 assert viewer_state.x_max == 4 assert viewer_state.y_att is self.data_2d.id['b'] assert viewer_state.y_min == 5 assert viewer_state.y_max == 8 assert self.viewer.layers[0].plot_artist.get_xdata().shape == (4,) def test_apply_roi_single(self): # Regression test for a bug that caused mode.update to be called # multiple times and resulted in all other viewers receiving many # messages regarding subset updates (this occurred when multiple) # datasets were present. layer_tree = LayerTreeWidget() layer_tree.set_checkable(False) layer_tree.setup(self.data_collection) layer_tree.bind_selection_to_edit_subset() class Client(HubListener): def __init__(self, *args, **kwargs): super(Client, self).__init__(*args, **kwargs) self.count = Counter() def ping(self, message): self.count[message.sender] += 1 def register_to_hub(self, hub): hub.subscribe(self, SubsetUpdateMessage, handler=self.ping) d1 = Data(a=[1, 2, 3], label='d3') d2 = Data(b=[1, 2, 3], label='d4') d3 = Data(c=[1, 2, 3], label='d5') d4 = Data(d=[1, 2, 3], label='d6') self.data_collection.append(d1) self.data_collection.append(d2) self.data_collection.append(d3) self.data_collection.append(d4) client = Client() client.register_to_hub(self.hub) self.viewer.add_data(d1) self.viewer.add_data(d3) roi = XRangeROI(2.5, 3.5) self.viewer.apply_roi(roi) for subset in client.count: assert client.count[subset] == 1 @pytest.mark.parametrize('ndim', [1, 2]) def test_all_options(self, ndim): # This test makes sure that all the code for the different scatter modes # gets run, though does not check the result. viewer_state = self.viewer.state if ndim == 1: data = self.data elif ndim == 2: data = self.data_2d self.viewer.add_data(data) layer_state = viewer_state.layers[0] layer_state.style = 'Scatter' layer_state.size_mode = 'Linear' layer_state.size_att = data.id['y'] layer_state.size_vmin = 1.2 layer_state.size_vmax = 4. layer_state.size_scaling = 2 layer_state.cmap_mode = 'Linear' layer_state.cmap_att = data.id['x'] layer_state.cmap_vmin = -1 layer_state.cmap_vmax = 2. layer_state.cmap = colormaps.members[3][1] # Check inverting works layer_state.cmap_vmin = 3. layer_state.size_mode = 'Fixed' layer_state.xerr_visible = True layer_state.xerr_att = data.id['x'] layer_state.yerr_visible = True layer_state.yerr_att = data.id['y'] layer_state.style = 'Line' layer_state.linewidth = 3 layer_state.linestyle = 'dashed' 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'] 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'] def test_enable_disable_components_combo(self): # Regression test for a bug that caused an error when turning off pixel # components from combo boxes. self.viewer.add_data(self.data) self.data['a'] = self.data.id['x'] + 5 self.viewer.state.x_att_helper.pixel_coord = True self.viewer.state.x_att = self.data.pixel_component_ids[0] self.viewer.state.x_att_helper.pixel_coord = False def test_component_renamed(self): # If a component ID is renamed, this needs to be reflected in the combo self.viewer.add_data(self.data) self.data.id['x'].label = 'test' assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:test:y:z:Coordinate components:Pixel Axis 0 [x]:World 0'