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
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
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
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]
def get_tractography(self, tractography, color=None, color_by="manual", others_alpha=1, verbose=True, VIP_regions=[], VIP_color=None, others_color="white", include_all_inj_regions=False, extract_region_from_inj_coords=False, display_injection_volume=True): """ Renders tractography data and adds it to the scene. A subset of tractography data can receive special treatment using the with VIP regions argument: if the injection site for the tractography data is in a VIP regions, this is colored differently. :param tractography: list of dictionaries with tractography data :param color: color of rendered tractography data :param color_by: str, specifies which criteria to use to color the tractography (Default value = "manual") :param others_alpha: float (Default value = 1) :param verbose: bool (Default value = True) :param VIP_regions: list of brain regions with VIP treatement (Default value = []) :param VIP_color: str, color to use for VIP data (Default value = None) :param others_color: str, color for not VIP data (Default value = "white") :param include_all_inj_regions: bool (Default value = False) :param extract_region_from_inj_coords: bool (Default value = False) :param display_injection_volume: float, if True a spehere is added to display the injection coordinates and volume (Default value = True) """ # check argument if not isinstance(tractography, list): if isinstance(tractography, dict): tractography = [tractography] else: raise ValueError("the 'tractography' variable passed must be a list of dictionaries") else: if not isinstance(tractography[0], dict): raise ValueError("the 'tractography' variable passed must be a list of dictionaries") if not isinstance(VIP_regions, list): raise ValueError("VIP_regions should be a list of acronyms") # check coloring mode used and prepare a list COLORS to use for coloring stuff if color_by == "manual": # check color argument if color is None: color = TRACT_DEFAULT_COLOR COLORS = [color for i in range(len(tractography))] elif isinstance(color, list): if not len(color) == len(tractography): raise ValueError("If a list of colors is passed, it must have the same number of items as the number of tractography traces") else: for col in color: if not check_colors(col): raise ValueError("Color variable passed to tractography is invalid: {}".format(col)) COLORS = color else: if not check_colors(color): raise ValueError("Color variable passed to tractography is invalid: {}".format(color)) else: COLORS = [color for i in range(len(tractography))] elif color_by == "region": COLORS = [self.get_region_color(t['structure-abbrev']) for t in tractography] elif color_by == "target_region": if VIP_color is not None: if not check_colors(VIP_color) or not check_colors(others_color): raise ValueError("Invalid VIP or other color passed") try: if include_all_inj_regions: COLORS = [VIP_color if is_any_item_in_list( [x['abbreviation'] for x in t['injection-structures']], VIP_regions)\ else others_color for t in tractography] else: COLORS = [VIP_color if t['structure-abbrev'] in VIP_regions else others_color for t in tractography] except: raise ValueError("Something went wrong while getting colors for tractography") else: COLORS = [self.get_region_color(t['structure-abbrev']) if t['structure-abbrev'] in VIP_regions else others_color for t in tractography] else: raise ValueError("Unrecognised 'color_by' argument {}".format(color_by)) # add actors to represent tractography data actors, structures_acronyms = [], [] if VERBOSE and verbose: print("Structures found to be projecting to target: ") # Loop over injection experiments for i, (t, color) in enumerate(zip(tractography, COLORS)): # Use allen metadata if include_all_inj_regions: inj_structures = [x['abbreviation'] for x in t['injection-structures']] else: inj_structures = [self.get_structure_parent(t['structure-abbrev'])['acronym']] if VERBOSE and verbose and not is_any_item_in_list(inj_structures, structures_acronyms): print(" -- ({})".format(t['structure-abbrev'])) structures_acronyms.append(t['structure-abbrev']) # get tractography points and represent as list if color_by == "target_region" and not is_any_item_in_list(inj_structures, VIP_regions): alpha = others_alpha else: alpha = TRACTO_ALPHA if alpha == 0: continue # skip transparent ones # check if we need to manually check injection coords if extract_region_from_inj_coords: try: region = self.get_structure_from_coordinates(t['injection-coordinates'], just_acronym=False) if region is None: continue inj_structures = [self.get_structure_parent(region['acronym'])['acronym']] except: raise ValueError(self.get_structure_from_coordinates(t['injection-coordinates'], just_acronym=False)) if inj_structures is None: continue elif isinstance(extract_region_from_inj_coords, list): # check if injection coord are in one of the brain regions in list, otherwise skip if not is_any_item_in_list(inj_structures, extract_region_from_inj_coords): continue # represent injection site as sphere if display_injection_volume: actors.append(shapes.Sphere(pos=t['injection-coordinates'], c=color, r=INJECTION_VOLUME_SIZE*t['injection-volume'], alpha=TRACTO_ALPHA)) points = [p['coord'] for p in t['path']] actors.append(shapes.Tube(points, r=TRACTO_RADIUS, c=color, alpha=alpha, res=TRACTO_RES)) return actors