2
$\begingroup$

I want to make 3D visualisation of a neuron. How can I plot curves from a set of points ? The data is shown in the image.

I found an artist created a mesh with this data. Same posted below. enter image description here

Data is from this website. http://neuromorpho.org/neuron_info.jsp?neuron_name=211-6mt

enter image description here

$\endgroup$
1
  • $\begingroup$ Very close to this recent question blender.stackexchange.com/questions/153717/… (about creating curves) From that should be just matter of reading the file and follow the links between data lines. $\endgroup$
    – lemon
    Sep 25, 2019 at 17:52

1 Answer 1

3
$\begingroup$

From the provided data file, you can obtain this kind of rendering, with (for example) either creating splines (curves) or creating a mesh with skin modifier.

enter image description here

enter image description here

The script allows to:

  • Load the data from the indicated site

  • Convert it to inner objects (NeuronPoint class below)

  • Optionally add some random intermediate points between the loaded coordinates

And

  • Either convert them to curve, creating a new curve for each data ramification

  • Or create a mesh from this point and add a skin modifier in order to obtain the neuron branches

Here is the commented code and blend file:

import bpy
import random
from mathutils import Vector

# Store the information from the SWC text file
class NeuronPoint:
    def __init__(self, id, type, x, y, z, radius, parent_id):
        self.id = id
        self.type = type
        self.x = x
        self.y = y
        self.z = z
        self.radius = radius
        self.parent_id = parent_id
        self.parent = None
        self.children = []

    @property
    def coordinates(self):
       return Vector( (self.x, self.y, self.z) )

# Read data from the file
def read_neuron_points( file_name ):
    file = open(file_name, "r")
    points = {}
    for line in [l for l in file if l and l[0] != '#']:
        string_data = line.split()
        if string_data:
            id = int(string_data[0]) - 1
            neuron_type = int(string_data[1])
            x = float(string_data[2])
            y = float(string_data[3])
            z = float(string_data[4])
            radius = float(string_data[5])
            parent_id = int(string_data[6]) - 1

            neuron_point = NeuronPoint(id, neuron_type, x, y, z, radius, parent_id)
            points[id] = neuron_point

    for point in points.values():
        point.parent = points.get( point.parent_id )
        if point.parent:
            point.parent.children.append( point )

    return [p for p in points.values()]

def random_vector( random_amount ):
    return Vector( (random.uniform(0,1), random.uniform(0,1), random.uniform(0,1)) ) * random_amount

# Cuts the data to have additional random points
def make_intermediate_points( points, point, min_distance, random_amount ):
    point_co = point.coordinates
    parent = point.parent
    parent_co = parent.coordinates
    vector = point_co - parent_co
    distance = vector.length
    # Do it only if distance is above the parameter
    if distance > min_distance:
        # Cut in half + random
        intermediate_co = parent_co + (vector * 0.5) + random_vector(random_amount)
        intermediate_radius = (parent.radius + point.radius) * 0.5
        # Insert the new point in the hierarchy
        new_point = NeuronPoint( len(points), point.type, *intermediate_co, intermediate_radius, parent.id )
        points.append( new_point )
        point.parent = new_point
        point.parent_id = new_point.id
        parent.children.remove( point )
        parent.children.append( new_point )
        new_point.parent = parent
        new_point.children.append( point )

        # Cut around this new point        
        make_intermediate_points( points, point, min_distance, random_amount )
        make_intermediate_points( points, new_point, min_distance, random_amount )

# Cuts the data to have additional random points
def subdivide_points( points, min_distance, random_amount ):
    for point in [p for p in points if p.parent]:
        make_intermediate_points( points, point, min_distance, random_amount )
    return points

