def save_cache(self, cache_file=None): if cache_file is None: cache_file = snudda_parse_path(self.cache_filename) assert not self.rotated_flag, \ "saveCache: The neuron should not be rotated when saving cache" morph = dict([]) morph["swc_filename"] = self.swc_filename morph["soma"] = self.soma morph["axon"] = self.axon morph["dend"] = self.dend morph["axonLinks"] = self.axon_links morph["dendLinks"] = self.dend_links morph["dendSecX"] = self.dend_sec_x morph["dendSecID"] = self.dend_sec_id morph["axonStumpIDFlag"] = self.axon_stump_id_flag morph["maxAxonRadius"] = self.max_axon_radius morph["maxDendRadius"] = self.max_dend_radius morph["dendDensity"] = self.dend_density morph["axonDensity"] = self.axon_density morph["version"] = self.cache_version assert (cache_file != self.swc_filename) self.write_log(f"Saving cache file: {cache_file}") import pickle with open(cache_file, 'wb') as cache_file: pickle.dump(morph, cache_file, self.pickle_version)
def cache_exist(self, cache_file=None): if cache_file is None: cache_file = snudda_parse_path(self.cache_filename) cache_flag = False import os if os.path.isfile(cache_file): swc_time = os.path.getmtime(snudda_parse_path(self.swc_filename)) cache_time = os.path.getmtime(cache_file) if cache_time > swc_time: self.write_log(f"Found cache file: {cache_file}") cache_flag = True else: self.write_log(f"Found old cache file: {cache_file}") else: self.write_log("No cache file found.") return cache_flag
def load_cache(self, cache_file=None): if cache_file is None: cache_file = snudda_parse_path(self.cache_filename) import pickle with open(cache_file, 'rb') as cache_file: morph = pickle.load(cache_file) assert self.swc_filename == morph["swc_filename"], \ "Cached file had different path. Saving new version of cache." assert self.axon_stump_id_flag == morph["axonStumpIDFlag"], \ "axonStumpIDFlag must match cached version" # axonStumpIDFlag affects the section ID for the dendrites (and axon) # True when running Network_simulate.py and False if running Neurodamus. # self.axonStumpIDFlag = morph["axonStumpIDFlag"] # True or False self.soma = np.copy(morph["soma"]) self.axon = np.copy(morph["axon"]) self.dend = np.copy(morph["dend"]) self.axon_links = morph["axonLinks"] self.dend_links = morph["dendLinks"] self.dend_sec_x = morph["dendSecX"] self.dend_sec_id = morph["dendSecID"] assert morph["version"] == self.cache_version, \ "Cache version mismatch, regenerating cache" self.max_axon_radius = morph["maxAxonRadius"] self.max_dend_radius = morph["maxDendRadius"] if morph["dendDensity"] is not None: self.dend_density = morph["dendDensity"] else: self.dend_density = None if morph["axonDensity"] is not None: self.axon_density = morph["axonDensity"] else: self.axon_density = None
def parse_config(self, config_file=None): if config_file is None: config_file = self.config_file if config_file is None: self.write_log("No configuration file specified") os.sys.exit(-1) if not os.path.exists(config_file): self.write_log(f"Config file does not exist: {config_file}") self.write_log("Run snudda init <your directory> first") os.sys.exit(-1) self.write_log(f"Parsing configuration file {config_file}") cfg_file = open(config_file, 'r') try: config = json.load(cfg_file, object_pairs_hook=OrderedDict) finally: cfg_file.close() if not config: self.write_log("Warning, empty network config.") if self.random_seed is None: if "RandomSeed" in config and "place" in config["RandomSeed"]: self.random_seed = config["RandomSeed"]["place"] self.write_log( f"Reading random seed from config file: {self.random_seed}" ) else: # No random seed given, invent one self.random_seed = 1001 self.write_log( f"No random seed provided, using: {self.random_seed}") else: self.write_log( f"Using random seed provided by command line: {self.random_seed}" ) self.random_generator = np.random.default_rng(self.random_seed + 115) if self.log_file is None: mesh_log_filename = "mesh-log.txt" else: mesh_log_filename = self.log_file.name + "-mesh" mesh_logfile = open(mesh_log_filename, 'wt') # First handle volume definitions volume_def = config["Volume"] # Setup random seeds for all volumes ss = np.random.SeedSequence(self.random_seed) all_seeds = ss.generate_state(len(volume_def)) all_vd = sorted(volume_def.keys()) vol_seed = dict() for vd, seed in zip(all_vd, all_seeds): vol_seed[vd] = seed for volume_id, vol_def in volume_def.items(): self.volume[volume_id] = vol_def if "meshFile" in vol_def: assert "dMin" in vol_def, "You must specify dMin if using a mesh" \ + " for volume " + str(volume_id) if "meshBinWidth" not in vol_def or not vol_def["meshBinWidth"]: self.write_log("No meshBinWidth specified, using 1e-4") mesh_bin_width = 1e-4 else: mesh_bin_width = vol_def["meshBinWidth"] self.write_log(f"Using mesh_bin_width {mesh_bin_width}") if "-cube-mesh-" in vol_def[ "meshFile"] or "slice.obj" in vol_def["meshFile"]: self.write_log( "Cube or slice mesh, switching to serial processing.") d_view = None lb_view = None else: d_view = self.d_view lb_view = self.lb_view if snudda_path_exists(vol_def["meshFile"]): mesh_file = snudda_parse_path(vol_def["meshFile"]) elif os.path.exists( os.path.join(self.network_path, vol_def["meshFile"])): mesh_file = os.path.join(self.network_path, vol_def["meshFile"]) else: self.write_log( f"Unable to find mesh file {vol_def['meshFile']}") os.sys.exit(-1) self.volume[volume_id]["mesh"] \ = RegionMesh(mesh_file, d_view=d_view, lb_view=lb_view, raytrace_borders=self.raytrace_borders, d_min=vol_def["dMin"], bin_width=mesh_bin_width, log_file=mesh_logfile, random_seed=vol_seed[volume_id]) if "density" in self.volume[volume_id]: # We need to set up the neuron density functions also # TODO: Here density for each neuron type in the volume is defined # as a numexpr evaluated string of x,y,z stored in a dictionary # with the neuron type as key. # Add ability to also specify a density file. for neuron_type in self.volume[volume_id]["density"]: density_func = None if "densityFunction" in self.volume[volume_id][ "density"][neuron_type]: density_str = self.volume[volume_id]["density"][ neuron_type]["densityFunction"] density_func = lambda x, y, z: numexpr.evaluate( density_str) if "densityFile" in self.volume[volume_id]["density"][ neuron_type]: density_file = self.volume[volume_id]["density"][ neuron_type]["densityFile"] # We need to load the data from the file from scipy.interpolate import griddata with open(snudda_parse_path(density_file), "r") as f: density_data = json.load(f) assert volume_id in density_data and neuron_type in density_data[volume_id], \ f"Volume {volume_id} does not contain data for neuron type {neuron_type}" assert "Coordinates" in density_data[volume_id][neuron_type] \ and "Density" in density_data[volume_id][neuron_type], \ (f"Missing Coordinates and/or Density data for " f"volume {volume_id}, neuron type {neuron_type}") coord = np.array( density_data[volume_id][neuron_type] ["Coordinates"]) * 1e-6 # Convert to SI density = np.array(density_data[volume_id] [neuron_type]["Density"]) density_func_helper = lambda pos: griddata( points=coord, values=density, xi=pos, method="linear", fill_value=0) density_func = lambda x, y, z: density_func_helper( np.array([x, y, z]).transpose()) self.volume[volume_id]["mesh"].define_density( neuron_type, density_func) self.write_log("Using dimensions from config file") # Setup for rotations self.rotate_helper = SnuddaRotate(self.config_file) assert "Neurons" in config, \ "No neurons defined. Is this config file old format?" # Read in the neurons for name, definition in config["Neurons"].items(): neuron_name = name morph = definition["morphology"] param = definition["parameters"] mech = definition["mechanisms"] if "modulation" in definition: modulation = definition["modulation"] else: # Modulation optional modulation = None num = definition["num"] volume_id = definition["volumeID"] if "neuronType" in definition: # type is "neuron" or "virtual" (provides input only) model_type = definition["neuronType"] else: model_type = "neuron" if 'hoc' in definition: hoc = definition["hoc"] else: hoc = None if model_type == "virtual": # Virtual neurons gets spikes from a file mech = "" hoc = "" virtual_neuron = True else: virtual_neuron = False rotation_mode = definition["rotationMode"] if "axonDensity" in definition: axon_density = definition["axonDensity"] else: axon_density = None self.write_log(f"Adding: {num} {neuron_name}") self.add_neurons(name=neuron_name, swc_filename=morph, param_data=param, mech_filename=mech, modulation=modulation, num_neurons=num, hoc=hoc, volume_id=volume_id, virtual_neuron=virtual_neuron, rotation_mode=rotation_mode, axon_density=axon_density) self.config_file = config_file # We reorder neurons, sorting their IDs after position # -- UPDATE: Now we spatial cluster neurons depending on number of workers self.sort_neurons(sort_idx=self.cluster_neurons()) if False: # Debug purposes, make sure neuron ranges are ok self.plot_ranges() if "PopulationUnits" in config: self.define_population_units(config["PopulationUnits"]) mesh_logfile.close()
print("Reusing obj for " + str(mo)) obj = objLookup[mo].copy() #obj.data = objLookup[mo].data for o in objLookup[mo].children: oc = o.copy() # oc.data = o.data.copy() oc.parent = obj bpy.context.scene.objects.link(oc) bpy.context.scene.objects.link(obj) else: print("Loading morphology " + str(mo)) bpy.ops.import_mesh.swc(filepath=snudda_parse_path(mo)) obj = bpy.context.selected_objects[0] # obj = bpy.data.objects[-1] objLookup[mo] = obj eRot = mathutils.Matrix(rt.reshape(3, 3)).to_euler() obj.rotation_euler = eRot # Draw this neuron (the SWC import scales from micrometers to mm), the # positions in the simulation are in meters, need to scale it to mm for # blender to have same units. print("Setting position: " + str(ps * 1e3)) obj.location = ps * 1e3 nType = nm.decode().split("_")[0]
def has_axon(swc_file): nm = NeuronMorphology(swc_filename=snudda_parse_path(swc_file)) return len(nm.axon) > 0
def gather_all_neurons(self, neuron_types=None): all_neurons = collections.OrderedDict() assert snudda_isdir( self.neurons_path ), f"Neurons directory {self.neurons_path} does not exist." neuron_type_dir = [ d for d in glob.glob( os.path.join(snudda_parse_path(self.neurons_path), '*')) if snudda_isdir(d) ] self.neuron_types = [] if neuron_types is not None: if type(neuron_types) == str: neuron_types = [neuron_types] neuron_types = [x.lower() for x in neuron_types] for ntd in neuron_type_dir: neuron_type = os.path.basename(os.path.normpath(ntd)) if neuron_types is not None: if neuron_type.lower() not in neuron_types: print(f"Skipping neuron type {neuron_type}") continue self.neuron_types.append(neuron_type) neuron_dir = [ d for d in glob.glob(os.path.join(ntd, '*')) if os.path.isdir(d) ] neuron_ctr = 0 for nd in neuron_dir: neuron_info = collections.OrderedDict() # Find neuron morphology swc file, obs currently assume lowercase(!) neuron_morph_list = glob.glob(os.path.join(nd, '*swc')) parameter_file = os.path.join(nd, "parameters.json") mechanism_file = os.path.join(nd, "mechanisms.json") modulation_file = os.path.join(nd, "modulation.json") # Optional if len(neuron_morph_list) == 0: assert (not os.path.isfile(parameter_file) and not os.path.isfile(mechanism_file)), \ f"Directory {nd} has parameter.json or mechanism.json but no swc file." # No swc file, skipping directory continue # Check if empty neuron_morph_list, or if more than one morphology assert len(neuron_morph_list ) == 1, f"Should only be one swc file in {nd}" assert os.path.isfile( parameter_file), f"Missing parameter file {parameter_file}" assert os.path.isfile( mechanism_file), f"Missing mechanism file {mechanism_file}" neuron_info["morphology"] = snudda_simplify_path( neuron_morph_list[0]) neuron_info["parameters"] = snudda_simplify_path( parameter_file) neuron_info["mechanisms"] = snudda_simplify_path( mechanism_file) # Modulation file is optional if os.path.isfile(modulation_file): neuron_info["modulation"] = snudda_simplify_path( modulation_file) neuron_name = f"{neuron_type}_{neuron_ctr}" neuron_ctr += 1 all_neurons[neuron_name] = neuron_info if neuron_ctr > 0: print(f"Found {neuron_ctr} neurons in {ntd}") assert len(all_neurons) > 0, ( f"No neurons selected. Did you specify an incorrect neuronType? {neuron_types}" f"\nSee skipped neurons above error message for available ones.") return all_neurons
def load_swc(self, swc_file=None): if not swc_file: swc_file = snudda_parse_path(self.swc_filename) with open(swc_file, 'r') as f: lines = f.readlines() comp_type = {1: "soma", 2: "axon", 3: "dend", 4: "apic"} swc_vals = np.zeros(shape=(len(lines), 7)) num_comps = 0 for ss in lines: if ss[0] != '#': swc_vals[num_comps, :] = [float(s) for s in ss.split()] num_comps = num_comps + 1 # swcVals -- 0: compID, 1: type, 2,3,4: xyz coords, 5: radius, 6: parentID assert (1 <= swc_vals[:num_comps, 1]).all() and (swc_vals[:num_comps, 1] <= 4).all(), \ f"loadMorphology: Only types 1,2,3,4 are supported: {swc_file}" # Subtract 1 from ID and parentID, so we get easier indexing swc_vals[:, 0] -= 1 swc_vals[:, 6] -= 1 swc_vals[:, 2:6] *= 1e-6 # Convert to meter x,y,z, radie # Columns: # 0: ID, 1,2,3: x,y,z 4: radie, 5: type, 6: parent, 7: somaDist, # 8: nodeParent, 9: childCount, 10: sectionID, 11: sectionLen, # 12: segmentLen # -- careful with sectionID and sectionLen at branch points, # they belong to the parent section # -- also dont confuse sectionLen and segmentLen (the latter is # for the segment, which is a part of the larger section) points = np.zeros((num_comps, 13)) points[:num_comps, 0] = swc_vals[:num_comps, 0] # ID points[:num_comps, 1:5] = swc_vals[:num_comps, 2:6] # x,y,z,r points[:num_comps, 5] = swc_vals[:num_comps, 1] # type points[:num_comps, 6] = swc_vals[:num_comps, 6] # parent assert points[0, 5] == 1, \ "First compartment must be a soma: " + str(swc_file) # Create list of the links, # exclude soma -> first comp link (should be within soma radius) # Columns: 0: ID1, 1: ID2, 2: sectionID, 3: sectionX0, 4: sectionX1 # 5: nodeParent, 6:type links = np.zeros((num_comps, 7)) link_idx = 0 for idx in range(0, num_comps): id0 = int(points[idx, 6]) # parent id1 = int(points[idx, 0]) # point if id0 <= 0: # No parent or soma is parent, skip link continue links[link_idx, 0:2] = [id0, id1] links[link_idx, 5] = points[idx, 5] link_idx += 1 # Trim link list links = links[:link_idx, :] # Count children each node has for idx in range(1, num_comps): try: # Increment parents child counter points[int(points[idx, 6]), 9] += 1 except: self.write_log( f"Are there gaps in the numbering of the compartments in the SWC file: {swc_file}", is_error=True) import traceback tstr = traceback.format_exc() self.write_log(tstr) import pdb pdb.set_trace() # Make sure soma has a child count > 1 --- no we dont want some as node # if(points[0,9] == 0): # points[0,9] = 100 # Also make sure all points with soma as parent get child count > 1 # (Child Count > 1 ==> start or end of segment) soma_child_idx = np.where(points[:, 6] == 0)[0] points[soma_child_idx, 9] += 50 # Mark node parent, and assign sectionID # -- this is used to set sectionID for links, the link # will use the end points sectionID # !!! Make sure sectionID is correct, and match what Neuron uses internally # Nodes are branch points (> 1 child), or end points (0 children) # and not soma node_idx = np.where((points[:, 9] != 1) & (points[:, 5] != 1))[0] # soma is section 0, but we dont include connection soma to first node # so let the first dend node be 0, since the section ID is taken from # the child ID section_id = 1 # Sonata specifies first axon, then basal, then apical sections axon_idx = node_idx[np.where(points[node_idx, 5] == 2)[0]] basal_idx = node_idx[np.where(points[node_idx, 5] == 3)[0]] apical_idx = node_idx[np.where(points[node_idx, 5] == 4)[0]] # If simulation will use an axon stump, where each axon branch is shortened # to a stump with the same section ID, then we need to make sure the # numbering is correct for the dendrites. # Update, set axonID to -1 for nIdx in axon_idx: points[nIdx, 10] = -1 # Set soma ID to 0 points[0, 10] = 0 # Calculate sectionID for dendrites section_id = 1 # Axon dealt with, only loop over dendrites next node_loop_list = [basal_idx, apical_idx] for idxList in node_loop_list: for nIdx in idxList: if points[nIdx, 6] > 0: # Set section ID, exclude soma, and compartments bordering to soma points[nIdx, 10] = section_id section_id += 1 # Assign node parents for nIdx in node_idx: # Find node parent parent_idx = int(points[nIdx, 6]) # While one child (= no node), keep following parent # But stop if parent is soma, or if grandparent is soma # !!! Here last link node to soma is not included in neurite morphology # since we assume it is inside the soma while points[parent_idx, 9] == 1 and parent_idx > 0 and points[parent_idx, 6] > 0: parent_idx = int(points[parent_idx, 6]) node_parent_idx = parent_idx points[nIdx, 8] = node_parent_idx section_id = points[nIdx, 10] parent_idx = int(points[nIdx, 6]) while points[parent_idx, 9] == 1 and parent_idx > 0: points[parent_idx, 8] = node_parent_idx assert points[parent_idx, 10] == 0, "SectionID should be unset prior" points[parent_idx, 10] = section_id parent_idx = int(points[parent_idx, 6]) for idx in range(1, num_comps): parent_idx = int(points[idx, 6]) # Calculate soma dist (and also save segLen) seg_len = np.sqrt( np.sum((points[idx, 1:4] - points[parent_idx, 1:4])**2)) points[idx, 7] = points[parent_idx, 7] + seg_len points[idx, 12] = seg_len # Calculate section length (length between nodes) for idx in node_idx: node_parent_idx = int(points[idx, 8]) # Difference in soma distance is section length section_len = points[idx, 7] - points[node_parent_idx, 7] points[idx, 11] = section_len if section_len == 0: self.write_log("Section length is zero --- !!! ") import pdb pdb.set_trace() prev_idx = int(points[idx, 6]) while prev_idx > node_parent_idx: points[prev_idx, 11] = section_len prev_idx = int(points[prev_idx, 6]) # Calculate sectionX for idx in range(0, links.shape[0]): id0 = int(links[idx, 0]) id1 = int(links[idx, 1]) links[idx, 2] = points[id1, 10] # Section ID from point (not parent) node_parent = int(points[id1, 8]) node_parent_soma_dist = points[node_parent, 7] section_len = points[id1, 11] # segX0 and segX1 links[idx, 3] = (points[id0, 7] - node_parent_soma_dist) / section_len links[idx, 4] = (points[id1, 7] - node_parent_soma_dist) / section_len links[idx, 5] = node_parent links[idx, 6] = points[id0, 5] # type (use parent, # to avoid soma to dend link) # Store the soma, axon, dend and links in the object self.soma = np.zeros((1, 4)) self.soma[0, :] = swc_vals[0, 2:6] # save x,y,z,r dend_idx = np.where((points[:, 5] == 3) | (points[:, 5] == 4))[0] axon_idx = np.where(points[:, 5] == 2)[0] dend_link_idx = np.where((links[:, 6] == 3) | (links[:, 6] == 4))[0] axon_link_idx = np.where(links[:, 6] == 2)[0] # 0,1,2: x,y,z 3: radie, 4: dist to soma self.dend = np.zeros((len(dend_idx), 5)) self.axon = np.zeros((len(axon_idx), 5)) self.dend_links = np.zeros((len(dend_link_idx), 2), dtype=int) # ID0,ID1 self.axon_links = np.zeros((len(axon_link_idx), 2), dtype=int) # ID0,ID1 self.dend_sec_id = np.zeros((len(dend_link_idx), ), dtype=int) # SectionID self.dend_sec_x = np.zeros((len(dend_link_idx), 2)) # SecX0, SecX1 dend_lookup = dict([]) axon_lookup = dict([]) for idx in range(0, len(dend_idx)): dend_lookup[dend_idx[idx]] = idx for idx in range(0, len(axon_idx)): axon_lookup[axon_idx[idx]] = idx for idx, dIdx in enumerate(dend_idx): self.dend[idx, 0:4] = points[dIdx, 1:5] # x,y,z,r self.dend[idx, 4] = points[dIdx, 7] # dist to soma for idx, aIdx in enumerate(axon_idx): self.axon[idx, 0:4] = points[aIdx, 1:5] # x,y,z,r self.axon[idx, 4] = points[aIdx, 7] # dist to soma for idx, dIdx in enumerate(dend_link_idx): self.dend_links[idx, 0] = dend_lookup[int(links[dIdx, 0])] # ID0 - parent self.dend_links[idx, 1] = dend_lookup[int(links[dIdx, 1])] # ID1 self.dend_sec_id[idx] = links[dIdx, 2] self.dend_sec_x[idx, :] = links[dIdx, 3:5] for idx, aIdx in enumerate(axon_link_idx): self.axon_links[idx, 0] = axon_lookup[links[aIdx, 0]] self.axon_links[idx, 1] = axon_lookup[links[aIdx, 1]] # We also have sectionID, secX0 and secX1 saved in links[:,2:5] # if needed in the future if self.virtual_neuron: # For virtual neurons, skip the dendrites (save space) self.dend = np.zeros((0, self.dend.shape[1])) self.dend_links = np.zeros((0, 2)) self.dend_sec_id = np.zeros((0, )) self.dend_sec_x = np.zeros((0, 2)) # self.dendriteDensity() # -- depricated self.find_radius() self.place()
def visualise(self, neuron_id=None, blender_output_image=None, white_background=True, show_synapses=True): if neuron_id: neurons = [self.data["neurons"][x] for x in neuron_id] else: neurons = self.data["neurons"] neuron_id = self.data["neuronID"] if blender_output_image: self.blender_output_image = blender_output_image origo = self.data["simulationOrigo"] voxel_size = self.data["voxelSize"] # Remove the start cube # bpy.ops.object.delete() VisualiseNetwork.clean_scene() bpy.data.scenes['Scene'].render.engine = 'CYCLES' world = bpy.data.worlds['World'] world.use_nodes = True # changing these values does affect the render. bg = world.node_tree.nodes['Background'] if white_background: # Set to True for white background bg.inputs[0].default_value[:3] = (1.0, 1.0, 1.0) bg.inputs[1].default_value = 1.0 else: bg.inputs[0].default_value[:3] = (0.0, 0.0, 0.0) bg.inputs[1].default_value = 0.0 # Define materials mat_msd1 = bpy.data.materials.new("PKHG") mat_msd1.diffuse_color = (77. / 255, 151. / 255, 1.0) mat_msd2 = bpy.data.materials.new("PKHG") mat_msd2.diffuse_color = (67. / 255, 55. / 255, 181. / 255) mat_fs = bpy.data.materials.new("PKHG") mat_fs.diffuse_color = (6. / 255, 31. / 255, 85. / 255) mat_chin = bpy.data.materials.new("PKHG") mat_chin.diffuse_color = (252. / 255, 102. / 255, 0.0) mat_lts = bpy.data.materials.new("PKHG") mat_lts.diffuse_color = (150. / 255, 63. / 255, 212. / 255) mat_other = bpy.data.materials.new("PKHG") mat_other.diffuse_color = (0.4, 0.4, 0.4) mat_synapse = bpy.data.materials.new("PKHG") material_lookup = { "dspn": mat_msd1, "ispn": mat_msd2, "fsn": mat_fs, "fs": mat_fs, "chin": mat_chin, "lts": mat_lts, "synapse": mat_synapse, "other": mat_other } if white_background: mat_synapse.diffuse_color = (0.8, 0.0, 0.0) else: mat_synapse.diffuse_color = (1.0, 1.0, 0.9) # matSynapse.use_transparency = True mat_synapse.use_nodes = True if not white_background: emission_strength = 5.0 # Make synapses glow emission = mat_synapse.node_tree.nodes.new('ShaderNodeEmission') emission.inputs['Strength'].default_value = emission_strength material_output = mat_synapse.node_tree.nodes.get( 'Material Output') mat_synapse.node_tree.links.new(material_output.inputs[0], emission.outputs[0]) for neuron in neurons: e_rot = mathutils.Matrix(neuron["rotation"].reshape(3, 3)).to_euler() if neuron["name"] in self.neuron_cache: # If we already have the object in memory, copy it. obj = self.neuron_cache[neuron["name"]].copy() if self.neuron_cache[neuron["name"]].data: obj.data = self.neuron_cache[neuron["name"]].data.copy() VisualiseNetwork.copy_children( self.neuron_cache[neuron["name"]], obj) obj.animation_data_clear() # Will return None if there is no obj named CUBe obj.name = f"{neuron['name']}-{neuron['neuronID']}" VisualiseNetwork.link_object(obj) else: bpy.ops.import_mesh.swc( filepath=snudda_parse_path(neuron["morphology"])) obj = bpy.context.selected_objects[0] obj.name = f"{neuron['name']}-{neuron['neuronID']}" self.neuron_cache[neuron["name"]] = obj obj.rotation_euler = e_rot print( f"Setting neuron {neuron['neuronID']} ({neuron['name']}) position: {neuron['position'] * 1e3}" ) obj.location = neuron["position"] * 1e3 n_type = neuron["type"].lower() if n_type in material_lookup: mat = material_lookup[n_type] else: mat = material_lookup["other"] for ch in obj.children: ch.active_material = mat obj.select = False if show_synapses: print("Adding synapses...") # Draw the synapses n_synapses = 0 for ob in bpy.context.selected_objects: ob.select = False synapse_obj = None for vis_pre_id in neuron_id: for vis_post_id in neuron_id: synapses, synapse_coords = self.sl.find_synapses( pre_id=vis_pre_id, post_id=vis_post_id) if synapses is None: # No synapses between pair continue for syn in synapses: pre_id = syn[0] post_id = syn[1] assert pre_id == vis_pre_id and post_id == vis_post_id # Just sanity check, should be true # Draw this neuron (the SWC import scales from micrometers to mm), the # positions in the simulation are in meters, need to scale it to mm for # blender to have same units. x = (origo[0] + voxel_size * syn[2]) * 1e3 y = (origo[1] + voxel_size * syn[3]) * 1e3 z = (origo[2] + voxel_size * syn[4]) * 1e3 if synapse_obj: obj = synapse_obj.copy() if synapse_obj.data: obj.data = synapse_obj.data.copy() obj.animation_data_clear() obj.location = (x, y, z) obj.name = f"synapse-{n_synapses}" VisualiseNetwork.link_object(obj) else: bpy.ops.mesh.primitive_uv_sphere_add( location=(x, y, z), size=0.001 * 4) obj = bpy.context.selected_objects[0] obj.active_material = mat_synapse obj.select = False synapse_obj = obj n_synapses += 1 # print(f"Added synapse #{n_synapses} at {[x, y, z]}") if n_synapses % 5000 == 0: print(f"Synapses added so far: {n_synapses}") print(f"nSynapses = {n_synapses}") # Add a light source lamp_data = bpy.data.lamps.new(name="Sun", type='SUN') lamp_object = bpy.data.objects.new(name="Sun", object_data=lamp_data) bpy.context.scene.objects.link(lamp_object) # Place lamp to a specified location lamp_object.location = (1000.0, 1000.0, 1000.0) # Reposition camera cam = bpy.data.objects["Camera"] # cam.location = (2.98,2.68,4.96) # cam.rotation_euler = (1.59,0,-0.26) cam.location = (3.19, 3.46, 4.96) cam.rotation_euler = (96.7 * np.pi / 180, 0, -14.3 * np.pi / 180) # Is this needed? bpy.context.scene.update() bpy.ops.wm.save_as_mainfile(filepath=self.blender_save_file) if self.blender_output_image: print("Rendering image.") bpy.ops.render.render() bpy.data.images['Render Result'].save_render( filepath=self.blender_output_image)