def jglue(*args, **kwargs): """ Create a new Jupyter-based glue application. It is typically easiest to call this function without arguments and load data and add links separately in subsequent calls. However, this function can also take the same inputs as the `~glue.qglue.qglue` function. Once this function is called, it will return a `~glue_jupyter.JupyterApplication` object, which can then be used to load data, set up links, and create visualizations. See the documentation for that class for more details. """ show = kwargs.pop('show', False) from glue.core import DataCollection from glue.qglue import parse_data, parse_links from glue.core.data_factories import load_data links = kwargs.pop('links', None) dc = DataCollection() for label, data in kwargs.items(): if isinstance(data, str): data = load_data(data) dc.extend(parse_data(data, label)) for data in args: dc.append(data) if links is not None: dc.add_link(parse_links(dc, links)) japp = JupyterApplication(dc) if show: display(app) return japp
def qglue(**kwargs): """ Quickly send python variables to Glue for visualization. The generic calling sequence is:: qglue(label1=data1, label2=data2, ..., [links=links]) The kewyords label1, label2, ... can be named anything besides ``links`` data1, data2, ... can be in many formats: * A pandas data frame * A path to a file * A numpy array, or python list * A numpy rec array * A dictionary of numpy arrays with the same shape * An astropy Table ``Links`` is an optional list of link descriptions, each of which has the format: ([left_ids], [right_ids], forward, backward) Each ``left_id``/``right_id`` is a string naming a component in a dataset (i.e., ``data1.x``). ``forward`` and ``backward`` are functions which map quantities on the left to quantities on the right, and vice versa. `backward` is optional Examples:: balls = {'kg': [1, 2, 3], 'radius_cm': [10, 15, 30]} cones = {'lbs': [5, 3, 3, 1]} def lb2kg(lb): return lb / 2.2 def kg2lb(kg): return kg * 2.2 links = [(['balls.kg'], ['cones.lbs'], lb2kg, kg2lb)] qglue(balls=balls, cones=cones, links=links) :returns: A :class:`~glue.app.qt.application.GlueApplication` object """ from glue.core import DataCollection from glue.app.qt import GlueApplication from glue.dialogs.autolinker.qt import run_autolinker links = kwargs.pop('links', None) dc = DataCollection() for label, data in kwargs.items(): dc.extend(parse_data(data, label)) if links is not None: dc.add_link(parse_links(dc, links)) with restore_io(): ga = GlueApplication(dc) run_autolinker(dc) ga.start() return ga
def qglue(**kwargs): """ Quickly send python variables to Glue for visualization. The generic calling sequence is:: qglue(label1=data1, label2=data2, ..., [links=links]) The kewyords label1, label2, ... can be named anything besides ``links`` data1, data2, ... can be in many formats: * A pandas data frame * A path to a file * A numpy array, or python list * A numpy rec array * A dictionary of numpy arrays with the same shape * An astropy Table ``Links`` is an optional list of link descriptions, each of which has the format: ([left_ids], [right_ids], forward, backward) Each ``left_id``/``right_id`` is a string naming a component in a dataset (i.e., ``data1.x``). ``forward`` and ``backward`` are functions which map quantities on the left to quantities on the right, and vice versa. `backward` is optional Examples:: balls = {'kg': [1, 2, 3], 'radius_cm': [10, 15, 30]} cones = {'lbs': [5, 3, 3, 1]} def lb2kg(lb): return lb / 2.2 def kg2lb(kg): return kg * 2.2 links = [(['balls.kg'], ['cones.lbs'], lb2kg, kg2lb)] qglue(balls=balls, cones=cones, links=links) :returns: A :class:`~glue.app.qt.application.GlueApplication` object """ from glue.core import DataCollection from glue.app.qt import GlueApplication links = kwargs.pop('links', None) dc = DataCollection() for label, data in kwargs.items(): dc.extend(parse_data(data, label)) if links is not None: dc.add_link(parse_links(dc, links)) with restore_io(): ga = GlueApplication(dc) ga.start() return ga
def test_1d_world_link(): x, y = r(10), r(10) d1 = Data(label='d1', x=x) d2 = Data(label='d2', y=y) dc = DataCollection([d1, d2]) dc.add_link(LinkSame(d2.get_world_component_id(0), d1.id['x'])) assert d2.get_world_component_id(0) in d1.components np.testing.assert_array_equal(d1[d2.get_world_component_id(0)], x) np.testing.assert_array_equal(d1[d2.get_pixel_component_id(0)], x)
def test_link_aligned(ndim): shp = tuple([2] * ndim) data1 = Data(test=np.random.random(shp)) data2 = Data(test=np.random.random(shp)) links = LinkAligned(data1, data2) dc = DataCollection([data1, data2]) dc.add_link(links) for i in range(ndim): id0 = data1.pixel_component_ids[i] id1 = data2.pixel_component_ids[i] np.testing.assert_array_equal(data1[id0], data2[id1])
def test_2d_world_link(): """Should be able to grab pixel coords after linking world""" x, y = r(10), r(10) cat = Data(label='cat', x=x, y=y) im = Data(label='im', inten=r((3, 3))) dc = DataCollection([cat, im]) dc.add_link(LinkSame(im.get_world_component_id(0), cat.id['x'])) dc.add_link(LinkSame(im.get_world_component_id(1), cat.id['y'])) np.testing.assert_array_equal(cat[im.get_pixel_component_id(0)], x) np.testing.assert_array_equal(cat[im.get_pixel_component_id(1)], y)
def test_preexisting_links_twodata(self): # Regression test for an issue that occurred specifically if there were # exactly two datasets and pre-existing links (since this means that # the window opens with a current_link selected by default) data1 = Data(x=[1, 2, 3], y=[2, 3, 4], z=[6, 5, 4], label='data1') data2 = Data(a=[2, 3, 4], b=[4, 5, 4], c=[3, 4, 1], label='data2') data_collection = DataCollection([data1, data2]) link1 = ComponentLink([data1.id['x']], data2.id['c']) data_collection.add_link(link1) dialog = LinkEditor(data_collection) dialog.show() dialog.accept()
def test_link_aligned(ndata, ndim): ds = [] shp = tuple([2] * ndim) for i in range(ndata): d = Data() c = Component(np.random.random(shp)) d.add_component(c, 'test') ds.append(d) # assert that all componentIDs are interchangeable links = LinkAligned(ds) dc = DataCollection(ds) dc.add_link(links) for i in range(ndim): id0 = ds[0].pixel_component_ids[i] for j in range(1, ndata): id1 = ds[j].pixel_component_ids[i] np.testing.assert_array_equal(ds[j][id0], ds[j][id1])
def test_link_aligned(ndata, ndim): ds = [] shp = tuple([2] * ndim) for i in range(ndata): d = Data() c = Component(np.random.random(shp)) d.add_component(c, 'test') ds.append(d) # assert that all componentIDs are interchangeable links = LinkAligned(ds) dc = DataCollection(ds) dc.add_link(links) for i in range(ndim): id0 = ds[0].get_pixel_component_id(i) for j in range(1, ndata): id1 = ds[j].get_pixel_component_id(i) np.testing.assert_array_equal(ds[j][id0], ds[j][id1])
def jglue(*args, **kwargs): from glue.core import DataCollection from glue.app.qt import GlueApplication from glue.qglue import parse_data, parse_links from glue.core.data_factories import load_data links = kwargs.pop('links', None) dc = DataCollection() for label, data in kwargs.items(): if isinstance(data, six.string_types): data = load_data(data) dc.extend(parse_data(data, label)) for data in args: dc.append(data) if links is not None: dc.add_link(parse_links(dc, links)) japp = JupyterApplication(dc) return japp
def test_scatter_on_volume(tmpdir): data1 = Data(a=np.arange(60).reshape((3, 4, 5))) data2 = Data(x=[1, 2, 3], y=[2, 3, 4], z=[3, 4, 5]) data3 = Data(b=np.arange(60).reshape((3, 4, 5))) dc = DataCollection([data1, data2, data3]) dc.add_link(LinkSame(data1.pixel_component_ids[2], data2.id['x'])) dc.add_link(LinkSame(data1.pixel_component_ids[1], data2.id['y'])) dc.add_link(LinkSame(data1.pixel_component_ids[0], data2.id['z'])) ga = GlueApplication(dc) ga.show() volume = ga.new_data_viewer(VispyVolumeViewer) volume.add_data(data1) volume.add_data(data2) volume.add_data(data3) # Check that writing a session works as expected. session_file = tmpdir.join('test_scatter_on_volume.glu').strpath ga.save_session(session_file) ga.close() # Now we can check that everything is restored correctly ga2 = GlueApplication.restore_session(session_file) ga2.show() volume_r = ga2.viewers[0][0] assert len(volume_r.layers) == 3 ga2.close()
class TestLinkEditor: def setup_method(self, method): self.data1 = Data(x=[1, 2, 3], y=[2, 3, 4], z=[6, 5, 4], label='data1') self.data2 = Data(a=[2, 3, 4], b=[4, 5, 4], c=[3, 4, 1], label='data2') self.data3 = Data(i=[5, 4, 3], j=[2, 2, 1], label='data3') self.data_collection = DataCollection([self.data1, self.data2, self.data3]) def test_defaults(self): # Make sure the dialog opens and closes and check default settings. dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget assert link_widget.state.data1 is None assert link_widget.state.data2 is None assert not link_widget.button_add_link.isEnabled() assert not link_widget.button_remove_link.isEnabled() link_widget.state.data1 = self.data2 assert not link_widget.button_add_link.isEnabled() assert not link_widget.button_remove_link.isEnabled() link_widget.state.data2 = self.data1 assert link_widget.button_add_link.isEnabled() assert link_widget.button_remove_link.isEnabled() dialog.accept() assert len(self.data_collection.external_links) == 0 def test_defaults_two(self): # Make sure the dialog opens and closes and check default settings. With # two datasets, the datasets should be selected by default. self.data_collection.remove(self.data3) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data2 assert link_widget.button_add_link.isEnabled() assert link_widget.button_remove_link.isEnabled() dialog.accept() assert len(self.data_collection.external_links) == 0 def test_ui_behavior(self): # This is a bit more detailed test that checks that things update # correctly as we change various settings app = get_qapp() dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 add_identity_link = get_action(link_widget, 'identity') add_lengths_volume_link = get_action(link_widget, 'lengths_to_volume') # At this point, there should be no links in the main list widget # and nothing on the right. assert link_widget.listsel_current_link.count() == 0 assert link_widget.link_details.text() == '' assert link_widget.link_io.itemAt(0) is None # Let's add an identity link add_identity_link.trigger() # Ensure that all events get processed app.processEvents() # Now there should be one link in the main list and content in the # right hand panel. assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a' # Let's change the current components for the link link_widget.state.current_link.x = self.data1.id['y'] link_widget.state.current_link.y = self.data2.id['b'] # and make sure the UI gets updated assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # We now add another link of a different type add_lengths_volume_link.trigger() # Ensure that all events get processed app.processEvents() # and make sure the UI has updated assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Convert between linear measurements and volume' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a' # Now switch back to the first link link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[0] # and make sure the UI updates and has preserved the correct settings assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # Next up, we try changing the data link_widget.state.data1 = self.data3 # At this point there should be no links in the list assert link_widget.listsel_current_link.count() == 0 assert link_widget.link_details.text() == '' assert link_widget.link_io.itemAt(0) is None # Add another identity link add_identity_link.trigger() # Ensure that all events get processed app.processEvents() # Now there should be one link in the main list assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a' # Switch back to the original data link_widget.state.data1 = self.data1 # And check the output is as before assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # Let's now remove this link link_widget.button_remove_link.click() # Ensure that all events get processed app.processEvents() # We should now see the lengths/volume link assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Convert between linear measurements and volume' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a' dialog.accept() links = self.data_collection.external_links assert len(links) == 2 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_from_ids()[1] is self.data1.id['y'] assert links[0].get_from_ids()[2] is self.data1.id['z'] assert links[0].get_to_id() is self.data2.id['a'] assert isinstance(links[1], ComponentLink) assert links[1].get_from_ids()[0] is self.data3.id['i'] assert links[1].get_to_id() is self.data2.id['a'] def test_graph(self): dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget add_identity_link = get_action(link_widget, 'identity') graph = link_widget.graph_widget def click(node_or_edge): # We now simulate a selection - since we can't deterministically # figure out the exact pixel coordinates to use, we patch # 'find_object' to return the object we want to select. with patch.object(graph, 'find_object', return_value=node_or_edge): graph.mousePressEvent(None) def hover(node_or_edge): # Same as for select, we patch find_object with patch.object(graph, 'find_object', return_value=node_or_edge): graph.mouseMoveEvent(None) # To start with, no data should be selected assert link_widget.state.data1 is None assert link_widget.state.data2 is None # and the graph should have three nodes and no edges assert len(graph.nodes) == 3 assert len(graph.edges) == 0 click(graph.nodes[0]) # Check that this has caused one dataset to be selected assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is None # Click on the same node again and this should deselect the data # (but only once we move off from the node) click(graph.nodes[0]) assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is None hover(None) assert link_widget.state.data1 is None assert link_widget.state.data2 is None # Select it again click(graph.nodes[0]) # and now select another node too click(graph.nodes[1]) assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data2 assert len(graph.nodes) == 3 assert len(graph.edges) == 0 add_identity_link.trigger() assert len(graph.nodes) == 3 assert len(graph.edges) == 1 # Unselect and select another node click(graph.nodes[1]) click(graph.nodes[2]) # and check the data selections have been updated assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data3 # Deselect it and move off click(graph.nodes[2]) hover(None) # and the second dataset should now once again be None assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is None # Now change the data manually link_widget.state.data1 = self.data2 link_widget.state.data2 = self.data3 # and check that if we select the edge the datasets change back click(graph.edges[0]) assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data2 # Unselect and hover over nothing click(graph.edges[0]) hover(None) assert link_widget.state.data1 is None assert link_widget.state.data2 is None # Hover over the edge and the datasets should change back hover(graph.edges[0]) assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data2 # And check that clicking outside of nodes/edges deselects everything click(None) assert link_widget.state.data1 is None assert link_widget.state.data2 is None # Select a node, select another, then make sure that selecting a third # one will deselect the two original ones click(graph.nodes[0]) click(graph.nodes[1]) click(graph.nodes[2]) assert link_widget.state.data1 is self.data3 assert link_widget.state.data2 is None dialog.accept() def test_preexisting_links(self): # Check that things work properly if there are pre-existing links app = get_qapp() link1 = ComponentLink([self.data1.id['x']], self.data2.id['c']) def add(x, y): return x + y def double(x): return x * 2 def halve(x): return x / 2 link2 = ComponentLink([self.data2.id['a'], self.data2.id['b']], self.data3.id['j'], using=add) link3 = ComponentLink([self.data3.id['i']], self.data2.id['c'], using=double, inverse=halve) self.data_collection.add_link(link1) self.data_collection.add_link(link2) self.data_collection.add_link(link3) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == '' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c' link_widget.state.data1 = self.data3 assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == '' assert non_empty_rows_count(get_link_io(link_widget)) == 6 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'a' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'b' assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'j' link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[1] assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == '' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c' dialog.accept() links = self.data_collection.external_links assert len(links) == 3 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_to_id() is self.data2.id['c'] assert links[0].get_using() is identity assert isinstance(links[1], ComponentLink) assert links[1].get_from_ids()[0] is self.data2.id['a'] assert links[1].get_from_ids()[1] is self.data2.id['b'] assert links[1].get_to_id() is self.data3.id['j'] assert links[1].get_using() is add assert isinstance(links[2], ComponentLink) assert links[2].get_from_ids()[0] is self.data3.id['i'] assert links[2].get_to_id() is self.data2.id['c'] assert links[2].get_using() is double assert links[2].get_inverse() is halve def test_add_helper(self): app = get_qapp() dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 add_coordinate_link = get_action(link_widget, 'ICRS <-> Galactic') # Add a coordinate link add_coordinate_link.trigger() # Ensure that all events get processed app.processEvents() assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link ICRS and Galactic coordinates' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'a' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b' dialog.accept() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], ICRS_to_Galactic) assert links[0].cids1[0] is self.data1.id['x'] assert links[0].cids1[1] is self.data1.id['y'] assert links[0].cids2[0] is self.data2.id['a'] assert links[0].cids2[1] is self.data2.id['b'] def test_preexisting_helper(self): app = get_qapp() link1 = Galactic_to_FK5(cids1=[self.data1.id['x'], self.data1.id['y']], cids2=[self.data2.id['c'], self.data2.id['b']]) self.data_collection.add_link(link1) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget assert link_widget.listsel_current_link.count() == 0 link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link Galactic and FK5 (J2000) Equatorial coordinates' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'c' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b' dialog.accept() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], Galactic_to_FK5) assert links[0].cids1[0] is self.data1.id['x'] assert links[0].cids1[1] is self.data1.id['y'] assert links[0].cids2[0] is self.data2.id['c'] assert links[0].cids2[1] is self.data2.id['b'] def test_cancel(self): # Make sure that changes aren't saved if dialog is cancelled # This is a bit more detailed test that checks that things update # correctly as we change various settings app = get_qapp() link1 = ComponentLink([self.data1.id['x']], self.data2.id['c']) self.data_collection.add_link(link1) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 link_widget.state.current_link.x = self.data1.id['y'] assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' add_identity_link = get_action(link_widget, 'identity') add_identity_link.trigger() assert link_widget.listsel_current_link.count() == 2 dialog.reject() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_to_id() is self.data2.id['c'] def test_functional_link_collection(self): # Test that if we use a @link_helper in 'legacy' mode, i.e. with only # input labels, both datasets are available from the combos in the # link editor dialog. Also test the new-style @link_helper. app = get_qapp() def deg_arcsec(degree, arcsecond): return [ComponentLink([degree], arcsecond, using=lambda d: d * 3600), ComponentLink([arcsecond], degree, using=lambda a: a / 3600)] # Old-style link helper helper1 = functional_link_collection(deg_arcsec, description='Legacy link', labels1=['deg', 'arcsec'], labels2=[]) link1 = helper1(cids1=[self.data1.id['x'], self.data2.id['c']]) self.data_collection.add_link(link1) # New-style link helper helper2 = functional_link_collection(deg_arcsec, description='New-style link', labels1=['deg'], labels2=['arcsec']) link2 = helper2(cids1=[self.data1.id['x']], cids2=[self.data2.id['c']]) self.data_collection.add_link(link2) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget assert link_widget.listsel_current_link.count() == 0 link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Legacy link' assert non_empty_rows_count(get_link_io(link_widget)) == 4 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'c' link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[1] assert link_widget.link_details.text() == 'New-style link' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c' dialog.accept() links = self.data_collection.external_links assert len(links) == 2 assert isinstance(links[0], helper1) assert links[0].cids1[0] is self.data1.id['x'] assert links[0].cids1[1] is self.data2.id['c'] assert isinstance(links[1], helper2) assert links[1].cids1[0] is self.data1.id['x'] assert links[1].cids2[0] is self.data2.id['c'] def test_same_data(self): # Test that we can't set the same data twice app = get_qapp() dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.state.data1 == self.data1 assert link_widget.state.data2 == self.data2 link_widget.state.data1 = self.data2 assert link_widget.state.data1 == self.data2 assert link_widget.state.data2 == self.data1 link_widget.state.data2 = self.data2 assert link_widget.state.data1 == self.data1 assert link_widget.state.data2 == self.data2
apath('fitted_line_parameters_Chi2Constraints.ipac'), format='ascii.ipac') catalog.label = 'FittedLineParameters' catalog.style.color = 'green' catalog.style.marker = 'o' cube = load_data(hpath('APEX_H2CO_303_202_bl.fits')) cube.label = 'H2CO 303/202' cube2 = load_data(molpath('APEX_SiO_54.fits')) cube2.label = 'SiO' cube3 = load_data(hpath('APEX_13CO_matched_H2CO.fits')) cube3.label = '13CO' higaltem = load_data('/Users/adam/work/gc/gcmosaic_temp_conv36.fits') dc = DataCollection([cube, catalog, cube2, cube3, higaltem]) dc.merge(cube, cube2, cube3) dc.add_link(LinkSame(cube.id['Galactic Longitude'], catalog.id['GLON'])) dc.add_link(LinkSame(cube.id['Galactic Latitude'], catalog.id['GLAT'])) def ms_to_kms(x): return x / 1e3 def kms_to_ms(x): return x * 1e3 dc.add_link( LinkTwoWay(cube.id['Vrad'], catalog.id['center'], ms_to_kms, kms_to_ms)) subset_tem_lt_60 = (catalog.id['temperature_chi2'] < 60) & (
dc = DataCollection(dendrogram) #dc = DataCollection([cube, dendrogram, catalog]) #dc.merge(cube,sncube) #sncube.join_on_key(dendro, 'structure', dendro.pixel_component_ids[0]) #dc.merge(catalog, dendro) # UNCOMMENT THIS LINE TO BREAK THE VIEWER dc.append(catalog) app = GlueApplication(dc) cube_viewer = app.new_data_viewer(ImageWidget) cube_viewer.add_data(sncube) # link positional information dc.add_link(LinkSame(sncube.id['structure'], catalog.id['_idx'])) #dc.add_link(LinkSame(image.id['World y: DEC--TAN'], catalog.id['DEJ2000'])) dc.add_link(LinkSame(cube.id['Galactic Longitude'], catalog.id['x_cen'])) dc.add_link(LinkSame(cube.id['Galactic Latitude'], catalog.id['y_cen'])) def ms_to_kms(x): return x/1e3 def kms_to_ms(x): return x*1e3 dc.add_link(LinkTwoWay(cube.id['Vrad'], catalog.id['v_cen'], ms_to_kms, kms_to_ms)) scatter = app.new_data_viewer(ScatterWidget) scatter.add_data(catalog) scatter.yatt = catalog.id['temperature_chi2'] scatter.xatt = catalog.id['area_exact']
def _load_data_collection(rec, context): dc = DataCollection(list(map(context.object, rec['data']))) for link in rec['links']: dc.add_link(context.object(link)) coerce_subset_groups(dc) return dc
from glue.core.data_factories import load_data from glue.core import DataCollection from glue.core.link_helpers import LinkSame from glue.app.qt.application import GlueApplication #load 2 datasets from files image = load_data('w5.fits') catalog = load_data('w5_psc.vot') dc = DataCollection([image, catalog]) # link positional information dc.add_link(LinkSame(image.id['Right Ascension'], catalog.id['RAJ2000'])) dc.add_link(LinkSame(image.id['Declination'], catalog.id['DEJ2000'])) #start Glue app = GlueApplication(dc) app.start()
class TestComponentManagerWidget: def setup_method(self): self.data1 = Data(x=[1, 2, 3], y=[3.5, 4.5, -1.0], z=['a', 'r', 'w']) self.data2 = Data(a=[3, 4, 1], b=[1.5, -2.0, 3.5], c=['y', 'e', 'r']) # Add a derived component so that we can test how we deal with existing ones components = dict((cid.label, cid) for cid in self.data2.components) pc = ParsedCommand('{a}', components) link = ParsedComponentLink(ComponentID('d'), pc) self.data2.add_component_link(link) self.data_collection = DataCollection([self.data1, self.data2]) link = ComponentLink([self.data1.id['x']], self.data2.id['a']) self.data_collection.add_link(link) self.listener1 = ChangeListener(self.data1) self.listener2 = ChangeListener(self.data2) def test_nochanges(self): self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() self.manager.button_ok.click() self.listener1.assert_exact_changes() self.listener2.assert_exact_changes() def test_remove(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list)[0] self.manager.list.select_item(item) self.manager.button_remove_main.click() self.manager.button_ok.click() self.listener1.assert_exact_changes(removed=[x_cid]) self.listener2.assert_exact_changes() def test_rename_valid(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list)[0] item.setText(0, 'newname') self.manager.button_ok.click() assert self.manager.result() == 1 self.listener1.assert_exact_changes(renamed=[x_cid]) self.listener2.assert_exact_changes() assert x_cid.label == 'newname' assert_equal(self.data1['newname'], [1, 2, 3]) def test_rename_invalid(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list)[0] item.setText(0, 'y') assert not self.manager.button_ok.isEnabled() assert self.manager.ui.label_status.text() == 'Error: some components have duplicate names' item = list(self.manager.list)[0] item.setText(0, 'a') assert self.manager.button_ok.isEnabled() assert self.manager.ui.label_status.text() == '' self.manager.button_ok.click() self.listener1.assert_exact_changes(renamed=[x_cid]) self.listener2.assert_exact_changes() assert x_cid.label == 'a' assert_equal(self.data1['a'], [1, 2, 3])
class TestArithmeticEditorWidget: def setup_method(self): self.data1 = Data(x=[1, 2, 3], y=[3.5, 4.5, -1.0], z=['a', 'r', 'w']) self.data2 = Data(a=[3, 4, 1], b=[1.5, -2.0, 3.5], c=['y', 'e', 'r']) # Add a derived component so that we can test how we deal with existing ones components = dict((cid.label, cid) for cid in self.data2.components) pc = ParsedCommand('{a}', components) link = ParsedComponentLink(ComponentID('d'), pc) self.data2.add_component_link(link) self.data_collection = DataCollection([self.data1, self.data2]) link = ComponentLink([self.data1.id['x']], self.data2.id['a']) self.data_collection.add_link(link) self.listener1 = ChangeListener(self.data1) self.listener2 = ChangeListener(self.data2) def test_nochanges(self): editor = ArithmeticEditorWidget(self.data_collection) editor.show() editor.button_ok.click() self.listener1.assert_exact_changes() self.listener2.assert_exact_changes() editor.close() def test_add_derived_and_rename(self): editor = ArithmeticEditorWidget(self.data_collection) editor.show() with patch.object(EquationEditorDialog, 'exec_', auto_accept('{x} + {y}')): editor.button_add_derived.click() item = list(editor.list)[0] item.setText(0, 'new') editor.button_ok.click() self.listener1.assert_exact_changes(added=[self.data1.id['new']]) self.listener2.assert_exact_changes() assert_equal(self.data1['new'], [4.5, 6.5, 2.0]) editor.close() def test_add_derived_and_cancel(self): editor = ArithmeticEditorWidget(self.data_collection) editor.show() with patch.object(EquationEditorDialog, 'exec_', auto_reject()): editor.button_add_derived.click() assert len(editor.list) == 0 editor.close() def test_edit_existing_equation(self): assert_equal(self.data2['d'], [3, 4, 1]) editor = ArithmeticEditorWidget(self.data_collection) editor.show() assert len(editor.list) == 0 editor.combosel_data.setCurrentIndex(1) assert len(editor.list) == 1 editor.list.select_cid(self.data2.id['d']) with patch.object(EquationEditorDialog, 'exec_', auto_accept('{a} + {b}')): editor.button_edit_derived.click() editor.button_ok.click() self.listener1.assert_exact_changes() self.listener2.assert_exact_changes(numerical=True) assert_equal(self.data2['d'], [4.5, 2.0, 4.5]) editor.close()
class TestLinkEditor: def setup_method(self, method): self.data1 = Data(x=[1, 2, 3], y=[2, 3, 4], z=[6, 5, 4], label='data1') self.data2 = Data(a=[2, 3, 4], b=[4, 5, 4], c=[3, 4, 1], label='data2') self.data3 = Data(i=[5, 4, 3], j=[2, 2, 1], label='data3') self.data_collection = DataCollection([self.data1, self.data2, self.data3]) @pytest.mark.parametrize('accept', [False, True]) def test_basic(self, accept): # Set up an existing link link1 = ComponentLink([self.data1.id['x']], self.data2.id['c']) self.data_collection.add_link(link1) # Set up two suggested links def add(x, y): return x + y def double(x): return x * 2 def halve(x): return x / 2 link2 = ComponentLink([self.data2.id['a'], self.data2.id['b']], self.data3.id['j'], using=add) link3 = ComponentLink([self.data3.id['i']], self.data2.id['c'], using=double, inverse=halve) suggested_links = [link2, link3] dialog = AutoLinkPreview('test autolinker', self.data_collection, suggested_links) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.listsel_current_link.count() == 1 link_widget.state.data1 = self.data3 assert link_widget.listsel_current_link.count() == 2 if accept: dialog.accept() links = self.data_collection.external_links assert len(links) == 3 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_to_id() is self.data2.id['c'] assert links[0].get_using() is identity assert isinstance(links[1], ComponentLink) assert links[1].get_from_ids()[0] is self.data2.id['a'] assert links[1].get_from_ids()[1] is self.data2.id['b'] assert links[1].get_to_id() is self.data3.id['j'] assert links[1].get_using() is add assert isinstance(links[2], ComponentLink) assert links[2].get_from_ids()[0] is self.data3.id['i'] assert links[2].get_to_id() is self.data2.id['c'] assert links[2].get_using() is double assert links[2].get_inverse() is halve else: dialog.reject() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_to_id() is self.data2.id['c'] assert links[0].get_using() is identity
class TestReprojection(): def setup_method(self, method): self.data_collection = DataCollection() self.array = np.arange(3024).reshape((6, 7, 8, 9)) # The reference dataset. Shape is (6, 7, 8, 9). self.data1 = Data(x=self.array) self.data_collection.append(self.data1) # A dataset with the same shape but not linked. Shape is (6, 7, 8, 9). self.data2 = Data(x=self.array) self.data_collection.append(self.data2) # A dataset with the same number of dimesnions but in a different # order, linked to the first. Shape is (9, 7, 6, 8). self.data3 = Data(x=np.moveaxis(self.array, (3, 1, 0, 2), (0, 1, 2, 3))) self.data_collection.append(self.data3) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[0], self.data3.pixel_component_ids[2])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[1], self.data3.pixel_component_ids[1])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[2], self.data3.pixel_component_ids[3])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[3], self.data3.pixel_component_ids[0])) # A dataset with fewer dimensions, linked to the first one. Shape is # (8, 7, 6) self.data4 = Data(x=self.array[:, :, :, 0].transpose()) self.data_collection.append(self.data4) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[0], self.data4.pixel_component_ids[2])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[1], self.data4.pixel_component_ids[1])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[2], self.data4.pixel_component_ids[0])) # A dataset with even fewer dimensions, linked to the first one. Shape # is (8, 6) self.data5 = Data(x=self.array[:, 0, :, 0].transpose()) self.data_collection.append(self.data5) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[0], self.data5.pixel_component_ids[1])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[2], self.data5.pixel_component_ids[0])) # A dataset that is not on the same pixel grid and requires reprojection self.data6 = Data() self.data6.coords = SimpleCoordinates() self.array_nonaligned = np.arange(60).reshape((5, 3, 4)) self.data6['x'] = np.array(self.array_nonaligned) self.data_collection.append(self.data6) self.data_collection.add_link(LinkSame(self.data1.world_component_ids[0], self.data6.world_component_ids[1])) self.data_collection.add_link(LinkSame(self.data1.world_component_ids[1], self.data6.world_component_ids[2])) self.data_collection.add_link(LinkSame(self.data1.world_component_ids[2], self.data6.world_component_ids[0])) self.viewer_state = ImageViewerState() self.viewer_state.layers.append(ImageLayerState(viewer_state=self.viewer_state, layer=self.data1)) self.viewer_state.layers.append(ImageLayerState(viewer_state=self.viewer_state, layer=self.data2)) self.viewer_state.layers.append(ImageLayerState(viewer_state=self.viewer_state, layer=self.data3)) self.viewer_state.layers.append(ImageLayerState(viewer_state=self.viewer_state, layer=self.data4)) self.viewer_state.layers.append(ImageLayerState(viewer_state=self.viewer_state, layer=self.data5)) self.viewer_state.layers.append(ImageLayerState(viewer_state=self.viewer_state, layer=self.data6)) self.viewer_state.reference_data = self.data1 def test_default_axis_order(self): # Start off with a combination of x/y that means that only one of the # other datasets will be matched. self.viewer_state.x_att = self.data1.pixel_component_ids[3] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (3, 2, 4, 1) image = self.viewer_state.layers[0].get_sliced_data() assert_equal(image, self.array[3, 2, :, :]) with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[1].get_sliced_data() image = self.viewer_state.layers[2].get_sliced_data() assert_equal(image, self.array[3, 2, :, :]) with pytest.raises(IncompatibleDataException): self.viewer_state.layers[3].get_sliced_data() with pytest.raises(IncompatibleDataException): self.viewer_state.layers[4].get_sliced_data() def test_transpose_axis_order(self): # Next make it so the x/y axes correspond to the dimensions with length # 6 and 8 which most datasets will be compatible with, and this also # requires a tranposition. self.viewer_state.x_att = self.data1.pixel_component_ids[0] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (3, 2, 4, 1) image = self.viewer_state.layers[0].get_sliced_data() print(image.shape) assert_equal(image, self.array[:, 2, :, 1].transpose()) with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[1].get_sliced_data() image = self.viewer_state.layers[2].get_sliced_data() print(image.shape) assert_equal(image, self.array[:, 2, :, 1].transpose()) image = self.viewer_state.layers[3].get_sliced_data() assert_equal(image, self.array[:, 2, :, 0].transpose()) image = self.viewer_state.layers[4].get_sliced_data() assert_equal(image, self.array[:, 0, :, 0].transpose()) def test_transpose_axis_order_view(self): # As for the previous test, but this time with a view applied self.viewer_state.x_att = self.data1.pixel_component_ids[0] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (3, 2, 4, 1) view = [slice(1, None, 2), slice(None, None, 3)] image = self.viewer_state.layers[0].get_sliced_data(view=view) assert_equal(image, self.array[::3, 2, 1::2, 1].transpose()) with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[1].get_sliced_data(view=view) image = self.viewer_state.layers[2].get_sliced_data(view=view) print(image.shape) assert_equal(image, self.array[::3, 2, 1::2, 1].transpose()) image = self.viewer_state.layers[3].get_sliced_data(view=view) assert_equal(image, self.array[::3, 2, 1::2, 0].transpose()) image = self.viewer_state.layers[4].get_sliced_data(view=view) assert_equal(image, self.array[::3, 0, 1::2, 0].transpose()) def test_reproject(self): # Test a case where the data needs to actually be reprojected # As for the previous test, but this time with a view applied self.viewer_state.x_att = self.data1.pixel_component_ids[0] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (3, 2, 4, 1) view = [slice(1, None, 2), slice(None, None, 3)] actual = self.viewer_state.layers[5].get_sliced_data(view=view) # The data to be reprojected is 3-dimensional. The axes we have set # correspond to 1 (for x) and 0 (for y). The third dimension of the # data to be reprojected should be sliced. This is linked with the # second dimension of the original data, for which the slice index is # 2. Since the data to be reprojected has coordinates that are 2.5 times # those of the reference data, this means the slice index should be 0.8, # which rounded corresponds to 1. expected = self.array_nonaligned[:, :, 1] # Now in the frame of the reference data, the data to show are indices # [0, 3] along x and [1, 3, 5, 7] along y. Applying the transformation, # this gives values of [0, 1.2] and [0.4, 1.2, 2, 2.8] for x and y, # and rounded, this gives [0, 1] and [0, 1, 2, 3]. As a reminder, in the # data to reproject, dimension 0 is y and dimension 1 is x expected = expected[:4, :2] # Let's make sure this works! assert_equal(actual, expected) def test_too_many_dimensions(self): # If we change the reference data, then the first dataset won't be # visible anymore because it has too many dimensions self.viewer_state.reference_data = self.data4 with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[0].get_sliced_data() self.viewer_state.reference_data = self.data6 with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[0].get_sliced_data() def test_reset_pixel_cache(self): # Test to make sure that resetting the pixel cache works properly self.viewer_state.x_att = self.data1.pixel_component_ids[0] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (1, 1, 1, 1) layer = self.viewer_state.layers[5] assert layer._pixel_cache is None layer.get_sliced_data() assert layer._pixel_cache['reset_slices'][0] == [False, False, True, False] assert layer._pixel_cache['reset_slices'][1] == [True, True, False, False] assert layer._pixel_cache['reset_slices'][2] == [True, True, False, False] self.viewer_state.slices = (1, 1, 1, 2) assert layer._pixel_cache['reset_slices'][0] == [False, False, True, False] assert layer._pixel_cache['reset_slices'][1] == [True, True, False, False] assert layer._pixel_cache['reset_slices'][2] == [True, True, False, False] self.viewer_state.slices = (1, 2, 1, 2) assert layer._pixel_cache['reset_slices'][0] == [False, False, True, False] assert layer._pixel_cache['reset_slices'][1] is None assert layer._pixel_cache['reset_slices'][2] is None self.viewer_state.slices = (1, 2, 2, 2) assert layer._pixel_cache['reset_slices'][0] is None assert layer._pixel_cache['reset_slices'][1] is None assert layer._pixel_cache['reset_slices'][2] is None
class TestFixedResolutionBuffer(): def setup_method(self, method): self.data_collection = DataCollection() # The reference dataset. Shape is (6, 7, 8, 9). self.data1 = Data(x=ARRAY) self.data_collection.append(self.data1) # A dataset with the same shape but not linked. Shape is (6, 7, 8, 9). self.data2 = Data(x=ARRAY) self.data_collection.append(self.data2) # A dataset with the same number of dimensions but in a different # order, linked to the first. Shape is (9, 7, 6, 8). self.data3 = Data(x=np.moveaxis(ARRAY, (3, 1, 0, 2), (0, 1, 2, 3))) self.data_collection.append(self.data3) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[0], self.data3.pixel_component_ids[2])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[1], self.data3.pixel_component_ids[1])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[2], self.data3.pixel_component_ids[3])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[3], self.data3.pixel_component_ids[0])) # A dataset with fewer dimensions, linked to the first one. Shape is # (8, 7, 6) self.data4 = Data(x=ARRAY[:, :, :, 0].transpose()) self.data_collection.append(self.data4) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[0], self.data4.pixel_component_ids[2])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[1], self.data4.pixel_component_ids[1])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[2], self.data4.pixel_component_ids[0])) # A dataset with even fewer dimensions, linked to the first one. Shape # is (8, 6) self.data5 = Data(x=ARRAY[:, 0, :, 0].transpose()) self.data_collection.append(self.data5) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[0], self.data5.pixel_component_ids[1])) self.data_collection.add_link(LinkSame(self.data1.pixel_component_ids[2], self.data5.pixel_component_ids[0])) # A dataset that is not on the same pixel grid and requires reprojection # self.data6 = Data() # self.data6.coords = SimpleCoordinates() # self.array_nonaligned = np.arange(60).reshape((5, 3, 4)) # self.data6['x'] = np.array(self.array_nonaligned) # self.data_collection.append(self.data6) # self.data_collection.add_link(LinkSame(self.data1.world_component_ids[0], # self.data6.world_component_ids[1])) # self.data_collection.add_link(LinkSame(self.data1.world_component_ids[1], # self.data6.world_component_ids[2])) # self.data_collection.add_link(LinkSame(self.data1.world_component_ids[2], # self.data6.world_component_ids[0])) # Start off with the cases where the data is the target data. Enumerate # the different cases for the bounds and the expected result. DATA_IS_TARGET_CASES = [ # Bounds are full extent of data ([(0, 5, 6), (0, 6, 7), (0, 7, 8), (0, 8, 9)], ARRAY), # Bounds are inside data ([(2, 3, 2), (3, 3, 1), (0, 7, 8), (0, 7, 8)], ARRAY[2:4, 3:4, :, :8]), # Bounds are outside data along some dimensions ([(-5, 9, 15), (3, 5, 3), (0, 9, 10), (5, 6, 2)], np.pad(ARRAY[:, 3:6, :, 5:7], [(5, 4), (0, 0), (0, 2), (0, 0)], mode='constant', constant_values=-np.inf)), # No overlap ([(2, 3, 2), (3, 3, 1), (-5, -4, 2), (0, 7, 8)], -np.inf * np.ones((2, 1, 2, 8))) ] @pytest.mark.parametrize(('bounds', 'expected'), DATA_IS_TARGET_CASES) def test_data_is_target_full_bounds(self, bounds, expected): buffer = self.data1.compute_fixed_resolution_buffer(target_data=self.data1, bounds=bounds, target_cid=self.data1.id['x']) assert_equal(buffer, expected) buffer = self.data3.compute_fixed_resolution_buffer(target_data=self.data1, bounds=bounds, target_cid=self.data3.id['x']) assert_equal(buffer, expected)
class TestReprojection(): def setup_method(self, method): self.data_collection = DataCollection() self.array = np.arange(3024).reshape((6, 7, 8, 9)) # The reference dataset. Shape is (6, 7, 8, 9). self.data1 = Data(x=self.array) self.data_collection.append(self.data1) # A dataset with the same shape but not linked. Shape is (6, 7, 8, 9). self.data2 = Data(x=self.array) self.data_collection.append(self.data2) # A dataset with the same number of dimesnions but in a different # order, linked to the first. Shape is (9, 7, 6, 8). self.data3 = Data( x=np.moveaxis(self.array, (3, 1, 0, 2), (0, 1, 2, 3))) self.data_collection.append(self.data3) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[0], self.data3.pixel_component_ids[2])) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[1], self.data3.pixel_component_ids[1])) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[2], self.data3.pixel_component_ids[3])) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[3], self.data3.pixel_component_ids[0])) # A dataset with fewer dimensions, linked to the first one. Shape is # (8, 7, 6) self.data4 = Data(x=self.array[:, :, :, 0].transpose()) self.data_collection.append(self.data4) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[0], self.data4.pixel_component_ids[2])) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[1], self.data4.pixel_component_ids[1])) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[2], self.data4.pixel_component_ids[0])) # A dataset with even fewer dimensions, linked to the first one. Shape # is (8, 6) self.data5 = Data(x=self.array[:, 0, :, 0].transpose()) self.data_collection.append(self.data5) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[0], self.data5.pixel_component_ids[1])) self.data_collection.add_link( LinkSame(self.data1.pixel_component_ids[2], self.data5.pixel_component_ids[0])) # A dataset that is not on the same pixel grid and requires reprojection self.data6 = Data() self.data6.coords = SimpleCoordinates() self.array_nonaligned = np.arange(60).reshape((5, 3, 4)) self.data6['x'] = np.array(self.array_nonaligned) self.data_collection.append(self.data6) self.data_collection.add_link( LinkSame(self.data1.world_component_ids[0], self.data6.world_component_ids[1])) self.data_collection.add_link( LinkSame(self.data1.world_component_ids[1], self.data6.world_component_ids[2])) self.data_collection.add_link( LinkSame(self.data1.world_component_ids[2], self.data6.world_component_ids[0])) self.viewer_state = ImageViewerState() self.viewer_state.layers.append( ImageLayerState(viewer_state=self.viewer_state, layer=self.data1)) self.viewer_state.layers.append( ImageLayerState(viewer_state=self.viewer_state, layer=self.data2)) self.viewer_state.layers.append( ImageLayerState(viewer_state=self.viewer_state, layer=self.data3)) self.viewer_state.layers.append( ImageLayerState(viewer_state=self.viewer_state, layer=self.data4)) self.viewer_state.layers.append( ImageLayerState(viewer_state=self.viewer_state, layer=self.data5)) self.viewer_state.layers.append( ImageLayerState(viewer_state=self.viewer_state, layer=self.data6)) self.viewer_state.reference_data = self.data1 def test_default_axis_order(self): # Start off with a combination of x/y that means that only one of the # other datasets will be matched. self.viewer_state.x_att = self.data1.pixel_component_ids[3] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (3, 2, 4, 1) image = self.viewer_state.layers[0].get_sliced_data() assert_equal(image, self.array[3, 2, :, :]) with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[1].get_sliced_data() image = self.viewer_state.layers[2].get_sliced_data() assert_equal(image, self.array[3, 2, :, :]) with pytest.raises(IncompatibleDataException): self.viewer_state.layers[3].get_sliced_data() with pytest.raises(IncompatibleDataException): self.viewer_state.layers[4].get_sliced_data() def test_transpose_axis_order(self): # Next make it so the x/y axes correspond to the dimensions with length # 6 and 8 which most datasets will be compatible with, and this also # requires a tranposition. self.viewer_state.x_att = self.data1.pixel_component_ids[0] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (3, 2, 4, 1) image = self.viewer_state.layers[0].get_sliced_data() print(image.shape) assert_equal(image, self.array[:, 2, :, 1].transpose()) with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[1].get_sliced_data() image = self.viewer_state.layers[2].get_sliced_data() print(image.shape) assert_equal(image, self.array[:, 2, :, 1].transpose()) image = self.viewer_state.layers[3].get_sliced_data() assert_equal(image, self.array[:, 2, :, 0].transpose()) image = self.viewer_state.layers[4].get_sliced_data() assert_equal(image, self.array[:, 0, :, 0].transpose()) def test_transpose_axis_order_view(self): # As for the previous test, but this time with a view applied self.viewer_state.x_att = self.data1.pixel_component_ids[0] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (3, 2, 4, 1) view = [slice(1, None, 2), slice(None, None, 3)] image = self.viewer_state.layers[0].get_sliced_data(view=view) assert_equal(image, self.array[::3, 2, 1::2, 1].transpose()) with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[1].get_sliced_data(view=view) image = self.viewer_state.layers[2].get_sliced_data(view=view) print(image.shape) assert_equal(image, self.array[::3, 2, 1::2, 1].transpose()) image = self.viewer_state.layers[3].get_sliced_data(view=view) assert_equal(image, self.array[::3, 2, 1::2, 0].transpose()) image = self.viewer_state.layers[4].get_sliced_data(view=view) assert_equal(image, self.array[::3, 0, 1::2, 0].transpose()) def test_reproject(self): # Test a case where the data needs to actually be reprojected # As for the previous test, but this time with a view applied self.viewer_state.x_att = self.data1.pixel_component_ids[0] self.viewer_state.y_att = self.data1.pixel_component_ids[2] self.viewer_state.slices = (3, 2, 4, 1) view = [slice(1, None, 2), slice(None, None, 3)] actual = self.viewer_state.layers[5].get_sliced_data(view=view) # The data to be reprojected is 3-dimensional. The axes we have set # correspond to 1 (for x) and 0 (for y). The third dimension of the # data to be reprojected should be sliced. This is linked with the # second dimension of the original data, for which the slice index is # 2. Since the data to be reprojected has coordinates that are 2.5 times # those of the reference data, this means the slice index should be 0.8, # which rounded corresponds to 1. expected = self.array_nonaligned[:, :, 1] # Now in the frame of the reference data, the data to show are indices # [0, 3] along x and [1, 3, 5, 7] along y. Applying the transformation, # this gives values of [0, 1.2] and [0.4, 1.2, 2, 2.8] for x and y, # and rounded, this gives [0, 1] and [0, 1, 2, 3]. As a reminder, in the # data to reproject, dimension 0 is y and dimension 1 is x expected = expected[:4, :2] # Let's make sure this works! assert_equal(actual, expected) def test_too_many_dimensions(self): # If we change the reference data, then the first dataset won't be # visible anymore because it has too many dimensions self.viewer_state.reference_data = self.data4 with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[0].get_sliced_data() self.viewer_state.reference_data = self.data6 with pytest.raises(IncompatibleAttribute): self.viewer_state.layers[0].get_sliced_data()
class TestComponentManagerWidget: def setup_method(self): self.app = get_qapp() self.data1 = Data(x=[1, 2, 3], y=[3.5, 4.5, -1.0], z=['a', 'r', 'w']) self.data2 = Data(a=[3, 4, 1], b=[1.5, -2.0, 3.5], c=['y', 'e', 'r']) # Add a derived component so that we can test how we deal with existing ones components = dict((cid.label, cid) for cid in self.data2.components) pc = ParsedCommand('{a}', components) link = ParsedComponentLink(ComponentID('d'), pc) self.data2.add_component_link(link) self.data_collection = DataCollection([self.data1, self.data2]) link = ComponentLink([self.data1.id['x']], self.data2.id['a']) self.data_collection.add_link(link) self.listener1 = ChangeListener(self.data1) self.listener2 = ChangeListener(self.data2) def test_nochanges(self): self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() self.manager.button_ok.click() self.listener1.assert_exact_changes() self.listener2.assert_exact_changes() def test_remove(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list['main'])[0] self.manager.list['main'].select_item(item) self.manager.button_remove_main.click() self.manager.button_ok.click() self.listener1.assert_exact_changes(removed=[x_cid]) self.listener2.assert_exact_changes() def test_rename_valid(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list['main'])[0] item.setText(0, 'newname') self.manager.button_ok.click() assert self.manager.result() == 1 self.listener1.assert_exact_changes(renamed=[x_cid]) self.listener2.assert_exact_changes() assert x_cid.label == 'newname' assert_equal(self.data1['newname'], [1, 2, 3]) def test_rename_invalid(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list['main'])[0] item.setText(0, 'y') assert not self.manager.button_ok.isEnabled() assert self.manager.ui.label_status.text( ) == 'Error: some components have duplicate names' item = list(self.manager.list['main'])[0] item.setText(0, 'a') assert self.manager.button_ok.isEnabled() assert self.manager.ui.label_status.text() == '' self.manager.button_ok.click() self.listener1.assert_exact_changes(renamed=[x_cid]) self.listener2.assert_exact_changes() assert x_cid.label == 'a' assert_equal(self.data1['a'], [1, 2, 3]) def test_add_derived_and_rename(self): self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() with patch.object(EquationEditorDialog, 'exec_', auto_accept('{x} + {y}')): self.manager.button_add_derived.click() item = list(self.manager.list['derived'])[0] item.setText(0, 'new') self.manager.button_ok.click() self.listener1.assert_exact_changes(added=[self.data1.id['new']]) self.listener2.assert_exact_changes() assert_equal(self.data1['new'], [4.5, 6.5, 2.0]) def test_add_derived_and_cancel(self): self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() with patch.object(EquationEditorDialog, 'exec_', auto_reject()): self.manager.button_add_derived.click() assert len(self.manager.list['derived']) == 0 def test_edit_existing_equation(self): assert_equal(self.data2['d'], [3, 4, 1]) self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() assert len(self.manager.list['derived']) == 0 self.manager.combosel_data.setCurrentIndex(1) assert len(self.manager.list['derived']) == 1 self.manager.list['derived'].select_cid(self.data2.id['d']) with patch.object(EquationEditorDialog, 'exec_', auto_accept('{a} + {b}')): self.manager.button_edit_derived.click() self.manager.button_ok.click() self.listener1.assert_exact_changes() self.listener2.assert_exact_changes(numerical=True) assert_equal(self.data2['d'], [4.5, 2.0, 4.5]) def test_edit_equation_after_rename(self): self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() self.manager.combosel_data.setCurrentIndex(1) self.manager.list['main'].select_cid(self.data2.id['a']) self.manager.list['main'].selected_item.setText(0, 'renamed') self.manager.list['derived'].select_cid(self.data2.id['d']) with patch.object(EquationEditorDialog, 'exec_', auto_accept('{renamed} + 1')): self.manager.button_edit_derived.click() self.manager.button_ok.click() self.listener1.assert_exact_changes() self.listener2.assert_exact_changes(renamed=[self.data2.id['renamed']], numerical=True) assert_equal(self.data2['d'], [4, 5, 2])
class TestComponentManagerWidget: def setup_method(self): self.data1 = Data(x=[1, 2, 3], y=[3.5, 4.5, -1.0], z=['a', 'r', 'w']) self.data2 = Data(a=[3, 4, 1], b=[1.5, -2.0, 3.5], c=['y', 'e', 'r']) # Add a derived component so that we can test how we deal with existing ones components = dict((cid.label, cid) for cid in self.data2.components) pc = ParsedCommand('{a}', components) link = ParsedComponentLink(ComponentID('d'), pc) self.data2.add_component_link(link) self.data_collection = DataCollection([self.data1, self.data2]) link = ComponentLink([self.data1.id['x']], self.data2.id['a']) self.data_collection.add_link(link) self.listener1 = ChangeListener(self.data1) self.listener2 = ChangeListener(self.data2) def test_nochanges(self): self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() self.manager.button_ok.click() self.listener1.assert_exact_changes() self.listener2.assert_exact_changes() def test_remove(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list)[0] self.manager.list.select_item(item) self.manager.button_remove_main.click() self.manager.button_ok.click() self.listener1.assert_exact_changes(removed=[x_cid]) self.listener2.assert_exact_changes() def test_rename_valid(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list)[0] item.setText(0, 'newname') self.manager.button_ok.click() assert self.manager.result() == 1 self.listener1.assert_exact_changes(renamed=[x_cid]) self.listener2.assert_exact_changes() assert x_cid.label == 'newname' assert_equal(self.data1['newname'], [1, 2, 3]) def test_rename_invalid(self): x_cid = self.data1.id['x'] self.manager = ComponentManagerWidget(self.data_collection) self.manager.show() item = list(self.manager.list)[0] item.setText(0, 'y') assert not self.manager.button_ok.isEnabled() assert self.manager.ui.label_status.text( ) == 'Error: some components have duplicate names' item = list(self.manager.list)[0] item.setText(0, 'a') assert self.manager.button_ok.isEnabled() assert self.manager.ui.label_status.text() == '' self.manager.button_ok.click() self.listener1.assert_exact_changes(renamed=[x_cid]) self.listener2.assert_exact_changes() assert x_cid.label == 'a' assert_equal(self.data1['a'], [1, 2, 3])
dc = DataCollection(dendrogram) #dc = DataCollection([cube, dendrogram, catalog]) #dc.merge(cube,sncube) #sncube.join_on_key(dendro, 'structure', dendro.pixel_component_ids[0]) #dc.merge(catalog, dendro) # UNCOMMENT THIS LINE TO BREAK THE VIEWER dc.append(catalog) app = GlueApplication(dc) cube_viewer = app.new_data_viewer(ImageWidget) cube_viewer.add_data(sncube) # link positional information dc.add_link(LinkSame(sncube.id['structure'], catalog.id['_idx'])) #dc.add_link(LinkSame(image.id['World y: DEC--TAN'], catalog.id['DEJ2000'])) dc.add_link(LinkSame(cube.id['Galactic Longitude'], catalog.id['x_cen'])) dc.add_link(LinkSame(cube.id['Galactic Latitude'], catalog.id['y_cen'])) def ms_to_kms(x): return x / 1e3 def kms_to_ms(x): return x * 1e3 dc.add_link(
from glue.core.data_factories import load_data from glue.core import DataCollection from glue.core.link_helpers import LinkSame from glue.qt.glue_application import GlueApplication #load 2 datasets from files image = load_data('w5.fits') catalog = load_data('w5_psc.vot') dc = DataCollection([image, catalog]) # link positional information dc.add_link(LinkSame(image.id['World x: RA---TAN'], catalog.id['RAJ2000'])) dc.add_link(LinkSame(image.id['World y: DEC--TAN'], catalog.id['DEJ2000'])) #start Glue app = GlueApplication(dc) app.start()
format='ascii.ipac') catalog.label='FittedLineParameters' catalog.style.color = 'green' catalog.style.marker = 'o' cube = load_data(hpath('APEX_H2CO_303_202_bl.fits')) cube.label='H2CO 303/202' cube2 = load_data(molpath('APEX_SiO_54.fits')) cube2.label='SiO' cube3 = load_data(hpath('APEX_13CO_matched_H2CO.fits')) cube3.label='13CO' higaltem = load_data('/Users/adam/work/gc/gcmosaic_temp_conv36.fits') dc = DataCollection([cube, catalog, cube2, cube3, higaltem]) dc.merge(cube,cube2,cube3) dc.add_link(LinkSame(cube.id['Galactic Longitude'], catalog.id['GLON'])) dc.add_link(LinkSame(cube.id['Galactic Latitude'], catalog.id['GLAT'])) def ms_to_kms(x): return x/1e3 def kms_to_ms(x): return x*1e3 dc.add_link(LinkTwoWay(cube.id['Vrad'], catalog.id['center'], ms_to_kms, kms_to_ms)) subset_tem_lt_60 = (catalog.id['temperature_chi2'] < 60) & (catalog.id['temperature_chi2'] > 10) & (catalog.id['area'] < 0.015) subset_tem_gt_60 = (catalog.id['temperature_chi2'] > 60) & (catalog.id['area'] < 0.015) app = GlueApplication(dc) # plot x vs y, flip the x axis, log-scale y axis scatter = app.new_data_viewer(ScatterWidget)
class TestLinkEditor: def setup_method(self, method): self.data1 = Data(x=[1, 2, 3], y=[2, 3, 4], z=[6, 5, 4], label='data1') self.data2 = Data(a=[2, 3, 4], b=[4, 5, 4], c=[3, 4, 1], label='data2') self.data3 = Data(i=[5, 4, 3], j=[2, 2, 1], label='data3') self.data_collection = DataCollection([self.data1, self.data2, self.data3]) def test_defaults(self): # Make sure the dialog opens and closes and check default settings. dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget assert link_widget.state.data1 is None assert link_widget.state.data2 is None assert not link_widget.button_add_link.isEnabled() assert not link_widget.button_remove_link.isEnabled() link_widget.state.data1 = self.data2 assert not link_widget.button_add_link.isEnabled() assert not link_widget.button_remove_link.isEnabled() link_widget.state.data2 = self.data1 assert link_widget.button_add_link.isEnabled() assert link_widget.button_remove_link.isEnabled() dialog.accept() assert len(self.data_collection.external_links) == 0 def test_defaults_two(self): # Make sure the dialog opens and closes and check default settings. With # two datasets, the datasets should be selected by default. self.data_collection.remove(self.data3) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data2 assert link_widget.button_add_link.isEnabled() assert link_widget.button_remove_link.isEnabled() dialog.accept() assert len(self.data_collection.external_links) == 0 def test_ui_behavior(self): # This is a bit more detailed test that checks that things update # correctly as we change various settings dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 add_identity_link = get_action(link_widget, 'identity') add_lengths_volume_link = get_action(link_widget, 'lengths_to_volume') # At this point, there should be no links in the main list widget # and nothing on the right. assert link_widget.listsel_current_link.count() == 0 assert link_widget.link_details.text() == '' assert link_widget.link_io.itemAt(0) is None # Let's add an identity link add_identity_link.trigger() # Ensure that all events get processed process_events() # Now there should be one link in the main list and content in the # right hand panel. assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a' # Let's change the current components for the link link_widget.state.current_link.x = self.data1.id['y'] link_widget.state.current_link.y = self.data2.id['b'] # and make sure the UI gets updated assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # We now add another link of a different type add_lengths_volume_link.trigger() # Ensure that all events get processed process_events() # and make sure the UI has updated assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Convert between linear measurements and volume' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a' # Now switch back to the first link link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[0] # and make sure the UI updates and has preserved the correct settings assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # Next up, we try changing the data link_widget.state.data1 = self.data3 # At this point there should be no links in the list assert link_widget.listsel_current_link.count() == 0 assert link_widget.link_details.text() == '' assert link_widget.link_io.itemAt(0) is None # Add another identity link add_identity_link.trigger() # Ensure that all events get processed process_events() # Now there should be one link in the main list assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a' # Switch back to the original data link_widget.state.data1 = self.data1 # And check the output is as before assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Link conceptually identical components' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b' # Let's now remove this link link_widget.button_remove_link.click() # Ensure that all events get processed process_events() # We should now see the lengths/volume link assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Convert between linear measurements and volume' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a' dialog.accept() links = self.data_collection.external_links assert len(links) == 2 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_from_ids()[1] is self.data1.id['y'] assert links[0].get_from_ids()[2] is self.data1.id['z'] assert links[0].get_to_id() is self.data2.id['a'] assert isinstance(links[1], ComponentLink) assert links[1].get_from_ids()[0] is self.data3.id['i'] assert links[1].get_to_id() is self.data2.id['a'] def test_graph(self): dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget add_identity_link = get_action(link_widget, 'identity') graph = link_widget.graph_widget def click(node_or_edge): # We now simulate a selection - since we can't deterministically # figure out the exact pixel coordinates to use, we patch # 'find_object' to return the object we want to select. with patch.object(graph, 'find_object', return_value=node_or_edge): graph.mousePressEvent(None) def hover(node_or_edge): # Same as for select, we patch find_object with patch.object(graph, 'find_object', return_value=node_or_edge): graph.mouseMoveEvent(None) # To start with, no data should be selected assert link_widget.state.data1 is None assert link_widget.state.data2 is None # and the graph should have three nodes and no edges assert len(graph.nodes) == 3 assert len(graph.edges) == 0 click(graph.nodes[0]) # Check that this has caused one dataset to be selected assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is None # Click on the same node again and this should deselect the data # (but only once we move off from the node) click(graph.nodes[0]) assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is None hover(None) assert link_widget.state.data1 is None assert link_widget.state.data2 is None # Select it again click(graph.nodes[0]) # and now select another node too click(graph.nodes[1]) assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data2 assert len(graph.nodes) == 3 assert len(graph.edges) == 0 add_identity_link.trigger() assert len(graph.nodes) == 3 assert len(graph.edges) == 1 # Unselect and select another node click(graph.nodes[1]) click(graph.nodes[2]) # and check the data selections have been updated assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data3 # Deselect it and move off click(graph.nodes[2]) hover(None) # and the second dataset should now once again be None assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is None # Now change the data manually link_widget.state.data1 = self.data2 link_widget.state.data2 = self.data3 # and check that if we select the edge the datasets change back click(graph.edges[0]) assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data2 # Unselect and hover over nothing click(graph.edges[0]) hover(None) assert link_widget.state.data1 is None assert link_widget.state.data2 is None # Hover over the edge and the datasets should change back hover(graph.edges[0]) assert link_widget.state.data1 is self.data1 assert link_widget.state.data2 is self.data2 # And check that clicking outside of nodes/edges deselects everything click(None) assert link_widget.state.data1 is None assert link_widget.state.data2 is None # Select a node, select another, then make sure that selecting a third # one will deselect the two original ones click(graph.nodes[0]) click(graph.nodes[1]) click(graph.nodes[2]) assert link_widget.state.data1 is self.data3 assert link_widget.state.data2 is None dialog.accept() def test_preexisting_links(self): # Check that things work properly if there are pre-existing links link1 = ComponentLink([self.data1.id['x']], self.data2.id['c']) def add(x, y): return x + y def double(x): return x * 2 def halve(x): return x / 2 link2 = ComponentLink([self.data2.id['a'], self.data2.id['b']], self.data3.id['j'], using=add) link3 = ComponentLink([self.data3.id['i']], self.data2.id['c'], using=double, inverse=halve) self.data_collection.add_link(link1) self.data_collection.add_link(link2) self.data_collection.add_link(link3) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == '' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c' link_widget.state.data1 = self.data3 assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == '' assert non_empty_rows_count(get_link_io(link_widget)) == 6 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'a' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'b' assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'j' link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[1] assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == '' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c' dialog.accept() links = self.data_collection.external_links assert len(links) == 3 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_to_id() is self.data2.id['c'] assert links[0].get_using() is identity assert isinstance(links[1], ComponentLink) assert links[1].get_from_ids()[0] is self.data2.id['a'] assert links[1].get_from_ids()[1] is self.data2.id['b'] assert links[1].get_to_id() is self.data3.id['j'] assert links[1].get_using() is add assert isinstance(links[2], ComponentLink) assert links[2].get_from_ids()[0] is self.data3.id['i'] assert links[2].get_to_id() is self.data2.id['c'] assert links[2].get_using() is double assert links[2].get_inverse() is halve def test_add_helper(self): dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 add_coordinate_link = get_action(link_widget, 'ICRS <-> Galactic') # Add a coordinate link add_coordinate_link.trigger() # Ensure that all events get processed process_events() assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link ICRS and Galactic coordinates' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'a' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b' dialog.accept() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], ICRS_to_Galactic) assert links[0].cids1[0] is self.data1.id['x'] assert links[0].cids1[1] is self.data1.id['y'] assert links[0].cids2[0] is self.data2.id['a'] assert links[0].cids2[1] is self.data2.id['b'] def test_preexisting_helper(self): link1 = Galactic_to_FK5(cids1=[self.data1.id['x'], self.data1.id['y']], cids2=[self.data2.id['c'], self.data2.id['b']]) self.data_collection.add_link(link1) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget assert link_widget.listsel_current_link.count() == 0 link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.listsel_current_link.count() == 1 assert link_widget.link_details.text() == 'Link Galactic and FK5 (J2000) Equatorial coordinates' assert non_empty_rows_count(get_link_io(link_widget)) == 7 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y' assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'c' assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b' dialog.accept() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], Galactic_to_FK5) assert links[0].cids1[0] is self.data1.id['x'] assert links[0].cids1[1] is self.data1.id['y'] assert links[0].cids2[0] is self.data2.id['c'] assert links[0].cids2[1] is self.data2.id['b'] def test_cancel(self): # Make sure that changes aren't saved if dialog is cancelled # This is a bit more detailed test that checks that things update # correctly as we change various settings link1 = ComponentLink([self.data1.id['x']], self.data2.id['c']) self.data_collection.add_link(link1) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 link_widget.state.current_link.x = self.data1.id['y'] assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y' add_identity_link = get_action(link_widget, 'identity') add_identity_link.trigger() assert link_widget.listsel_current_link.count() == 2 dialog.reject() links = self.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], ComponentLink) assert links[0].get_from_ids()[0] is self.data1.id['x'] assert links[0].get_to_id() is self.data2.id['c'] def test_functional_link_collection(self): # Test that if we use a @link_helper in 'legacy' mode, i.e. with only # input labels, both datasets are available from the combos in the # link editor dialog. Also test the new-style @link_helper. def deg_arcsec(degree, arcsecond): return [ComponentLink([degree], arcsecond, using=lambda d: d * 3600), ComponentLink([arcsecond], degree, using=lambda a: a / 3600)] # Old-style link helper helper1 = functional_link_collection(deg_arcsec, description='Legacy link', labels1=['deg', 'arcsec'], labels2=[]) link1 = helper1(cids1=[self.data1.id['x'], self.data2.id['c']]) self.data_collection.add_link(link1) # New-style link helper helper2 = functional_link_collection(deg_arcsec, description='New-style link', labels1=['deg'], labels2=['arcsec']) link2 = helper2(cids1=[self.data1.id['x']], cids2=[self.data2.id['c']]) self.data_collection.add_link(link2) dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget assert link_widget.listsel_current_link.count() == 0 link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.listsel_current_link.count() == 2 assert link_widget.link_details.text() == 'Legacy link' assert non_empty_rows_count(get_link_io(link_widget)) == 4 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'c' link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[1] assert link_widget.link_details.text() == 'New-style link' assert non_empty_rows_count(get_link_io(link_widget)) == 5 assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x' assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c' dialog.accept() links = self.data_collection.external_links assert len(links) == 2 assert isinstance(links[0], helper1) assert links[0].cids1[0] is self.data1.id['x'] assert links[0].cids1[1] is self.data2.id['c'] assert isinstance(links[1], helper2) assert links[1].cids1[0] is self.data1.id['x'] assert links[1].cids2[0] is self.data2.id['c'] def test_same_data(self): # Test that we can't set the same data twice dialog = LinkEditor(self.data_collection) dialog.show() link_widget = dialog.link_widget link_widget.state.data1 = self.data1 link_widget.state.data2 = self.data2 assert link_widget.state.data1 == self.data1 assert link_widget.state.data2 == self.data2 link_widget.state.data1 = self.data2 assert link_widget.state.data1 == self.data2 assert link_widget.state.data2 == self.data1 link_widget.state.data2 = self.data2 assert link_widget.state.data1 == self.data1 assert link_widget.state.data2 == self.data2