Esempio n. 1
0
    def _make_root(self, rootpath):
        """
            Creates a root mesh by merging the mesh corresponding to each neuron,
            then saves it as an obj file at rootpath
        """
        raise NotImplementedError(f"Create root method not supported yet, sorry")

        print(f"Creating root mesh for atlas {self.atlas_name}")
        temp_scene = Scene(atlas=Celegans, add_root=False,
                        display_inset=False,
                        atlas_kwargs=dict(data_folder=self.data_folder))

        temp_scene.add_neurons(self.neurons_names)
        temp_scene.render(interactive=False)
        temp_scene.close()

        root = merge(*temp_scene.actors['neurons']).clean().cap()
        # root = mesh2Volume(root, spacing=(0.02, 0.02, 0.02)).isosurface()

        points = Points(root.points()).smoothMLS2D(f=0.8).clean(tol=0.005)

        root =  recoSurface(points, dims=100, radius=0.2)


        # Save
        write(root, rootpath)
        
        del temp_scene
        return root
Esempio n. 2
0
def showSolution3D(S, start, goal):
    from vtkplotter import Text, Cube, Line, Grid, merge, show

    pts, cubes, txts = [], [], []
    pts = [(x, -y) for y, x in S[0]]
    for y, line in enumerate(Z):
        for x, c in enumerate(line):
            if c: cubes.append(Cube([x, -y, 0]))

    path = Line(pts).lw(6).c('tomato')
    walls = merge(cubes).clean().flat().texture('wood1')

    sy, sx = S[1].shape
    gradient = np.flip(S[1], axis=0).ravel()
    grd = Grid(pos=((sx - 1) / 2, -(sy - 1) / 2, -0.49),
               sx=sx,
               sy=sy,
               resx=sx,
               resy=sy)
    grd.lw(0).wireframe(False).cellColors(gradient, cmap='gist_earth_r')
    grd.addScalarBar(title='Gradient', horizontal=True, c='k', nlabels=2)

    txts.append(__doc__)
    txts.append(Text('Start', pos=[start[1] - 1, -start[0] + 1.5, 1], c='k'))
    txts.append(Text('Goal!', pos=[goal[1] - 2, -goal[0] - 2.7, 1], c='k'))
    show(path, walls, grd, txts, axes=0, zoom=1.2)
Esempio n. 3
0
    def _parse_neuron_skeleton(self, neuron):
        """
            Parses a neuron's skeleton information from skeleton .json file
            to create a vtk actor that represents the neuron

            :param neuron: str, neuron name
        """
        try: # make this work if called by a Scene class
            cs = self.atlas
        except:
            cs = self
        try:
            data = cs.skeletons_data[neuron]
        except:
            print(f'No skeleton data found for {neuron}')
            return None

        # Create an actor for each neuron's branch and then merge
        actors = []
        for branch in data['branches']:
            coords = [data['coordinates'][str(p)] for p in branch]

            # Just like for synapses we need to adjust the coordinates to match the .obj files
            # coords are x z -y
            adjusted_coords = [(c[0], c[2], -c[1]) for c in coords]
            actors.append(Tube(adjusted_coords, r=cs.skeleton_radius, res=NEURON_RESOLUTION))

        return merge(*actors)