def mesh_from_neuron_points( context, name, points, scale_factor = 0.01, radius_factor = 1 ):

    # Gets vertices and edges from the points data
    verts = [p.coordinates * scale_factor for p in points]
    edges = [(p.id, p.parent_id) for p in points if p.parent]

    # Create a mesh
    mesh = bpy.data.meshes.new(name)
    mesh.from_pydata(verts, edges, [])

    # Create an object with this mesh
    obj = bpy.data.objects.new(name, mesh)

    # Add a subdivision surface (smooth the edges)
    subdivision = obj.modifiers.new( "subdivision", 'SUBSURF' )
    subdivision.render_levels = 1
    subdivision.levels = 1
    # Add a skin modifier in order to have thickness
    skin = obj.modifiers.new( "skin", 'SKIN' )
    skin.branch_smoothing = 0.5
    skin.use_smooth_shade = True
    # Smooth again with another subdivision
    subdivision = obj.modifiers.new( "subdivision", 'SUBSURF' )

    # Associates the radius to each skin vertex
    for s, p in zip( obj.data.skin_vertices[''].data, points ):
        radius = p.radius * radius_factor
        if not p.children:
            radius *= 0.1
        s.radius = (radius, radius)

    # Link the object to the scene
    context.scene.collection.objects.link(obj)
    return obj

# Cuts the point set into continuous spline parts, creating a new part for each ramification
def splines_points_from_points( points ):
    result = []
    base_points = [p for p in points if not p.children]

    while base_points:
        next_points = []
        for point in base_points:
            current = [point]
            result.append(current)
            while point.parent:
                point = point.parent
                current.append(point)
                if len(point.children) > 1:
                    next_points.append( point )
                    break
        base_points = next_points

    for spline in result:
        spline.reverse()

    return result

def curve_from_neuron_points( context, name, points, scale_factor = 0.01, radius_factor = 1 ):
    # Create a curve with some bevel depth
    curve = bpy.data.curves.new(name=name, type='CURVE')
    curve.dimensions = '3D'
    curve.bevel_depth = 1

    # Create an object with it    
    obj = bpy.data.objects.new(name, curve)

    # Calculate splines parts
    splines_points = splines_points_from_points( points )

    for spline_points in splines_points:
        # Create a spline for each part
        bezier_curve = curve.splines.new('BEZIER')
        bezier_curve.bezier_points.add(len(spline_points)-1)
        # Set the points
        for bezier, point in zip(bezier_curve.bezier_points, spline_points):
            bezier.co = point.coordinates * scale_factor
            bezier.radius = point.radius * radius_factor
            if not point.children:
                bezier.radius *= 0.01

    # Link object to the scene
    context.scene.collection.objects.link(obj)
    # Toggle handle type (faster than doing it point by point)
    obj.select_set( True )
    context.view_layer.objects.active = obj
    bpy.ops.object.editmode_toggle()
    bpy.ops.curve.select_all(action='SELECT')
    bpy.ops.curve.handle_type_set(type='AUTOMATIC')
    bpy.ops.object.editmode_toggle()

    return obj

# Get the file name relative to this blend file
file_name = bpy.path.abspath("//test.txt")
# Read the file into points
points = read_neuron_points( file_name )

# Some parameters to handle result scale
scale_factor = 0.1
radius_factor = 0.1
# Subdivide the model with some random (optional)
points = subdivide_points( points, min_distance = 4, random_amount = 1 )
# Create the object as mesh with skin modifier
#obj = mesh_from_neuron_points( bpy.context, "test", points, scale_factor = scale_factor, radius_factor = radius_factor )
# Create the object as curve
obj = curve_from_neuron_points( bpy.context, "test", points, scale_factor = scale_factor, radius_factor = radius_factor )

# Set a material on it, if defined
mat = bpy.data.materials.get( "Material" )
if mat:
    obj.data.materials.append(mat)

$\endgroup$
2
  • $\begingroup$ Wow great work. Thanks alot lemon. Where in the code I should add the coordinates? $\endgroup$ Sep 29, 2019 at 16:49
  • $\begingroup$ @RafeequeBinUsman, if you mean where the coordinates are read from the file, it is in the function called "read_neuron_points( file_name )" $\endgroup$
    – lemon
    Oct 10, 2019 at 4:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .