Example #1
0
    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)
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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()
Example #5
0
        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]
Example #6
0
    def has_axon(swc_file):
        nm = NeuronMorphology(swc_filename=snudda_parse_path(swc_file))

        return len(nm.axon) > 0
Example #7
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
Example #8
0
    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()
Example #9
0
    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)