1
$\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 '19 at 17:52
1
$\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 '19 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 '19 at 4:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.