Esempio n. 4
0
    def parse_neurons_swc_allen(self, morphology, color='blackboard', alpha=1):
        """
		SWC parser for Allen neuron's morphology data, they're a bit different from the Mouse Light SWC

		:param morphology: data with morphology
		:param neuron_number: int, number of the neuron being rendered.

		"""
        # Create soma actor
        radius = 1
        neuron_actors = [
            shapes.Sphere(pos=get_coords(morphology.soma)[::-1],
                          c=color,
                          r=radius * 3)
        ]

        # loop over trees
        for tree in morphology._tree_list:

            tree = pd.DataFrame(tree)
            branching_points = [
                t.id for i, t in tree.iterrows()
                if len(t.children) > 2 and t.id < len(tree)
            ]

            branch_starts = []
            for bp in branching_points:
                branch_starts.extend(tree.iloc[bp].children)

            for bp in branch_starts:
                parent = tree.iloc[tree.iloc[bp].parent]
                branch = [(parent.x, parent.y, parent.z)]
                point = tree.iloc[bp]

                while True:
                    branch.append((point.x, point.y, point.z))

                    if not point.children:
                        break
                    else:
                        try:
                            point = tree.iloc[point.children[0]]
                        except:
                            break

                # Create actor
                neuron_actors.append(
                    shapes.Tube(branch, r=radius, c='red', alpha=1, res=24))

        actor = merge(*neuron_actors)
        actor.color(color)
        actor.alpha(alpha)
        return actor
Esempio n. 5
0
    def make_root_mesh(self):
        if self.structures is None: return

        obj_path = os.path.join(self.meshes_folder, "root.vtk")
        if os.path.isfile(obj_path):
            return

        # Get the mesh for each brain region to create root
        meshes = [
            self._get_structure_mesh(reg) for reg in self.region_acronyms
        ]
        root = merge(meshes)
        write(root, obj_path)
Esempio n. 6
0
    def parse_neuron_swc(self,
                         filepath,
                         color='blackboard',
                         alpha=1,
                         radius_multiplier=.1,
                         overwrite=False):
        """
		Given an swc file, render the neuron

		:param filepath: str with path to swc file
		:param neuron_number: numnber of neuron being rendered

		"""
        # See if we rendered this neuron already
        if not overwrite:
            loaded = self.load_save_neuron(filepath)
            if loaded is not None:
                return loaded.color(color)

        print(f"Parsing swc file: {filepath}")
        # details on swc files: http://www.neuronland.org/NLMorphologyConverter/MorphologyFormats/SWC/Spec.html
        _sample = namedtuple("sample", "sampleN structureID x y z r parent"
                             )  # sampleN structureID x y z r parent

        if not os.path.isfile(filepath) or not ".swc" in filepath.lower():
            raise ValueError("unrecognized file path: {}".format(filepath))

        try:
            return self.parse_neurons_swc_allen(filepath)
        except:
            pass  #  the .swc file fas not generate with by allen

        f = open(filepath)
        content = f.readlines()
        f.close()
        content = [
            sample.replace("\n", "") for sample in content if sample[0] != '#'
        ]
        content = [sample for sample in content if len(sample) > 3]

        # crate empty dicts for soma axon and dendrites
        data = dict(id=[],
                    parentNumber=[],
                    radius=[],
                    sampleNumber=[],
                    x=[],
                    y=[],
                    z=[])

        # start looping around samples
        for sample in content:
            s = _sample(
                *[float(samp) for samp in sample.lstrip().rstrip().split(" ")])

            # append data to dictionary
            data['id'] = s.structureID
            data['parentNumber'].append(int(s.parent))
            data['radius'].append(s.r)
            data['x'].append(s.x)
            data['y'].append(s.y)
            data['z'].append(s.z)
            data['sampleNumber'].append(int(s.sampleN))

        # Get branches and soma
        print("		reconstructing neurites trees")
        data = pd.DataFrame(data)
        radius = data['radius'].values[0] * radius_multiplier

        soma = data.iloc[0]
        soma = shapes.Sphere(pos=[soma.x, soma.y, soma.z],
                             c=color,
                             r=radius * 4)
        neuron_actors = [soma]

        branches_end, branches_start = [], []  # Get branches start and end
        for parent in data.parentNumber.values:
            sons = data.loc[data.parentNumber == parent]
            if len(sons) > 1:
                branches_end.append(parent)
                for i, son in sons.iterrows():
                    branches_start.append(son.sampleNumber)

        print("		creating actors")
        for start in branches_start:
            node = data.loc[data.sampleNumber == start]
            parent = data.loc[data.sampleNumber == node.parentNumber.values[0]]

            branch = [(parent.x.values[0], parent.y.values[0],
                       parent.z.values[0])]
            while True:
                branch.append(
                    (node.x.values[0], node.y.values[0], node.z.values[0]))

                node = data.loc[data.parentNumber ==
                                node.sampleNumber.values[0]]
                if not len(node): break
                if node.sampleNumber.values[0] in branches_end:
                    branch.append(
                        (node.x.values[0], node.y.values[0], node.z.values[0]))
                    break

            neuron_actors.append(
                shapes.Tube(branch, r=radius, c='red', alpha=1, res=24))

        # Merge actors and save
        actor = merge(*neuron_actors)
        actor.color(color)
        actor.alpha(alpha)

        self.load_save_neuron(filepath, neuron=actor)
        return actor
