Macro problem (python) for skeleton length measurement

Hello,

I was wondering if anyone could help me with this macro to measure the length of a skeleton over time (to get filament extension rates). The code I found online is in Python format. My problem is that is saves each frame as a separate .xls and I would like it to print the lengths in the same results window instead. Can anyone help? If there is a better tool out there, I’d be grateful for any advice. Thank you for your help!

from ij import IJ, ImagePlus
from java.lang import Runtime, Runnable
 
# the current image
imp = IJ.getImage()
stack = imp.getStack()
 
# for all the frames in the movie, run  "Analyze Skeleton (2D/3D)" (maybe later in multiple threads)
for i in range(1, imp.getNFrames() + 1):  # remember a python range (1, 10) is the numbers 1 to 9 !
#getNFrames not getNSlices since input data is a 2D time series stack NOT a 3D stack.
  slice = ImagePlus(str(i), stack.getProcessor(i))
  # Execute plugin exactly on this slice i
  IJ.run(slice, "Analyze Skeleton (2D/3D)", "")
  #Save the results for this frame as a .xls file
  IJ.saveAs("Measurements", ("/Users/dthomson/Desktop/16-4-14/results/" + str(i) + ".xls") )
 
 
# turn all the output tagged images into a stack - seems to be in the right order even though all the output frames have the same name
IJ.run("Images to Stack", "name=TaggedMovie title=Tagged use")

I found another python-based macro which works well. You have to have your skeleton already made before analysing. Script below:

import math
import sys, os
from ij import IJ, ImagePlus
from java.lang import Runtime, Runnable
##Below will work for early and new versions of FIJI
try:
    from sc.fiji.analyzeSkeleton import AnalyzeSkeleton_
except:
    from skeleton_analysis import AnalyzeSkeleton_

BASIC_NAMES = [ 'Frame', 'Branches', 'Junctions', 'End-point Voxels',
                'Junction Voxels', 'Slab Voxels', 'Average branch length',
                'Triple Points', 'Quadruple Points', 'Maximum Branch Length' ]
DETAIL_NAMES = [ 'Skeleton ID', 'Branch length', 'V1 x', 'V1 y', 'V1 z', 'V2 x',
                 'V2 y', 'V2 z', 'Euclidean distance' ]

def get_euclidean_distance( vertex1, vertex2 ):
    x1, y1, z1 = get_points( vertex1 )
    x2, y2, z2 = get_points( vertex2 )
    return math.sqrt( math.pow( ( x2 - x1 ), 2 ) +
                      math.pow( ( y2 - y1 ), 2 ) +
                      math.pow( ( z2 - z1 ), 2 ) )

def get_graph_length( graph ):
    length = 0
    for edge in graph.getEdges():
        length = length + edge.getLength()
    return length

def get_points( vertex ):
    # An array of Point, which has attributes x,y,z.
    point = vertex.getPoints()[ 0 ]
    return point.x, point.y, point.z
    
def get_sorted_edge_lengths( graph ):
    # Return graph edges sorted from longest to shortest.
    edges = graph.getEdges()
    edges = sorted( edges, key=lambda edge: edge.getLength(), reverse=True )
    return edges

def get_sorted_graph_lengths( result ):
    # Get the separate graphs (skeletons).
    graphs = result.getGraph()
    # Sort graphs from longest to shortest.
    graphs = sorted( graphs, key=lambda g: get_graph_length( g ), reverse=True )
    return graphs

def save( frame, result, outf, show_detailed_info, calculate_largest_shortest_path, sep='\t' ):
    num_trees = int( result.getNumOfTrees() )
    
    for index in range( num_trees ):
        outf.write( '%d%s' % ( frame, sep ) )
        outf.write( '%d%s' % ( result.getBranches()[ index ], sep ) )
        outf.write( '%d%s' % ( result.getJunctions()[ index ], sep ) )
        outf.write( '%d%s' % ( result.getEndPoints()[ index ], sep ) )
        outf.write( '%d%s' % ( result.getJunctionVoxels()[ index ], sep ) )
        outf.write( '%d%s' % ( result.getSlabs()[ index ], sep ) )
        outf.write( '%.3f%s' % ( result.getAverageBranchLength()[ index ], sep ) )
        outf.write( '%d%s' % ( result.getTriples()[ index ], sep ) )
        outf.write( '%d%s' % ( result.getQuadruples()[ index ], sep ) )
        outf.write( '%.3f' % result.getMaximumBranchLength()[ index ] )
        if calculate_largest_shortest_path:
            outf.write( '%s%.3f%s' % ( sep, result.shortestPathList.get( index ), sep ) )
            outf.write( '%d%s' % ( result.spStartPosition[ index ][ 0 ], sep ) )
            outf.write( '%d%s' % ( result.spStartPosition[ index ][ 1 ], sep ) )
            outf.write( '%d\n' % result.spStartPosition[ index ][ 2 ] )
        else:
            outf.write( '\n' )
    if show_detailed_info:
        outf.write( '%s\n' % sep.join( DETAIL_NAMES ) )
        # The following index is a placeholder for the skeleton ID.
        # The terms "graph" and "skeleton" refer to the same thing.
        # Also, the SkeletonResult.java code states that the
        # private Graph[] graph object is an array of graphs (one
        # per tree).
        for index, graph in enumerate( get_sorted_graph_lengths( result ) ):
            for edge in get_sorted_edge_lengths( graph ):
                vertex1 = edge.getV1()
                x1, y1, z1 = get_points( vertex1 )
                vertex2 = edge.getV2()
                x2, y2, z2 = get_points( vertex2 )
                outf.write( '%d%s' % ( index+1, sep ) )
                outf.write( '%.3f%s' % ( edge.getLength(), sep ) )
                outf.write( '%d%s' % ( x1, sep ) )
                outf.write( '%d%s' % ( y1, sep ) )
                outf.write( '%d%s' % ( z1, sep ) )
                outf.write( '%d%s' % ( x2, sep ) )
                outf.write( '%d%s' % ( y2, sep ) )
                outf.write( '%d%s' % ( z2, sep ) )
                outf.write( '%.3f' % get_euclidean_distance( vertex1, vertex2 ) )
                if calculate_largest_shortest_path:
                    # Keep number of separated items the same for each line.
                    outf.write( '%s %s' % ( sep, sep ) )
                    outf.write( ' %s' % sep )
                    outf.write( ' %s' % sep )
                    outf.write( ' \n' )
                else:
                    outf.write( '\n' )

