Skip to content
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

Added segment picking from faces #428

Merged
merged 6 commits into from
Oct 16, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 110 additions & 8 deletions pyneuroml/plot/PlotMorphologyVispy.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
from vispy.scene.visuals import Mesh
from vispy.scene.widgets.viewbox import ViewBox
from vispy.util.transforms import rotate
from vispy.visuals.filters import ShadingFilter
from vispy.visuals.filters import FacePickingFilter, ShadingFilter

if app.Application.is_interactive(app):
pynml_in_jupyter = True
Expand Down Expand Up @@ -901,7 +901,12 @@ def plot_interactive_3D(
if not nogui:
if pbar is not None:
pbar.finish()
create_mesh(meshdata, current_view, save_mesh_to=save_mesh_to)
create_mesh(
meshdata,
current_view,
save_mesh_to=save_mesh_to,
current_canvas=current_canvas,
)
if pynml_in_jupyter:
display(current_canvas)
else:
Expand Down Expand Up @@ -1174,13 +1179,22 @@ def plot_3D_cell_morphology(
seg_color = segment_spec["marker_color"]

if offset is not None:
meshdata.append((f"{r1}", f"{r2}", f"{length}", p, d, seg_color, offset))
meshdata.append(
(f"{r1}", f"{r2}", f"{length}", p, d, seg_color, seg.id, cell, offset)
)
else:
meshdata.append((f"{r1}", f"{r2}", f"{length}", p, d, seg_color))
meshdata.append(
(f"{r1}", f"{r2}", f"{length}", p, d, seg_color, seg.id, cell)
)
logger.debug(f"meshdata added: {meshdata[-1]}")

if not nogui:
create_mesh(meshdata, current_view, save_mesh_to=save_mesh_to)
create_mesh(
meshdata,
current_view,
save_mesh_to=save_mesh_to,
current_canvas=current_canvas,
)
if pynml_in_jupyter:
display(current_canvas)
else:
Expand Down Expand Up @@ -1414,7 +1428,12 @@ def plot_3D_schematic(
)

if not nogui:
create_mesh(meshdata, current_view, save_mesh_to=save_mesh_to)
create_mesh(
meshdata,
current_view,
save_mesh_to=save_mesh_to,
current_canvas=current_canvas,
)
if pynml_in_jupyter:
display(current_canvas)
else:
Expand Down Expand Up @@ -1568,22 +1587,28 @@ def create_mesh(
Point3DWithDiam,
Point3DWithDiam,
Union[str, Tuple[float, float, float]],
int,
Cell,
Optional[Tuple[float, float, float]],
]
],
current_view: ViewBox,
save_mesh_to: Optional[str],
current_canvas: scene.SceneCanvas,
):
"""Internal function to create a mesh from the mesh data

See: https://vispy.org/api/vispy.scene.visuals.html#vispy.scene.visuals.Mesh

:param meshdata: meshdata to plot: list with:
[(r1, r2, length, prox, dist, color, offset)]
[(r1, r2, length, prox, dist, color, seg id, cell object offset)]
:type meshdata: list of tuples
:param current_view: vispy viewbox to use
:type current_view: ViewBox
:param save_mesh_to: name of file to save mesh object to
:type save_mesh_to: str or None
:param scene: vispy scene object
:type scene: scene.SceneCanvas
"""
mesh_start = time.time()
total_mesh_instances = len(meshdata)
Expand All @@ -1597,6 +1622,8 @@ def create_mesh(
num_vertices = 0
main_mesh_faces = []
main_mesh_colors = []
# dictionary storing faces as keys and corresponding segment ids as values
faces_to_segment = {}

pbar = progressbar.ProgressBar(
max_value=total_mesh_instances,
Expand All @@ -1611,7 +1638,14 @@ def create_mesh(
prox = d[3]
dist = d[4]
color = d[5]
offset = d[6]
if len(d) >= 8:
seg_id = d[6]
cell = d[7]
offset = d[8]
else:
seg_id = None
cell = None
offset = d[6]
if offset is None:
offset = (0.0, 0.0, 0.0)

Expand Down Expand Up @@ -1682,6 +1716,10 @@ def create_mesh(
)
translated_vertices = rotated_vertices + translator
main_mesh_faces.append(seg_mesh.get_faces() + num_vertices)
# Faces to segments
if seg_id is not None:
for face in seg_mesh.get_faces() + num_vertices:
faces_to_segment[tuple(face)] = seg_id

main_mesh_vertices.append(translated_vertices)
main_mesh_colors.append([[*color, 1]] * len(vertices))
Expand All @@ -1694,6 +1732,10 @@ def create_mesh(
translated_vertices = vertices + translator

main_mesh_faces.append(seg_mesh.get_faces() + num_vertices)
# Faces to segments
if seg_id is not None:
for face in seg_mesh.get_faces() + num_vertices:
faces_to_segment[tuple(face)] = seg_id

main_mesh_vertices.append(translated_vertices)
main_mesh_colors.append([[*color, 1]] * len(vertices))
Expand All @@ -1707,6 +1749,7 @@ def create_mesh(
numpy_mesh_vertices = numpy.concatenate(main_mesh_vertices, axis=0)
numpy_mesh_faces = numpy.concatenate(main_mesh_faces, axis=0)
numpy_mesh_colors = numpy.concatenate(main_mesh_colors, axis=0)
face_colors = numpy.tile((0.5, 0.0, 0.5, 1.0), (len(numpy_mesh_faces), 1))

logger.debug(f"Vertices: {numpy_mesh_vertices.shape}")
logger.debug(f"Faces: {numpy_mesh_faces.shape}")
Expand All @@ -1716,7 +1759,10 @@ def create_mesh(
faces=numpy_mesh_faces,
parent=current_view.scene,
vertex_colors=numpy_mesh_colors,
face_colors=face_colors.copy(),
)
mesh.interactive = True

assert mesh is not None
pbar.finish()
mesh_end = time.time()
Expand Down Expand Up @@ -1745,7 +1791,9 @@ def create_mesh(
specular_light=(1, 1, 1, 0.5),
light_dir=light_dir[:3],
)
face_picking_filter = FacePickingFilter()
mesh.attach(shading_filter)
mesh.attach(face_picking_filter)

def attach_headlight(current_view):
shading_filter.light_dir = light_dir[:3]
Expand All @@ -1756,6 +1804,45 @@ def on_transform_change(event):
transform = current_view.camera.transform
shading_filter.light_dir = transform.map(initial_light_dir)[:3]

# For handling mouse press
# currently identifies the segment and prints some information out to the
# terminal about it.
@current_canvas.events.mouse_press.connect
def on_mouse_press(event):
clicked_mesh = current_canvas.visual_at(event.pos)
if isinstance(clicked_mesh, Mesh) and seg_id is not None:
# adjust the event position for hidpi screens
render_size = tuple(
d * current_canvas.pixel_scale for d in current_canvas.size
)
x_pos = event.pos[0] * current_canvas.pixel_scale
y_pos = render_size[1] - (event.pos[1] * current_canvas.pixel_scale)

# render a small patch around the mouse cursor
restore_state = not face_picking_filter.enabled
face_picking_filter.enabled = True
mesh.update_gl_state(blend=False)
picking_render = current_canvas.render(
region=(x_pos - 1, y_pos - 1, 3, 3),
size=(3, 3),
bgcolor=(0, 0, 0, 0),
alpha=True,
)
if restore_state:
face_picking_filter.enabled = False
mesh.update_gl_state(blend=not face_picking_filter.enabled)

# unpack the face index from the color in the center pixel
face_idx = (picking_render.view(numpy.uint32) - 1)[1, 1, 0]
picked_face = tuple(mesh._meshdata._faces[face_idx])
picked_seg_id = faces_to_segment[picked_face]

logger.debug(f"face id is: {face_idx}")
logger.debug(f"face is: {picked_face}")
logger.debug(f"corresponding segment is: {picked_seg_id}")

clicked_on_seg(picked_seg_id, cell)

attach_headlight(current_view)

if save_mesh_to is not None:
Expand All @@ -1774,3 +1861,18 @@ def on_transform_change(event):
texcoords=None,
overwrite=False,
)


def clicked_on_seg(seg_id: int, cell: Cell):
"""
Callback function called when a segment is clicked on.

Prints information about the segment.

:param seg_id: id of segment
:type seg_id: int
:param cell: cell object that segment belongs to
:type cell: Cell
"""
print(f"Clicked on: Cell: {cell.id}; segment: {seg_id}.")
print(f"Segment info: {cell.get_segment_location_info(seg_id)}")
Loading