def __init__( self, data, radius=10, color="salmon", alpha=1, show_injection=True, name=None, ): """ Turns streamlines data to a mesh. :param data: pd.DataFrame with streamlines points data :param radius: float. Radius of the Tube mesh used to render streamlines :param color: str, name of the color to be used :param alpha: float, transparancy :param name: str, name of the actor. :param show_injection: bool. If true spheres mark the injection sites """ logger.debug(f"Creating a streamlines actor") if isinstance(data, (str, Path)): data = pd.read_json(data) elif not isinstance(data, pd.DataFrame): raise TypeError("Input data should be a dataframe") self.radius = radius mesh = ( self._make_mesh(data, show_injection=show_injection) .c(color) .alpha(alpha) .clean() ) name = name or "Streamlines" Actor.__init__(self, mesh, name=name, br_class="Streamliness")
def __init__(self, data, name=None, dims=(40, 40, 40), radius=None, **kwargs): """ Creates a Volume actor showing the 3d density of a set of points. :param data: np.ndarray, Nx3 array with cell coordinates from vedo: Generate a density field from a point cloud. Input can also be a set of 3D coordinates. Output is a ``Volume``. The local neighborhood is specified as the `radius` around each sample position (each voxel). The density is expressed as the number of counts in the radius search. :param int,list dims: numer of voxels in x, y and z of the output Volume. """ volume = vPoints(data).density(dims=dims, radius=radius, **kwargs) # returns a vedo Volume Actor.__init__(self, volume, name=name, br_class="density")
def __init__(self, pos, root, color="powderblue", alpha=1, radius=350): """ Cylinder class creates a cylinder mesh between a given point and the brain's surface. :param pos: list, np.array of ap, dv, ml coordinates. If an actor is passed, get's the center of mass instead :param root: brain root Actor or mesh object :param color: str, color :param alpha: float :param radius: float """ # Get pos if isinstance(pos, Mesh): pos = pos.points().mean(axis=0) elif isinstance(pos, Actor): pos = pos.center logger.debug(f"Creating Cylinder actor at: {pos}") # Get point at top of cylinder top = pos.copy() top[1] = root.bounds()[2] - 500 # Create mesh and Actor mesh = shapes.Cylinder(pos=[top, pos], c=color, r=radius, alpha=alpha) Actor.__init__(self, mesh, name="Cylinder", br_class="Cylinder")
def __init__(self, data, name=None, colors="salmon", alpha=1, radius=20): """ Creates an actor representing multiple points (more efficient than creating many Point instances). :param data: np.ndarray, Nx3 array or path to .npy file with coords data :param radius: float :param color: str, or list of str with color names or hex codes :param alpha: float :param name: str, actor name """ PointsBase.__init__(self) self.radius = radius self.colors = colors self.alpha = alpha self.name = name if isinstance(data, np.ndarray): mesh = self._from_numpy(data) elif isinstance(data, (str, Path)): mesh = self._from_file(data) else: # pragma: no cover raise TypeError( # pragma: no cover f"Input data should be either a numpy array or a file path, not: {_class_name(data)}" # pragma: no cover ) # pragma: no cover Actor.__init__(self, mesh, name=self.name, br_class="Points")
def __init__(self, *args, ROIs=None, **kwargs): self.ROIs = ROIs or [ (0, self.length), ] ProbeGeometry.__init__(self, *args, **kwargs) Actor.__init__(self, self.get_mesh(), name="Probe", br_class="Probe")
def __init__(self, data, name=None, dims=(40, 40, 40), radius=None, **kwargs): """ Creates a Volume actor showing the 3d density of a set of points. :param data: np.ndarray, Nx3 array with cell coordinates from vedo: Generate a density field from a point cloud. Input can also be a set of 3D coordinates. Output is a ``Volume``. The local neighborhood is specified as the `radius` around each sample position (each voxel). The density is expressed as the number of counts in the radius search. :param int,list dims: numer of voxels in x, y and z of the output Volume. """ logger.debug("Creating a PointsDensity actor") # flip coordinates on XY axis to match brainrender coordinates system # data[:, 2] = -data[:, 2] # create volume and then actor volume = (vPoints(data).density(dims=dims, radius=radius, **kwargs).c("Dark2").alpha([0, 0.9 ]).mode(1) ) # returns a vedo Volume volume.mirror("z") Actor.__init__(self, volume, name=name, br_class="density")
def add_actor(self, *actors, name=None, br_class=None, store=None): """ Add a vtk actor to the scene :param actor: :param store: a list to store added actors """ # Parse inputs to match a name and br class to each actor actors, names, br_classes = parse_add_actors_inputs( actors, name, br_class) # Add actors to scene to_return = [] for actor, name, br_class in zip(actors, names, br_classes): for act in listify(actor): if act is None: continue try: act = Actor(act, name=name, br_class=br_class) except Exception: # doesn't work for annotations act.name = name act.br_class = br_class act._is_transformed = False if store is None: self.actors.append(act) else: store.append(act) to_return.append(act) return return_list_smart(to_return)
def add(self, *items, names=None, classes=None, **kwargs): """ General method to add Actors to the scene. :param items: vedo.Mesh, Actor, (str, Path). If str/path it should be a path to a .obj or .stl file. Whatever the input it's turned into an instance of Actor before adding it to the scne :param names: names to be assigned to the Actors :param classs: br_classes to be assigned to the Actors :param **kwargs: parameters to be passed to the individual loading functions (e.g. to load from file and specify the color) """ names = names or [None for a in items] classes = classes or [None for a in items] # turn items into Actors actors = [] for item, name, _class in zip(items, listify(names), listify(classes)): if item is None: continue if isinstance(item, (Mesh, Assembly)): actors.append(Actor(item, name=name, br_class=_class)) elif pi.utils._class_name(item) == "vtkCornerAnnotation": # Mark text actors differently because they don't behave like # other 3d actors actors.append( Actor( item, name=name, br_class=_class, is_text=True, **kwargs, )) elif pi.utils._class_name(item) == "Volume" and not isinstance( item, Volume): actors.append( Volume(item, name=name, br_class=_class, **kwargs)) elif isinstance(item, Actor): actors.append(item) elif isinstance(item, (str, Path)): mesh = load_mesh_from_file(item, **kwargs) name = name or Path(item).name _class = _class or "from file" actors.append(Actor(mesh, name=name, br_class=_class)) else: raise ValueError( f"Unrecognized argument: {item} [{pi.utils._class_name(item)}]" ) # Add to the lists actors self.actors.extend(actors) return return_list_smart(actors)
def __init__( self, griddata, voxel_size=1, cmap="bwr", min_quantile=None, min_value=None, name=None, br_class=None, as_surface=True, **volume_kwargs, ): """ Takes a 3d numpy array with volumetric data and returns an Actor with mesh: vedo.Volume.isosurface or a vedo.Volume. BY default the volume is represented as a surface To extract the surface: The isosurface needs a lower bound threshold, this can be either a user defined hard value (min_value) or the value corresponding to some percentile of the grid data. :param griddata: np.ndarray, 3d array with grid data :param voxel_size: int, size of each voxel in microns :param min_quantile: float, percentile for threshold :param min_value: float, value for threshold :param cmap: str, name of colormap to use :param as_surface, bool. default True. If True a surface mesh is returned instead of the whole volume :param volume_kwargs: keyword arguments for vedo's Volume class """ # Create mesh color = volume_kwargs.pop("c", "viridis") if isinstance(griddata, np.ndarray): # create volume from data mesh = VedoVolume( griddata, spacing=[voxel_size, voxel_size, voxel_size], c=color, **volume_kwargs, ) else: mesh = griddata # assume a vedo Volume was passed if as_surface: # Get threshold if min_quantile is None and min_value is None: th = 0 elif min_value is not None: th = min_value else: th = np.percentile(griddata.ravel(), min_quantile) mesh = mesh.legosurface(vmin=th, cmap=cmap) Actor.__init__(self, mesh, name=name or "Volume", br_class=br_class or "Volume")
def get_plane( self, pos=None, norm=None, plane=None, sx=None, sy=None, color="lightgray", alpha=0.25, **kwargs, ): """ Returns a plane going through a point at pos, oriented orthogonally to the vector norm and of width and height sx, sy. :param pos: 3-tuple or list with x,y,z, coords of point the plane goes through :param norm: 3-tuple with plane's normal vector (optional) :param sx, sy: int, width and height of the plane :param plane: "sagittal", "horizontal", or "frontal" :param color, alpha: plane color and transparency """ axes_pairs = dict(sagittal=(0, 1), horizontal=(2, 0), frontal=(2, 1)) if pos is None: pos = self.root.centerOfMass() try: norm = norm or self.space.plane_normals[plane] except KeyError: # pragma: no cover raise ValueError( # pragma: no cover f"Could not find normals for plane {plane}. Atlas space provides these normals: {self.space.plane_normals}" # pragma: no cover ) # Get plane width and height idx_pair = ( axes_pairs[plane] if plane is not None else axes_pairs["horizontal"] ) bounds = self.root.bounds() root_bounds = [ [bounds[0], bounds[1]], [bounds[2], bounds[3]], [bounds[4], bounds[5]], ] wh = [float(np.diff(root_bounds[i])) for i in idx_pair] if sx is None: sx = wh[0] if sy is None: sy = wh[1] # return plane return Actor( Plane(pos=pos, normal=norm, sx=sx, sy=sy, c=color, alpha=alpha), name=f"Plane at {pos} norm: {norm}", br_class="plane", )
def __init__(self, data, radius=10, color="salmon", alpha=1, name=None): """ Turns streamlines data to a mesh. :param data: pd.DataFrame with streamlines points data :param radius: float. Radius of the Tube mesh used to render streamlines :param color: str, name of the color to be used :param alpha: float, transparancy :param name: str, name of the actor. """ if not isinstance(data, pd.DataFrame): raise TypeError("Input data should be a dataframe") self.radius = radius mesh = self._make_mesh(data).c(color).alpha(alpha) name = name or "Streamlines" Actor.__init__(self, mesh, name=name, br_class="Streamliness")
def __init__(self, pos, radius=100, color="blackboard", alpha=1, res=25, name=None): """ Creates an actor representing a single point :param pos: list or np.ndarray with coordinates :param radius: float :param color: str, :param alpha: float :param res: int, resolution of mesh :param name: str, actor name """ mesh = Sphere(pos=pos, r=radius, c=color, alpha=alpha, res=res) name = name or "Point" Actor.__init__(self, mesh, name=name, br_class="Point")
def get_region(self, *regions, alpha=1, color=None): """ Get brain regions meshes as Actors :param regions: str with names of brain regions in the atlas :param alpha: float :param color: str """ if not regions: return None _color = color actors = [] for region in regions: if ( region not in self.lookup_df.acronym.values and region not in self.lookup_df["id"].values ): print( f"The region {region} doesn't seem to belong to the atlas being used: {self.atlas_name}. Skipping" ) continue # Get mesh obj_file = str(self.meshfile_from_structure(region)) mesh = load_mesh_from_file(obj_file, color=color, alpha=alpha) # Get color if color is None: color = [ x / 255 for x in self._get_from_structure(region, "rgb_triplet") ] # Make actor actor = Actor(mesh, name=region, br_class="brain region") actor.c(color).alpha(alpha) actors.append(actor) # reset color to input color = _color return return_list_smart(actors)
def __init__( self, neuron, color=None, alpha=1, neurite_radius=8, soma_radius=15, name=None, ): """ Creates an Actor representing a single neuron's morphology :param neuron: path to .swc file, Mesh, Actor or Neuron from morphapi.morphology :param alpha: float :param color: str, :param neuron_radius: float, radius of axon/dendrites :param soma_radius: float, radius of soma :param name: str, actor name """ logger.debug(f"Creating a Neuron actor") if color is None: color = "blackboard" alpha = alpha self.neurite_radius = neurite_radius self.soma_radius = soma_radius self.name = None if isinstance(neuron, (str, Path)): mesh = self._from_file(neuron) elif isinstance(neuron, (Mesh)): mesh = neuron elif isinstance(neuron, Actor): mesh = neuron.mesh elif isinstance(neuron, MorphoNeuron): mesh = self._from_morphapi_neuron(neuron) else: raise ValueError( f'Argument "neuron" is not in a recognized format: {_class_name(neuron)}' ) Actor.__init__(self, mesh, name=self.name, br_class="Neuron") self.mesh.c(color).alpha(alpha)
def ruler(p1, p2, unit_scale=1, units=None, s=50): """ Creates a ruler showing the distance between two points. The ruler is composed of a line between the points and a text indicating the distance. :param p1: list, np.ndarray with coordinates of first point :param p2: list, np.ndarray with coordinates of second point :param unit_scale: float. To scale the units (e.g. show mm instead of µm) :param units: str, name of unit (e.g. 'mm') :param s: float size of text """ actors = [] # Make two line segments midpoint = np.array([(x + y) / 2 for x, y in zip(p1, p2)]) gap1 = ((midpoint - p1) * 0.8) + p1 gap2 = ((midpoint - p2) * 0.8) + p2 actors.append(Line(p1, gap1, lw=200)) actors.append(Line(gap2, p2, lw=200)) # Add label if units is None: # pragma: no cover units = "" # pragma: no cover dist = mag(p2 - p1) * unit_scale label = precision(dist, 3) + " " + units lbl = Text(label, pos=midpoint, s=s + 100, justify="center") lbl.SetOrientation([0, 0, 180]) actors.append(lbl) # Add spheres add end actors.append(Sphere(p1, r=s, c=[0.3, 0.3, 0.3])) actors.append(Sphere(p2, r=s, c=[0.3, 0.3, 0.3])) act = Actor(merge(*actors), name="Ruler", br_class="Ruler") act.c((0.3, 0.3, 0.3)).alpha(1).lw(2) return act
def test_neuron(): s = Scene(title="BR") neuron = s.add(Neuron("tests/files/neuron1.swc")) s.add(Neuron(Actor(neuron.mesh))) s.add(Neuron(neuron.mesh)) Neuron(Sphere()) with pytest.raises(ValueError): Neuron(1) with pytest.raises(FileExistsError): Neuron("tests/files/neuronsfsfs.swc") with pytest.raises(NotImplementedError): Neuron("tests/files/random_cells.h5") del s