Esempio n. 7
0
def parse_streamline(*args,
                     filepath=None,
                     data=None,
                     show_injection_site=True,
                     color='ivory',
                     alpha=.8,
                     radius=10,
                     **kwargs):
    """
        Given a path to a .json file with streamline data (or the data themselves), render the streamline as tubes actors.
        Either  filepath or data should be passed

        :param filepath: str, optional. Path to .json file with streamline data (Default value = None)
        :param data: panadas.DataFrame, optional. DataFrame with streamline data. (Default value = None)
        :param color: str color of the streamlines (Default value = 'ivory')
        :param alpha: float transparency of the streamlines (Default value = .8)
        :param radius: int radius of the streamlines actor (Default value = 10)
        :param show_injection_site: bool, if True spheres are used to render the injection volume (Default value = True)
        :param *args: 
        :param **kwargs: 

    """
    if filepath is not None and data is None:
        data = load_json(filepath)
        # data = {k:{int(k2):v2 for k2, v2 in v.items()} for k,v in data.items()}
    elif filepath is None and data is not None:
        pass
    else:
        raise ValueError(
            "Need to pass eiteher a filepath or data argument to parse_streamline"
        )

    # create actors for streamlines
    lines = []
    if len(data['lines']) == 1:
        lines_data = data['lines'][0]
    else:
        lines_data = data['lines']
    for line in lines_data:
        points = [[l['x'], l['y'], l['z']] for l in line]
        lines.append(
            shapes.Tube(points,
                        r=radius,
                        c=color,
                        alpha=alpha,
                        res=STREAMLINES_RESOLUTION))

    coords = []
    if show_injection_site:
        if len(data['injection_sites']) == 1:
            injection_data = data['injection_sites'][0]
        else:
            injection_data = data['injection_sites']

        for inj in injection_data:
            coords.append(list(inj.values()))
        spheres = [shapes.Spheres(coords, r=INJECTION_VOLUME_SIZE)]
    else:
        spheres = []

    merged = merge(*lines, *spheres)
    merged.color(color)
    merged.alpha(alpha)
    return [merged]
