Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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)
Beispiel #5
0
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])
Beispiel #6
0
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)
Beispiel #7
0
    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()
Beispiel #8
0
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])
Beispiel #9
0
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])
Beispiel #10
0
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()
Beispiel #12
0
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
Beispiel #13
0
    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) & (
Beispiel #14
0
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']
Beispiel #15
0
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
Beispiel #16
0
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()
Beispiel #17
0
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()
Beispiel #19
0
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
Beispiel #20
0
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 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()
Beispiel #23
0
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 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)
Beispiel #25
0
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])
Beispiel #26
0
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(
Beispiel #28
0
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()
Beispiel #29
0
                               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)
Beispiel #30
0
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