Skip to content

Commit

Permalink
Add function to mirror the mesh of an actor (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
IgorTatarnikov authored Oct 24, 2024
1 parent 836ecee commit 32b987c
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 0 deletions.
29 changes: 29 additions & 0 deletions brainrender/actor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from io import StringIO
from typing import Optional

import numpy as np
import numpy.typing as npt
import pyinspect as pi
from brainglobe_atlasapi import BrainGlobeAtlas
from brainglobe_space import AnatomicalSpace
from myterial import amber, orange, salmon
from rich.console import Console
from vedo import Sphere, Text3D
Expand Down Expand Up @@ -201,6 +205,31 @@ def make_silhouette(self):

return sil

def mirror(
self,
axis: str,
origin: Optional[npt.NDArray] = None,
atlas: Optional[BrainGlobeAtlas] = None,
):
"""
Mirror the actor's mesh around the specified axis, using the
parent_center as the center of the mirroring operation. The axes can
be specified using an abbreviation, e.g. 'x' for the x-axis, or anatomical
convention e.g. 'sagittal'. If an atlas is provided, then the anatomical
space of the atlas is used, otherwise `asr` is assumed.
:param axis: str, axis around which to mirror the mesh
:param origin: np.ndarray, center of the mirroring operation
:param atlas: BrainGlobeAtlas, atlas object to use for anatomical space
"""
if axis in ["sagittal", "vertical", "frontal"]:
anatomical_space = atlas.space if atlas else AnatomicalSpace("asr")

axis_ind = anatomical_space.get_axis_idx(axis)
axis = "x" if axis_ind == 0 else "y" if axis_ind == 1 else "z"

self.mesh = self.mesh.mirror(axis, origin)

def __rich_console__(self, *args):
"""
Print some useful characteristics to console.
Expand Down
68 changes: 68 additions & 0 deletions examples/mirror_actors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from pathlib import Path

import numpy as np
from myterial import orange
from rich import print

from brainrender import Scene
from brainrender._io import load_mesh_from_file
from brainrender.actor import Actor
from brainrender.actors import Neuron, Points

neuron_file = Path(__file__).parent.parent / "resources" / "neuron1.swc"
obj_file = Path(__file__).parent.parent / "resources" / "CC_134_1_ch1inj.obj"
probe_striatum = (
Path(__file__).parent.parent / "resources" / "probe_1_striatum.npy"
)

print(f"[{orange}]Running example: {Path(__file__).name}")

# Create a brainrender scene
scene = Scene(title="mirrored actors")

# Add the neuron
neuron_original = Neuron(neuron_file)
scene.add(neuron_original)

# Add a mesh from a file
scene.add(obj_file, color="tomato")

# Add a probe from a file
scene.add(
Points(
np.load(probe_striatum),
name="probe_1",
colors="darkred",
radius=50,
),
color="darkred",
radius=50,
)

# Add mirrored objects
axis = "frontal"
atlas_center = scene.root.center

neuron_mirrored = Neuron(neuron_file)
neuron_mirrored.mirror(axis, origin=atlas_center, atlas=scene.atlas)
scene.add(neuron_mirrored)

mesh_mirrored = Actor(
load_mesh_from_file(obj_file, color="tomato"),
name=obj_file.name,
br_class="from file",
)
mesh_mirrored.mirror(axis, origin=atlas_center, atlas=scene.atlas)
scene.add(mesh_mirrored)

mirrored_probe = Points(
np.load(probe_striatum),
name="probe_1",
colors="darkred",
radius=50,
)
mirrored_probe.mirror(axis, origin=atlas_center, atlas=scene.atlas)
scene.add(mirrored_probe)

# Render!
scene.render()
116 changes: 116 additions & 0 deletions tests/test_actor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
from pathlib import Path

import pytest
from brainglobe_space import AnatomicalSpace
from rich import print as rprint
from vedo import Mesh

from brainrender import Scene
from brainrender._io import load_mesh_from_file
from brainrender.actor import Actor


@pytest.fixture
def mesh_actor():
resources_dir = Path(__file__).parent.parent / "resources"
data_path = resources_dir / "CC_134_1_ch1inj.obj"
obj_mesh = load_mesh_from_file(data_path, color="tomato")

return Actor(obj_mesh, name=data_path.name, br_class="from file")


def test_actor():
s = Scene()

Expand All @@ -18,3 +32,105 @@ def test_actor():
assert s.alpha() == s.mesh.alpha()
assert s.name == "root"
assert s.br_class == "brain region"


@pytest.mark.parametrize(
"axis, expected_ind",
[
("z", 2),
("y", 1),
("x", 0),
("frontal", 2),
("vertical", 1),
("sagittal", 0),
],
)
def test_mirror_origin(mesh_actor, axis, expected_ind):
original_center = mesh_actor.center
mesh_actor.mirror(axis)
new_center = mesh_actor.center

assert new_center[expected_ind] == -original_center[expected_ind]


@pytest.mark.parametrize(
"axis, expected_ind",
[
("z", 2),
("y", 1),
("x", 0),
("frontal", 2),
("vertical", 1),
("sagittal", 0),
],
)
def test_mirror_around_root(mesh_actor, axis, expected_ind):
scene = Scene()
root_center = scene.root.center

original_center = mesh_actor.center
mesh_actor.mirror(axis, origin=root_center)
new_center = mesh_actor.center

# The new center should be the same distance from the root center as the original center
expected_location = (
-(original_center[expected_ind] - root_center[expected_ind])
+ root_center[expected_ind]
)

assert new_center[expected_ind] == pytest.approx(
expected_location, abs=1e-3
)


@pytest.mark.parametrize(
"axis, expected_ind",
[
("z", 2),
("y", 1),
("x", 0),
("frontal", 1),
("vertical", 0),
("sagittal", 2),
],
)
def test_mirror_custom_space(mesh_actor, axis, expected_ind):
scene = Scene()
scene.atlas.space = AnatomicalSpace("sra")

original_center = mesh_actor.center
mesh_actor.mirror(axis, atlas=scene.atlas)
new_center = mesh_actor.center

assert new_center[expected_ind] == -original_center[expected_ind]


@pytest.mark.parametrize(
"axis, expected_ind",
[
("z", 2),
("y", 1),
("x", 0),
("frontal", 1),
("vertical", 0),
("sagittal", 2),
],
)
def test_mirror_custom_space_around_root(mesh_actor, axis, expected_ind):
scene = Scene()
scene.atlas.space = AnatomicalSpace("sra")
root_center = scene.root.center

original_center = mesh_actor.center
mesh_actor.mirror(axis, origin=root_center, atlas=scene.atlas)
new_center = mesh_actor.center

# The new center should be the same distance from the root center as the original center
expected_location = (
-(original_center[expected_ind] - root_center[expected_ind])
+ root_center[expected_ind]
)

assert new_center[expected_ind] == pytest.approx(
expected_location, abs=1e-3
)

0 comments on commit 32b987c

Please sign in to comment.