Esempio n. 8
0
    def neurites_parser(self, neurites, color):
        """
		Given a dataframe with all the samples for some neurites, create "Tube" actors that render each neurite segment.	
		----------------------------------------------------------------
		This function works by first identifyingt the branching points of a neurite structure. Then each segment between either two branchin points
		or between a branching point and a terminal is modelled as a Tube. This minimizes the number of actors needed to represent the neurites
		while stil accurately modelling the neuron.
		
		Known issue: the axon initial segment is missing from renderings.

		:param neurites: dataframe with each sample for the neurites
		:param color: color to be assigned to the Tube actor

		
		"""
        neurite_radius = self._get_neurites_radius()

        # get branching points
        try:
            parent_counts = neurites["parentNumber"].value_counts()
        except:
            if len(neurites) == 0:
                print("Couldn't find neurites data")
                return [], []
            else:
                raise ValueError(
                    "Something went wrong while rendering neurites:\n{}".
                    format(neurites))
        branching_points = parent_counts.loc[parent_counts > 1]

        # loop over each branching point
        actors = []
        for idx, bp in branching_points.iteritems():
            # get neurites after the branching point
            bp = neurites.loc[neurites.sampleNumber == idx]
            post_bp = neurites.loc[neurites.parentNumber == idx]
            parent = neurites.loc[neurites.sampleNumber ==
                                  bp.parentNumber.values[0]]

            # loop on each branch after the branching point
            for bi, branch in post_bp.iterrows():

                # Start coordinates in a list, including parent and branch point
                if len(parent):
                    branch_points = [
                        get_coords(parent,
                                   mirror=self.mirror_coord,
                                   mirror_ax=self.mirror_ax)
                    ]
                else:
                    branch_points = []
                branch_points.extend([
                    get_coords(bp,
                               mirror=self.mirror_coord,
                               mirror_ax=self.mirror_ax),
                    get_coords(branch,
                               mirror=self.mirror_coord,
                               mirror_ax=self.mirror_ax)
                ])

                # loop over all following points along the branch, until you meet either a terminal or another branching point. store the points
                idx = branch.sampleNumber
                while True:
                    nxt = neurites.loc[neurites.parentNumber == idx]
                    if len(nxt) != 1:
                        break
                    else:
                        branch_points.append(
                            get_coords(nxt,
                                       mirror=self.mirror_coord,
                                       mirror_ax=self.mirror_ax))
                        idx = nxt.sampleNumber.values[0]

                # if the branch is too short for a tube, create a sphere instead
                if len(
                        branch_points
                ) < 2:  # plot either a line between two branch_points or  a spheere
                    actors.append(shapes.Sphere(branch_points[0], c="g",
                                                r=100))
                    continue

                # create tube actor
                actors.append(
                    shapes.Tube(branch_points,
                                r=neurite_radius,
                                c=color,
                                alpha=1,
                                res=NEURON_RESOLUTION))

        # merge actors' meshes to make rendering faster
        merged = merge(*actors)
        if merged is None:
            return None, None
        merged.color(color)

        # get regions the neurites go through
        regions = []
        if "allenId" in neurites.columns:
            for rid in set(neurites.allenId.values):
                try:
                    region = self.alleninfo.loc[self.alleninfo.allenId ==
                                                rid].acronym.values[0]
                    regions.append(
                        self.scene.get_structure_parent(region)['acronym'])
                except:
                    pass

        return merged, regions
Esempio n. 9
0
sphere.addCellArray(carr, 'carr')
sphere.addPointArray(parr, 'parr')

sphere.addPointArray(np.sin(sphere.points()), 'pvectors')
sphere.addElevationScalars()

cone.computeNormals()
sphere.computeNormals()

###################################### test clone()
c2 = cone.clone()
assert cone.N() == c2.N()
assert cone.NCells() == c2.NCells()

###################################### test merge()
m = merge(sphere, cone)
assert m.N() == cone.N() + sphere.N()
assert m.NCells() == cone.NCells() + sphere.NCells()

###################################### inputdata
assert isinstance(cone.inputdata(), vtk.vtkPolyData)

###################################### mapper
assert isinstance(cone.mapper(), vtk.vtkPolyDataMapper)

###################################### pickable
cone.pickable(False)
cone.pickable(True)
assert cone.pickable()

