def _add_neurons_get_colors(self, neurons, color): """ Parses color argument for self.add_neurons :para, neurons: list of Neuron object or file paths... :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 """ N = len(neurons) colors = dict( soma=None, axon=None, dendrites=None, ) # If no color is passed, get random colors if color is None: cols = [self.default_neuron_color for n in np.arange(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 "soma" not 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( "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( "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 color: if "soma" not 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}") return colors
def test_colors(): if not isinstance(get_random_colormap(), str): raise ValueError cols = get_n_shades_of("green", 40) if not isinstance(cols, list): raise ValueError if len(cols) != 40: raise ValueError getColor("orange") getColor([1, 1, 1]) getColor("k") getColor("#ffffff") getColor(7) getColor(-7) getColorName("#ffffff") cols = colorMap([0, 1, 2]) if not isinstance(cols, (list, np.ndarray)): raise ValueError if len(cols) != 3: raise ValueError c = colorMap(3, vmin=-3, vmax=4) check_colors(cols) check_colors(c)
def add_neurons_synapses(self, neurons, alpha=1, pre=False, post=False, colorby='synapse_type', draw_patches=False, draw_arrows=True): """ THIS METHODS GETS CALLED BY SCENE, self referes to the instance of Scene not to this class. Renders neurons and adds them to the scene. :param neurons: list of names of neurons :param alpha: float in range 0,1 - neurons transparency :param pre: bool, if True the presynaptic sites of each neuron are rendered :param post: bool, if True the postsynaptic sites on each neuron are rendered :param colorby: str, criteria to use to color the neurons. Accepts values like synapse_type, type, individual etc. :param draw_patches: bool, default True. If true dark patches are used to show the location of post synapses :param draw_arrows: bool, default True. If true arrows are used to show the location of post synapses """ col_names = ['x', 'z', 'y'] # used to correctly position synapses on .obj files neurons = self.atlas._check_neuron_argument(neurons) for neuron in neurons: if pre: if colorby == 'synapse_type': color = self.atlas.pre_synapses_color else: color = self.atlas.get_neuron_color(neuron, colorby=colorby) data = self.atlas.synapses_data.loc[self.atlas.synapses_data.pre == neuron] if not len(data): print(f"No pre- synapses found for neuron {neuron}") else: data = data[['x', 'y', 'z']] data['y'] = -data['y'] self.add_cells(data, color=color, verbose=False, alpha=alpha, radius=self.atlas.synapses_radius, res=24, col_names = col_names) if post: if colorby == 'synapse_type': color = self.atlas.post_synapses_color else: color = self.atlas.get_neuron_color(neuron, colorby=colorby) rows = [i for i,row in self.atlas.synapses_data.iterrows() if neuron in row.posts] data = self.atlas.synapses_data.iloc[rows] if not len(data): print(f"No post- synapses found for neuron {neuron}") else: data = data[['x', 'y', 'z']] data['y'] = -data['y'] """ Post synaptic locations are shown as darkening of patches of a neuron's mesh and or as a 3d arrow point toward the neuron. """ # get a sphere at each post synaptic location # spheres = self.add_cells(data, color=color, verbose=False, alpha=.1, # radius=self.atlas.synapses_radius*4, res=24, col_names = col_names) spheres = self.add_cells(data, color='black', verbose=False, alpha=0, radius=self.atlas.synapses_radius*4, res=24, col_names = col_names) # Get mesh points for neuron the synapses belong to if neuron not in self.store.keys(): neuron_file = [f for f in self.atlas.neurons_files if neuron in f][0] neuron_act = load_mesh_from_file(neuron_file, c=color) else: neuron_act, as_skeleton = self.store[neuron] # Draw post synapses as dark patches if draw_patches: if as_skeleton: print("Can't display post synapses as dark spots when neron is rendered in skeleton mode") else: # Get faces that are inside the synapses spheres and color them darker neuron_points = neuron_act.cellCenters() inside_points = spheres.insidePoints(neuron_points, returnIds=True) n_cells = neuron_act.polydata().GetNumberOfCells() scals = np.zeros((n_cells)) scals[inside_points] = 1 colors = [neuron_act.c() if s == 0 else getColor('blackboard') for s in scals] neuron_act.cellIndividualColors(colors) # Draw post synapses as arrow if draw_arrows: points1 = [[x, y, z] for x,y,z in zip(data[col_names[0]].values, data[col_names[1]].values, data[col_names[2]].values)] points2 = [neuron_act.closestPoint(p) for p in points1] # shift point1 to make arrows longer def dist(p1, p2): return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2 + (p1[2]-p2[2])**2) def get_point(p1, p2, d, u): alpha = (1/d)*u x = (1-alpha)*p1[0]+alpha*p2[0] y = (1-alpha)*p1[1]+alpha*p2[1] z = (1-alpha)*p1[2]+alpha*p2[2] return [x, y, z] dists = [dist(p1, p2) for p1, p2 in zip(points1, points2)] points0 = [get_point(p1, p2, d, -.5) for p1, p2, d in zip(points1, points2, dists)] arrows = Arrows(points0, endPoints=points2, c=color, s=4) self.add_vtkactor(arrows)
def __init__(self, brain_regions=None, regions_aba_color=False, neurons=None, tracts=None, add_root=None, verbose=True, jupyter=False, display_inset=None, base_dir=None, camera=None, screenshot_kwargs = {}, use_default_key_bindings=False, title=None, atlas=None, atlas_kwargs=dict(), **kwargs): """ Creates and manages a Plotter instance :param brain_regions: list of brain regions acronyms to be added to the rendered scene (default value None) :param regions_aba_color: if True, use the Allen Brain Atlas regions colors (default value None) :param neurons: path to JSON or SWC file with data of neurons to be rendered [or list of files] (default value None) :param tracts: list of JSON files with tractography data to be rendered (default value None) :param add_root: if False a rendered outline of the whole brain is added to the scene (default value None) :param verbose: if False less feedback is printed to screen (default value True) :param display_insert: if False the inset displaying the brain's outline is not rendered (but the root is added to the scene) (default value None) :param base_dir: path to directory to use for saving data (default value None) :param camera: name of the camera parameters setting to use (controls the orientation of the rendered scene) :param kwargs: can be used to pass path to individual data folders. See brainrender/Utils/paths_manager.py :param screenshot_kwargs: pass a dictionary with keys: - 'folder' -> str, path to folder where to save screenshots - 'name' -> str, filename to prepend to screenshots files - 'format' -> str, 'png', 'svg' or 'jpg' - scale -> float, values > 1 yield higher resultion screenshots :param use_default_key_bindings: if True the defualt keybindings from VtkPlotter are used, otherwise a custom function that can be used to take screenshots with the parameter above. :param title: str, if a string is passed a text is added to the top of the rendering window as a title :param atlas: an instance of a valid Atlas class to use to fetch anatomical data for the scene. By default if not atlas is passed the allen brain atlas for the adult mouse brain is used. :param atlas_kwargs: dictionary used to pass extra arguments to atlas class """ if atlas is None: self.atlas = ABA(base_dir=base_dir, **atlas_kwargs, **kwargs) else: self.atlas = atlas(base_dir=base_dir, **atlas_kwargs, **kwargs) # Setup a few rendering options self.verbose = verbose self.regions_aba_color = regions_aba_color # Infer if we are using k3d from vtkplotter.settings if settings.notebookBackend == 'k3d': self.jupyter = True else: self.jupyter = False if display_inset is None: self.display_inset = brainrender.DISPLAY_INSET else: self.display_inset = display_inset if self.display_inset and jupyter: print("Setting 'display_inset' to False as this feature is not available in juputer notebooks") self.display_inset = False if add_root is None: add_root = brainrender.DISPLAY_ROOT # Camera parameters if camera is None: if self.atlas.default_camera is not None: self.camera = check_camera_param(self.atlas.default_camera) else: self.camera = brainrender.CAMERA else: self.camera = check_camera_param(camera) # Set up vtkplotter plotter and actors records if brainrender.WHOLE_SCREEN and not self.jupyter: sz = "full" elif brainrender.WHOLE_SCREEN and self.jupyter: print("Setting window size to 'auto' as whole screen is not available in jupyter") sz='auto' else: sz = "auto" if brainrender.SHOW_AXES: axes = 1 else: axes = 0 # Create plotter self.plotter = Plotter(axes=axes, size=sz, pos=brainrender.WINDOW_POS, title='brainrender') self.plotter.legendBC = getColor('blackboard') # SCreenshots and keypresses variables self.screenshots_folder = screenshot_kwargs.pop('folder', self.atlas.output_screenshots) self.screenshots_name = screenshot_kwargs.pop('name', brainrender.DEFAULT_SCREENSHOT_NAME) self.screenshots_extension = screenshot_kwargs.pop('type', brainrender.DEFAULT_SCREENSHOT_TYPE) self.screenshots_scale = screenshot_kwargs.pop('scale', brainrender.DEFAULT_SCREENSHOT_SCALE) if not use_default_key_bindings: self.plotter.keyPressFunction = self.keypress self.verbose = False if not brainrender.SCREENSHOT_TRANSPARENT_BACKGROUND: settings.screenshotTransparentBackground = False settings.useFXAA = True # Prepare store for actors added to scene self.actors = {"regions":{}, "tracts":[], "neurons":[], "root":None, "injection_sites":[], "others":[], "labels":[],} self._actors = None # store a copy of the actors when manipulations like slicing are done self.store = {} # in case we need to store some data # Add items to scene if brain_regions is not None: self.add_brain_regions(brain_regions) if neurons is not None: self.add_neurons(neurons) if tracts is not None: self.add_tractography(tracts) if add_root: self.add_root(render=True) else: self.root = None if title is not None: self.add_text(title) # Placeholder variables self.inset = None # the first time the scene is rendered create and store the inset here self.is_rendered = False # keep track of if the scene has already been rendered
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