-
-
Notifications
You must be signed in to change notification settings - Fork 419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
graph layer #5861
base: main
Are you sure you want to change the base?
graph layer #5861
Changes from all commits
5ba80f3
e95b3d0
c45d2ec
92d9dd9
a3efea3
891217b
37ddd96
89fb3cf
b5a0b94
d278b5a
9c8a390
fb0cb59
66057a4
149c0d8
dc4ff25
6836979
a6564c7
cc8ff0f
3c97c56
4a6a08c
7b12d4b
7ba748e
28ce0c8
4cf0931
bbe6d46
3d5370c
c43ecd8
fef8077
e1a0d32
9bbef0c
05b9aa2
eb8a90e
d07aa40
62519a5
f36c8bf
05fe4b4
a910ca9
42bdb18
90f80f9
f04520f
81e9884
6e97eb5
3775280
d2036b0
981bd70
3c9c33d
f68f4b3
6b3ef25
6d8eb92
bf509a8
2e16e74
c907ce0
98adf82
1f066bf
761c05e
fd531c4
c747c74
41eb555
b1a3482
78cc0bc
3012731
3f4cfd8
9cd8395
0b289ee
b7c1888
29063c9
c225c80
ba3beca
6fa5c54
6adaa7d
08f6f3a
ed52d96
f4ea067
1a7123f
ad70173
a580c0a
6b576a4
b2321ae
e363e96
3731322
715ae5d
f23afd2
0b39ce5
cf1f40e
540636f
b34884f
3b91999
998d9f9
5cc814c
ff6ac5c
1f94fe9
69ebbc4
bef7d9e
d608af6
d466def
ea2ac8c
5315af3
f969d6c
6f9e718
b548030
b5c03fe
7f2e917
00dc63c
2848ebb
7cb291d
8ac802d
68ad317
d055ceb
b30dc39
bbace5b
4f158af
1901fd9
bcabf32
535ceca
458c338
ac460ee
4235019
6b80268
b4ea210
43a3bbd
9246384
76cb651
a367dba
ef6e1a6
83755da
e03887a
451b87b
efd0408
16b3a37
d8d8d21
e28e0c0
9791e34
758b2f5
b5b2472
34725fc
e1998eb
fec6bbb
05395f6
c2ded23
bc60ce3
6c21fc8
31b5703
565934b
6369a31
83ec8d5
cebfffc
2f348fc
5d5830a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
""" | ||
Add networkx graph | ||
================== | ||
|
||
Add a networkx graph directly to napari. This works as long as nodes | ||
have a "pos" attribute with the node coordinate. | ||
|
||
.. tags:: visualization-basic | ||
""" | ||
|
||
import networkx as nx | ||
|
||
import napari | ||
|
||
hex_grid = nx.hexagonal_lattice_graph(5, 5, with_positions=True) | ||
# below conversion not needed after napari/napari-graph#11 is released | ||
hex_grid_ints = nx.convert_node_labels_to_integers(hex_grid) | ||
|
||
viewer = napari.Viewer() | ||
layer = viewer.add_graph(hex_grid_ints, size=1) | ||
|
||
if __name__ == '__main__': | ||
napari.run() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
""" | ||
Add graph | ||
=================== | ||
|
||
Display a random undirected graph using the graph layer. | ||
|
||
.. tags:: visualization-basic | ||
""" | ||
|
||
import numpy as np | ||
import pandas as pd | ||
from napari_graph import UndirectedGraph | ||
|
||
import napari | ||
|
||
|
||
def build_graph(n_nodes: int, n_neighbors: int) -> UndirectedGraph: | ||
neighbors = np.random.randint(n_nodes, size=(n_nodes * n_neighbors)) | ||
edges = np.stack([np.repeat(np.arange(n_nodes), n_neighbors), neighbors], axis=1) | ||
|
||
nodes_df = pd.DataFrame( | ||
400 * np.random.uniform(size=(n_nodes, 4)), | ||
columns=['t', 'z', 'y', 'x'], | ||
) | ||
graph = UndirectedGraph(edges=edges, coords=nodes_df) | ||
|
||
return graph | ||
|
||
|
||
graph = build_graph(n_nodes=1_000_000, n_neighbors=5) | ||
|
||
viewer = napari.Viewer() | ||
layer = viewer.add_graph(graph, out_of_slice_display=True) | ||
|
||
|
||
if __name__ == '__main__': | ||
|
||
napari.run() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
""" | ||
Nuclei Segmentation Graph | ||
=============== | ||
|
||
Creates a delaunay graph from maxima of cell nuclei. | ||
|
||
.. tags:: visualization-nD | ||
""" | ||
from itertools import combinations | ||
|
||
import numpy as np | ||
from napari_graph import UndirectedGraph | ||
from scipy.spatial import Delaunay | ||
from skimage import data, feature, filters | ||
|
||
import napari | ||
|
||
|
||
def delaunay_edges(points: np.ndarray) -> np.ndarray: | ||
delaunay = Delaunay(points) | ||
edges = set() | ||
for simplex in delaunay.simplices: | ||
# each simplex is represented as a list of four points. | ||
# we add all edges between the points to the edge list | ||
edges |= set(combinations(simplex, 2)) | ||
|
||
return np.asarray(list(edges)) | ||
|
||
|
||
cells = data.cells3d() | ||
|
||
nuclei = cells[:, 1] | ||
smooth = filters.gaussian(nuclei, sigma=10) | ||
nodes_coords = feature.peak_local_max(smooth) | ||
edges = delaunay_edges(nodes_coords[:, 1:]) | ||
graph = UndirectedGraph(edges, nodes_coords) | ||
viewer, image_layer = napari.imshow( | ||
cells, channel_axis=1, name=['membranes', 'nuclei'], ndisplay=3 | ||
) | ||
graph_layer = viewer.add_graph(graph) | ||
viewer.camera.angles = (10, -20, 130) | ||
|
||
if __name__ == '__main__': | ||
napari.run() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from napari._qt.layer_controls.qt_points_controls import QtPointsControls | ||
|
||
|
||
class QtGraphControls(QtPointsControls): | ||
pass | ||
JoOkuma marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
from typing import Type | ||
|
||
import numpy as np | ||
import pytest | ||
from napari_graph import ( | ||
BaseGraph, | ||
DirectedGraph, | ||
UndirectedGraph, | ||
) | ||
|
||
from napari._vispy.layers.graph import VispyGraphLayer | ||
from napari.layers import Graph | ||
|
||
|
||
@pytest.mark.parametrize('graph_class', [UndirectedGraph, DirectedGraph]) | ||
def test_vispy_graph_layer(graph_class: Type[BaseGraph]) -> None: | ||
edges = np.asarray([[0, 1], [1, 2]]) | ||
coords = np.asarray([[0, 0, 0, -1], [0, 0, 1, 2], [1, 0, 2, 3]]) | ||
|
||
graph = graph_class(edges=edges, coords=coords) | ||
|
||
layer = Graph(graph) | ||
visual = VispyGraphLayer(layer) | ||
|
||
# checking nodes positions | ||
assert np.all( | ||
coords[:2, 1:] | ||
== np.flip(visual.node._subvisuals[0]._data['a_position'], axis=-1) | ||
) | ||
|
||
# checking edges positions | ||
assert np.all( | ||
coords[:2, 2:] == np.flip(visual.node._subvisuals[4]._pos, axis=-1) | ||
) | ||
|
||
|
||
@pytest.mark.parametrize('graph_class', [UndirectedGraph, DirectedGraph]) | ||
def test_vispy_graph_layer_removal(graph_class: Type[BaseGraph]) -> None: | ||
edges = np.asarray([[0, 1], [1, 2]]) | ||
coords = np.asarray([[0, 0, 0, -1], [0, 0, 1, 2], [0, 0, 2, 3]]) | ||
|
||
graph = graph_class(edges=edges, coords=coords) | ||
|
||
layer = Graph(graph) | ||
visual = VispyGraphLayer(layer) | ||
|
||
# checking nodes positions | ||
assert np.all( | ||
coords[:, 1:] | ||
== np.flip(visual.node._subvisuals[0]._data['a_position'], axis=-1) | ||
) | ||
|
||
# checking first edge | ||
assert np.all( | ||
coords[:2, 2:] == np.flip(visual.node._subvisuals[4]._pos[:2], axis=-1) | ||
) | ||
|
||
# checking second edge | ||
assert np.all( | ||
coords[1:3, 2:] | ||
== np.flip(visual.node._subvisuals[4]._pos[2:], axis=-1) | ||
) | ||
|
||
layer.remove(0) | ||
|
||
# checking remaining nodes positions | ||
assert np.all( | ||
coords[1:, 1:] | ||
== np.flip(visual.node._subvisuals[0]._data['a_position'], axis=-1) | ||
) | ||
|
||
# checking single edge | ||
assert np.all( | ||
coords[1:3, 2:] == np.flip(visual.node._subvisuals[4]._pos, axis=-1) | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from vispy import gloo | ||
|
||
from napari._vispy.layers.points import VispyPointsLayer | ||
from napari._vispy.visuals.graph import GraphVisual | ||
|
||
|
||
class VispyGraphLayer(VispyPointsLayer): | ||
_visual = GraphVisual | ||
node: GraphVisual | ||
|
||
def _on_data_change(self) -> None: | ||
self._set_graph_edges_data() | ||
super()._on_data_change() | ||
|
||
def _set_graph_edges_data(self) -> None: | ||
"""Sets the LineVisual (subvisual[4]) with the graph edges data""" | ||
subvisual = self.node._subvisuals[4] | ||
edges = self.layer._view_edges_coordinates | ||
|
||
if len(edges) == 0: | ||
subvisual.visible = False | ||
return | ||
|
||
subvisual.visible = True | ||
flat_edges = edges.reshape((-1, edges.shape[-1])) # (N x 2, D) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would love a comment here about why we need to flatten and duplicate the edges - I think it's because the vispy visual takes both ends of the edge? Still would be good to add a comment for future, or maybe even a utility if you think it'll be used in other update callbacks |
||
flat_edges = flat_edges[:, ::-1] | ||
|
||
# clearing up buffer, there was a vispy error otherwise | ||
subvisual._line_visual._pos_vbo = gloo.VertexBuffer() | ||
subvisual.set_data( | ||
flat_edges, | ||
color='white', | ||
width=1, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import numpy as np | ||
from vispy import gloo | ||
|
||
from napari._vispy.layers.base import VispyBaseLayer | ||
from napari._vispy.utils.gl import BLENDING_MODES | ||
|
@@ -10,10 +11,11 @@ | |
|
||
|
||
class VispyPointsLayer(VispyBaseLayer): | ||
_visual = PointsVisual | ||
node: PointsVisual | ||
|
||
def __init__(self, layer) -> None: | ||
node = PointsVisual() | ||
node = self._visual() | ||
super().__init__(layer, node) | ||
|
||
self.layer.events.symbol.connect(self._on_data_change) | ||
|
@@ -136,6 +138,9 @@ def _on_highlight_change(self): | |
pos = self.layer._highlight_box | ||
width = scaled_highlight | ||
|
||
# FIXME: vispy bug? LineVisual error when going from 2d to 3d (or the opposite) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JoOkuma I'm going through to split up points changes into their own PR - is this line part of a bad merge or did you add this? If you added it, can you expand a bit more what the bug is? Maybe this should be its own tiny PR |
||
self.node.highlight_lines._line_visual._pos_vbo = gloo.VertexBuffer() | ||
|
||
self.node.highlight_lines.set_data( | ||
pos=pos[:, ::-1], | ||
color=highlight_color, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should rename to
add_graph_networkx.py
to group together with other graph examples