###################################### pos
Esempio n. 10
0
    def add_neurons(self,
                    neurons,
                    color=None,
                    display_axon=True,
                    display_dendrites=True,
                    alpha=1,
                    neurite_radius=None):
        """
            Adds rendered morphological data of neurons reconstructions downloaded from the
            Mouse Light project at Janelia, neuromorpho.org and other sources. 
            Accepts neurons argument as:
                - file(s) with morphological data
                - vtkplotter mesh actor(s) of neurons reconstructions
                - dictionary or list of dictionary with actors for different neuron parts

            :param self: instance of brainrender Scene to use to render neurons
            :param neurons: str, list, dict. File(s) with neurons data or list of rendered neurons.
            :param display_axon, display_dendrites: if set to False the corresponding neurite is not rendered
            :param color: default None. Can be:
                    - None: each neuron is colored according to the default color
                    - color: rbg, hex etc. If a single color is passed all neurons will have that color
                    - cmap: str with name of a colormap: neurons are colored based on their sequential order and cmap
                    - dict: a dictionary specifying a color for soma, dendrites and axon actors, will be the same for all neurons
                    - list: a list of length = number of neurons with either a single color for each neuron
                            or a dictionary of colors for each neuron
            :param alpha: float in range 0,1. Neurons transparency
            :param neurite_radius: float > 0 , radius of tube actor representing neurites
        """

        if not isinstance(neurons, (list, tuple)):
            neurons = [neurons]

        # ------------------------------ Prepare colors ------------------------------ #
        colors = self._add_neurons_get_colors(neurons, color)

        # ---------------------------------- Render ---------------------------------- #
        _neurons_actors = []
        for neuron in neurons:
            neuron_actors = {'soma': None, 'dendrites': None, 'axon': None}

            # Deal with neuron as filepath
            if isinstance(neuron, str):
                if os.path.isfile(neuron):
                    if neuron.endswith('.swc'):
                        neuron_actors, _ = get_neuron_actors_with_morphapi(
                            swcfile=neuron, neurite_radius=neurite_radius)
                    else:
                        raise NotImplementedError(
                            'Currently we can only parse morphological reconstructions from swc files'
                        )
                else:
                    raise ValueError(
                        f"Passed neruon {neuron} is not a valid input. Maybe the file doesn't exist?"
                    )

            # Deal with neuron as single actor
            elif isinstance(neuron, Actor):
                # A single actor was passed, maybe it's the entire neuron
                neuron_actors['soma'] = neuron  # store it as soma anyway
                pass

            # Deal with neuron as dictionary of actor
            elif isinstance(neuron, dict):
                neuron_actors['soma'] = neuron.pop('soma', None)
                neuron_actors['axon'] = neuron.pop('axon', None)

                # Get dendrites actors
                if 'apical_dendrites' in neuron.keys(
                ) or 'basal_dendrites' in neuron.keys():
                    if 'apical_dendrites' not in neuron.keys():
                        neuron_actors['dendrites'] = neuron['basal_dendrites']
                    elif 'basal_dendrites' not in neuron.keys():
                        neuron_actors['dendrites'] = neuron['apical_dendrites']
                    else:
                        neuron_ctors['dendrites'] = merge(
                            neuron['apical_dendrites'],
                            neuron['basal_dendrites'])
                else:
                    neuron_actors['dendrites'] = neuron.pop('dendrites', None)

            # Deal with neuron as instance of Neuron from morphapi
            elif isinstance(neuron, Neuron):
                neuron_actors, _ = get_neuron_actors_with_morphapi(
                    neuron=neuron)
            # Deal with other inputs
            else:
                raise ValueError(
                    f"Passed neuron {neuron} is not a valid input")

            # Check that we don't have anything weird in neuron_actors
            for key, act in neuron_actors.items():
                if act is not None:
                    if not isinstance(act, Actor):
                        raise ValueError(
                            f"Neuron actor {key} is {act.__type__} but should be a vtkplotter Mesh. Not: {act}"
                        )

            if not display_axon:
                neuron_actors['axon'] = None
            if not display_dendrites:
                neuron_actors['dendrites'] = None
            _neurons_actors.append(neuron_actors)

        # Color actors
        for n, neuron in enumerate(_neurons_actors):
            if neuron['axon'] is not None:
                neuron['axon'].c(colors['axon'][n])
            neuron['soma'].c(colors['soma'][n])
            if neuron['dendrites'] is not None:
                neuron['dendrites'].c(colors['dendrites'][n])

        # Add to actors storage
        self.actors["neurons"].extend(_neurons_actors)

        if len(_neurons_actors) == 1:
            return _neurons_actors[0]
        elif not _neurons_actors:
            return None
        else:
            return _neurons_actors
