navis-org / pymaid

Python library to interface with CATMAID servers. Fully interoperable with navis.
https://pymaid.readthedocs.io/en/latest/
GNU General Public License v3.0
23 stars 11 forks source link

Exporting skeletons as object files #243

Open conochur opened 5 months ago

conochur commented 5 months ago

I know that there's a feature in the GUI to export skeleton selections as .obj files, but can one do the same using PyMaid?

schlegelp commented 5 months ago

Yes, in theory! In practice it depends a bit on what exactly it is you are after.

The obj format is (to my knowledge) most commonly used for meshes, i.e. an object with faces and vertices. It's pretty straight forward to convert a skeleton into a tubular mesh. OBJ appears to also support curve/line objects though. Not sure which of the two option CATMAID is exporting?

conochur commented 5 months ago

Thanks for the response! I'm looking for a mesh file that I can pop into other rendering software. But would like to do it programmatically. Do you have any recommendations for accomplishing that from python using the coordinate and radius data?

schlegelp commented 5 months ago

That functionality fortunately already exists. Here's a minimal example:

>>> import pymaid
>>> import navis

>>> # Grab your CATMAID skeleton
>>> s = pymaid.get_neuron()
>>> type(s)
pymaid.core.CatmaidNeuron

>>> # Turn the skeleton into a tubular mesh
>>> # You can tune the resolution using `tube_points` parameter
>>> m = navis.conversion.tree2meshneuron(s)
>>> type(m)
navis.core.mesh.MeshNeuron

>>> # Save as OBJ
>>> _ = m.trimesh.export('skeleton_mesh.obj')

Screenshot 2024-04-04 at 10 15 40 Original skeleton in yellow; mesh generated from skeleton in blue.

A couple notes:

  1. These meshes can get fairly large. You could consider e.g. downsampling the neuron before or after turning it into a mesh.
  2. The mesh will look funny if any of the node radii are missing or <=0 and the function will give a warning if that's the case. You can clip radii at a minimum radius like so:
    >>> s.nodes.loc[s.nodes.radius.fillna(0) <= 0), 'radius'] = 100