def get_prune_index(analyze_skeleton, prune_cycle_method):

    if prune_cycle_method == 'none':
        prune_index  = analyze_skeleton.NONE
    elif prune_cycle_method == 'shortest_branch':
        prune_index  = analyze_skeleton.SHORTEST_BRANCH
    elif prune_cycle_method == 'lowest_intensity_voxel':
        prune_index  = analyze_skeleton.LOWEST_INTENSITY_VOXEL
    elif prune_cycle_method == 'lowest_intensity_branch':
        prune_index  = analyze_skeleton.LOWEST_INTENSITY_BRANCH
    else:
        prune_index  = analyze_skeleton.NONE

    return prune_index

########################################################
# Options
########################################################

black_background = True

prune_cycle_method = 'none'
#prune_cycle_method = 'shortest_branch'
#prune_cycle_method = 'lowest_intensity_voxel'
#prune_cycle_method = 'lowest_intensity_branch'

prune_ends = True
calculate_largest_shortest_path = True
show_detailed_info = False
display_images = True

sep = ','

if calculate_largest_shortest_path:
    BASIC_NAMES.extend( [ 'Longest Shortest Path', 'spx', 'spy', 'spz' ] )
    DETAIL_NAMES.extend( [ ' ', ' ', ' ', ' ' ] )

########################################################
# Input image
########################################################

# the current image
imp = IJ.getImage()
stack = imp.getStack()

# Getting the image path and filename. Will replace the extension with csv.
fileinfo = imp.getOriginalFileInfo()
filename = fileinfo.fileName
directory = fileinfo.directory

filename_out, ext = os.path.splitext(filename)
filename_out = os.path.join(directory, filename_out+".csv")

IJ.log("Will save data to:")
IJ.log(filename_out)

########################################################
# Make an image copy to work with
########################################################

input_image_plus_copy = imp.duplicate()
image_processor_copy = input_image_plus_copy.getProcessor()

########################################################
# Processing...
# TODO Add cumulative length from
# http://imagej.net/AnalyzeSkeleton
########################################################

try:
    # Set binary options.
    #options = jython_utils.get_binary_options( black_background=black_background )
    #IJ.run( input_image_plus_copy ) #, "Options...", options )

    # Convert image to binary if necessary.
    if not image_processor_copy.isBinary():
        IJ.run( input_image_plus_copy, "Make Binary", "" )
        
    analyze_skeleton = AnalyzeSkeleton_()

    stack = input_image_plus_copy.getStack()

    outf = open( filename_out, 'wb' )
    outf.write( '%s\n' % sep.join( BASIC_NAMES ) )

    for i in range(1, imp.getNFrames() + 1):  # remember a python range (1, 10) is the numbers 1 to 9 !
        #getNFrames not getNSlices since input data is a 2D time series stack NOT a 3D stack.
        slice = ImagePlus(str(i), stack.getProcessor(i))

        analyze_skeleton.setup( "", slice )
        prune_index = get_prune_index(analyze_skeleton, prune_cycle_method)

        # Perform analysis in silent mode
        # (work on a copy of the ImagePlus if you don't want it displayed)
        # run(int pruneIndex, boolean pruneEnds, boolean shortPath, ImagePlus origIP, boolean silent, boolean verbose)
        
        result = analyze_skeleton.run( prune_index,
            prune_ends,
            calculate_largest_shortest_path,
            slice,
            not display_images,
            True )
        save( i, result, outf, show_detailed_info, calculate_largest_shortest_path ,sep)
        
        IJ.log(str(result))

    outf.close()

    # turn all the output tagged images into a stack - seems to be in the right order even though all the output frames have the same name
    if display_images:
        IJ. run("Images to Stack", "name=TaggedMovie title=Tagged use")
        IJ. run("Images to Stack", "name=LongestShortestMovie title=Longest use")

except Exception, e:
    IJ.log(str(e))


IJ.log("Done!")
2 Likes