Esempio n. 11
0
    def get_neurons(self, neurons, color=None, display_axon=True, display_dendrites=True,
                alpha=1, neurite_radius=None):
        """
        Gets rendered morphological data of neurons reconstructions downloaded from the
        Mouse Light project at Janelia (or other sources). 
        Accepts neurons argument as:
            - file(s) with morphological data
            - vtkplotter mesh actor(s) of entire neurons reconstructions
            - dictionary or list of dictionary with actors for different neuron parts

        :param neurons: str, list, dict. File(s) with neurons data or list of rendered neurons.
        :param display_axon, display_dendrites: if set to False the corresponding neurite is not rendered
        :param color: default None. Can be:
                - None: each neuron is given a random color
                - color: rbg, hex etc. If a single color is passed all neurons will have that color
                - cmap: str with name of a colormap: neurons are colored based on their sequential order and cmap
                - dict: a dictionary specifying a color for soma, dendrites and axon actors, will be the same for all neurons
                - list: a list of length = number of neurons with either a single color for each neuron
                        or a dictionary of colors for each neuron
        :param alpha: float in range 0,1. Neurons transparency
        :param neurite_radius: float > 0 , radius of tube actor representing neurites
        """

        if not isinstance(neurons, (list, tuple)):
            neurons = [neurons]

        # ------------------------------ Prepare colors ------------------------------ #
        N = len(neurons)
        colors = dict(
            soma = None,
            axon = None,
            dendrites = None,
        )

        # If no color is passed, get random colors
        if color is None:
            cols = get_random_colors(N)
            colors = dict(
                soma = cols.copy(),
                axon = cols.copy(),
                dendrites = cols.copy(),)
        else:
            if isinstance(color, str):
                # Deal with a a cmap being passed
                if color in _mapscales_cmaps:
                    cols = [colorMap(n, name=color, vmin=-2, vmax=N+2) for n in np.arange(N)]
                    colors = dict(
                        soma = cols.copy(),
                        axon = cols.copy(),
                        dendrites = cols.copy(),)

                else:
                    # Deal with a single color being passed
                    cols = [getColor(color) for n in np.arange(N)]
                    colors = dict(
                        soma = cols.copy(),
                        axon = cols.copy(),
                        dendrites = cols.copy(),)
            elif isinstance(color, dict):
                # Deal with a dictionary with color for each component
                if not 'soma' in color.keys():
                    raise ValueError(f"When passing a dictionary as color argument, \
                                                soma should be one fo the keys: {color}")
                dendrites_color = color.pop('dendrites', color['soma'])
                axon_color = color.pop('axon', color['soma'])

                colors = dict(
                        soma = [color['soma'] for n in np.arange(N)],
                        axon = [axon_color for n in np.arange(N)],
                        dendrites = [dendrites_color for n in np.arange(N)],)
                        
            elif isinstance(color, (list, tuple)):
                # Check that the list content makes sense
                if len(color) != N:
                    raise ValueError(f"When passing a list of color arguments, the list length"+
                                f" ({len(color)}) should match the number of neurons ({N}).")
                if len(set([type(c) for c in color])) > 1:
                    raise ValueError(f"When passing a list of color arguments, all list elements"+
                                " should have the same type (e.g. str or dict)")

                if isinstance(color[0], dict):
                    # Deal with a list of dictionaries
                    soma_colors, dendrites_colors, axon_colors = [], [], []

                    for col in colors:
                        if not 'soma' in col.keys():
                            raise ValueError(f"When passing a dictionary as col argument, \
                                                        soma should be one fo the keys: {col}")
                        dendrites_colors.append(col.pop('dendrites', col['soma']))
                        axon_colors.append(col.pop('axon', col['soma']))
                        soma_colors.append(col['soma'])

                    colors = dict(
                        soma = soma_colors,
                        axon = axon_colors,
                        dendrites = dendrites_colors,)

                else:
                    # Deal with a list of colors
                    colors = dict(
                        soma = color.copy(),
                        axon = color.copy(),
                        dendrites = color.copy(),)
            else:
                raise ValueError(f"Color argument passed is not valid. Should be a \
                                        str, dict, list or None, not {type(color)}:{color}")

        # Check colors, if everything went well we should have N colors per entry
        for k,v in colors.items():
            if len(v) != N:
                raise ValueError(f"Something went wrong while preparing colors. Not all \
                                entries have right length. We got: {colors}")



        # ---------------------------------- Render ---------------------------------- #
        _neurons_actors = []
        for neuron in neurons:
            neuron_actors = {'soma':None, 'dendrites':None, 'axon': None}
            
            # Deal with neuron as filepath
            if isinstance(neuron, str):
                if os.path.isfile(neuron):
                    if neuron.endswith('.swc'):
                        neuron_actors, _ = get_neuron_actors_with_morphapi(swcfile=neuron, neurite_radius=neurite_radius)
                    else:
                        raise NotImplementedError('Currently we can only parse morphological reconstructions from swc files')
                else:
                    raise ValueError(f"Passed neruon {neuron} is not a valid input. Maybe the file doesn't exist?")
            
            # Deal with neuron as single actor
            elif isinstance(neuron, Actor):
                # A single actor was passed, maybe it's the entire neuron
                neuron_actors['soma'] = neuron # store it as soma anyway
                pass

            # Deal with neuron as dictionary of actor
            elif isinstance(neuron, dict):
                neuron_actors['soma'] = neuron.pop('soma', None)
                neuron_actors['axon'] = neuron.pop('axon', None)

                # Get dendrites actors
                if 'apical_dendrites' in neuron.keys() or 'basal_dendrites' in neuron.keys():
                    if 'apical_dendrites' not in neuron.keys():
                        neuron_actors['dendrites'] = neuron['basal_dendrites']
                    elif 'basal_dendrites' not in neuron.keys():
                        neuron_actors['dendrites'] = neuron['apical_dendrites']
                    else:
                        neuron_ctors['dendrites'] = merge(neuron['apical_dendrites'], neuron['basal_dendrites'])
                else:
                    neuron_actors['dendrites'] = neuron.pop('dendrites', None)
            
            # Deal with neuron as instance of Neuron from morphapi
            elif isinstance(neuron, Neuron):
                neuron_actors, _ = get_neuron_actors_with_morphapi(neuron=neuron)                
            # Deal with other inputs
            else:
                raise ValueError(f"Passed neuron {neuron} is not a valid input")

            # Check that we don't have anything weird in neuron_actors
            for key, act in neuron_actors.items():
                if act is not None:
                    if not isinstance(act, Actor):
                        raise ValueError(f"Neuron actor {key} is {act.__type__} but should be a vtkplotter Mesh. Not: {act}")

            if not display_axon:
                neuron_actors['axon'] = None
            if not display_dendrites:
                neuron_actors['dendrites'] = None
            _neurons_actors.append(neuron_actors)

        # Color actors
        for n, neuron in enumerate(_neurons_actors):
            if neuron['axon'] is not None:
                neuron['axon'].c(colors['axon'][n])
            neuron['soma'].c(colors['soma'][n])
            if neuron['dendrites'] is not None:
                neuron['dendrites'].c(colors['dendrites'][n])

        # Return
        if len(_neurons_actors) == 1:
            return _neurons_actors[0], None
        elif not _neurons_actors:
            return None, None
        else:
            return _neurons_actors, None