Scripting to export track movies using the CaptureOverlayAction function

Hi,

How exactly should I script to execute the CaptureOverlayAction function?

I tried CaptureOverlayAction().execute(trackmate) but got the following error:
TypeError: fiji.plugin.trackmate.action.CaptureOverlayAction(): expected 1 args; got 0

I am not familiar with java so will appreciate your help on this!

Thanks!

Please stay tuned for TrackMate v3.8.0 due next week, that will ship a new overlay capture action.

Hi!
I would like to bump this old conversation. I’m now trying to do the same as Natacoman and have encountered the same error. I’m writing a Jython script that runs TrackMate and I would in the end like to export the overlay with spots and tracks as for example a .avi or a series of tif images. I, however, encounter the problems that the overlay doesn’t come along so I thought I should run the CaptureOverlayAction but I don’t know how to do so.

My initial attempt was similar as Natacoman
overlay = CaptureOverlayAction()
overlay.execute(trackmate)

but it seems I need to send TrackMateWizard object to the CaptureOverlayAction constructor.

Best regards
Tobias

Hello @bias

Yes, you need to pass the gui object to the constructor. Look at the constructor signature:


	public CaptureOverlayAction( final TrackMateWizard gui )
	{
		this.gui = gui;
	}

What does your script looks like?

Hi,
My script is pretty similar to the “A full example” in https://imagej.net/Scripting_TrackMate
I dont have have a gui object, do you have a example how to create it?

I guess I should create a TrackMateWizard object but I saw that the constructor for that class also wants a argument “TrackMateGUIController”

public TrackMateWizard( final TrackMateGUIController controller )
{
this.controller = controller;
initGUI();
}

I just added a few lines in the end to export the overlay as this:

	overlay = CaptureOverlayAction()
	overlay.execute(trackmate)

I thought the action would be similar to implement as the “ExportStatsToIJAction”, I used this one as (which is working fine maybe not the best-looking solution but it works at least)

	esta = ExportStatsToIJAction()
	esta.execute(trackmate)

	win_names = ["Spots in tracks statistics","Links in tracks statistics","Track statistics"]

	windows = [WindowManager.getWindow(i) for i in win_names]
	 
	for i in windows:
		WindowManager.setWindow(i)
		IJ.saveAs("Results", os.path.join(analysis_path,i.title + ".csv"))

Can you copy / paste your full script? As you have it?

Sure, its a bit of a mess though since I’m running trackmate inside a class function to make it easier to run analysis of multiple cases:

import os, glob, sys

import csv

from ij import IJ,ImageJ, ImageStack,WindowManager

from __builtin__ import None


from ij.plugin import ImageCalculator
from ij.plugin import FolderOpener
from ij.plugin import ZProjector


import logging
logging.basicConfig(level=logging.DEBUG)

import fiji.plugin.trackmate.Settings as Settings
import fiji.plugin.trackmate.Model as Model
import fiji.plugin.trackmate.SelectionModel as SelectionModel
import fiji.plugin.trackmate.TrackMate as TrackMate
import fiji.plugin.trackmate.Logger as Logger
import fiji.plugin.trackmate.detection.DetectorKeys as DetectorKeys
import fiji.plugin.trackmate.detection.DogDetectorFactory as DogDetectorFactory
from fiji.plugin.trackmate.detection import LogDetectorFactory
import fiji.plugin.trackmate.tracking.sparselap.SparseLAPTrackerFactory as SparseLAPTrackerFactory
import fiji.plugin.trackmate.tracking.LAPUtils as LAPUtils
import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer as HyperStackDisplayer
import fiji.plugin.trackmate.features.FeatureFilter as FeatureFilter
import fiji.plugin.trackmate.features.FeatureAnalyzer as FeatureAnalyzer
import fiji.plugin.trackmate.features.spot.SpotContrastAndSNRAnalyzerFactory as SpotContrastAndSNRAnalyzerFactory
import fiji.plugin.trackmate.action.ExportStatsToIJAction as ExportStatsToIJAction
import fiji.plugin.trackmate.io.TmXmlReader as TmXmlReader
import fiji.plugin.trackmate.action.ExportTracksToXML as ExportTracksToXML
import fiji.plugin.trackmate.io.TmXmlWriter as TmXmlWriter
import fiji.plugin.trackmate.features.ModelFeatureUpdater as ModelFeatureUpdater
import fiji.plugin.trackmate.features.SpotFeatureCalculator as SpotFeatureCalculator
import fiji.plugin.trackmate.features.spot.SpotContrastAndSNRAnalyzer as SpotContrastAndSNRAnalyzer
import fiji.plugin.trackmate.features.spot.SpotIntensityAnalyzerFactory as SpotIntensityAnalyzerFactory
import fiji.plugin.trackmate.features.spot.SpotRadiusEstimatorFactory as SpotRadiusEstimatorFactory
import fiji.plugin.trackmate.features.edges.EdgeVelocityAnalyzer as EdgeVelocityAnalyzer
import fiji.plugin.trackmate.features.edges.EdgeTargetAnalyzer as EdgeTargetAnalyzer
import fiji.plugin.trackmate.features.edges.EdgeTimeLocationAnalyzer as EdgeTimeLocationAnalyzer
import fiji.plugin.trackmate.features.track.TrackSpeedStatisticsAnalyzer as TrackSpeedStatisticsAnalyzer
import fiji.plugin.trackmate.util.TMUtils as TMUtils
import fiji.plugin.trackmate.features.track.TrackDurationAnalyzer as TrackDurationAnalyzer
import fiji.plugin.trackmate.features.track.TrackLocationAnalyzer as TrackLocationAnalyzer
import fiji.plugin.trackmate.features.track.TrackSpotQualityFeatureAnalyzer as TrackSpotQualityFeatureAnalyzer
import fiji.plugin.trackmate.action.ExportTracksToXML as ExportTracksToXML
from fiji.plugin.trackmate.action import TrackBranchAnalysis as TrackBranchAnalysis
from fiji.plugin.trackmate.action import CaptureOverlayAction as CaptureOverlayAction
from fiji.plugin.trackmate.gui import TrackMateWizard as TrackMateWizard
from fiji.plugin.trackmate.gui import TrackMateGUIController

import java.io.File as File
from java.lang import System



class pre_fiji_case:
    def __init__(self, path, overwrite_settings=False, overwrite_movie=True):

        if path.endswith('/'):
            path = path[:-1]

        self.path = path
        self.analysis_path = os.path.join(path,'analysis')
        
        self.overwrite_settings = overwrite_settings
        self.overwrite_movie = overwrite_movie
        self.case_file_path = os.path.join(self.analysis_path, "case.settings")

        #Default settings (Will be overwritten if case.settings file exists)
        self.background_removal = "max"
        self.mask_threshold_method = "Default"
        self.fill_hole = 1
        self.track_threshold = 0.1

        #Figure out tracking diameter based on naming convention
        #FolderName: particleMaterial_ParticleSize_WallMaterial_FallHeight_Temperature
        
        case_name = os.path.basename(path)
        try:
        	logging.debug("The split was {}".format(case_name.split('_')))
        	self.track_diameter = float(case_name.split('_')[1].replace('mm',''))
        	logging.debug("Read tracking diameter from folder name as {}mm".format(self.track_diameter))
        except (ValueError,IndexError) as e:
        	logging.warning("Did not manage to guess tracking diameter from folder name!")
        	self.track_diameter = 1.0


        if os.path.exists(self.analysis_path) == False:
			try:
			    os.mkdir(self.analysis_path)
			except OSError:
			    logging.debug("Creation of the directory %s failed" % self.analysis_path)
			else:
			    logging.debug("Successfully created the directory %s " % self.analysis_path)
	
        #If case.settings file exists read and import settings for case
        if os.path.exists(self.case_file_path) and not self.overwrite_settings:
            logging.debug("Reading settings from file instead of defaults!")
            with open(self.case_file_path) as file:
                readCSV = csv.reader(file, delimiter=',')
                csv_file = zip(*readCSV)
                for i in csv_file:
                    col = list(i)
                    logging.debug("Reading {}".format(col))
                    if col[0] == "background_removal":
                        self.background_removal = col[1]
                    elif col[0] == "mask_threshold_method":
                        self.mask_threshold_method = col[1]
                    elif col[0] == "fill_hole":
                        self.fill_hole = bool(int(col[1]))
                    elif col[0] == "track_diameter":
                        self.track_diameter = float(col[1])
                    elif col[0] == "track_threshold":
                        self.track_threshold = float(col[1])
                    
        else:
            #if no case.settings set default settings
            with open(self.case_file_path, 'w') as file:
                writer = csv.writer(file)
                writer.writerow(["background_removal", "mask_threshold_method", "fill_hole","track_diameter", "track_threshold"])
                writer.writerow([self.background_removal, self.mask_threshold_method, self.fill_hole, self.track_diameter, self.track_threshold])

    def run_fiji_analysis(self):
        # Run fiji
        self.open_in_fiji()

        self.set_scalebar()

        self.export_movie(name=self.case_name)

        logging.debug("Running analysis on case: {}".format(self.case_name))

        # Pre-segmentation
        IJ.run(self.imp,"32-bit", "")

    
        #IJ.run(self.imp, "Invert", "stack")

        #Reorder stacks Z and T
        #

        self.export_snapshot(name='Step_0_32bit')


       
        # Remove gradient background
        if self.background_removal is not None:
            logging.debug("\tBackground removal: {}".format(self.background_removal))

            IJ.run(self.imp, "Subtract Background...", "rolling=50 light stack")
            #IJ.run(self.imp, "Median...", "radius=5 stack")

            self.export_snapshot(name='Step_1_removed_background_32bit')

        
        # Segmentation
        if self.mask_threshold_method is not None:
            logging.debug("\tThreshold: {}".format(self.mask_threshold_method))

            IJ.run(self.imp, "Convert to Mask", "method="+self.mask_threshold_method + " background=Light calculate")
            
            self.export_snapshot(name='Step_2_masked_8bit')
        # Post-segmentation
        if self.fill_hole == True:
            logging.debug("\tFilling mask holes.")
            IJ.run(self.imp,"Fill Holes", "stack")

            self.export_snapshot(name='Step_3_filled_holes_8bit')
            
        logging.debug("\tStarting TrackMate")
        self.run_trackmate_analysis()

        
        

    def run_trackmate_analysis(self):


		#Calculated how high intensity a full sphere is assuming 8-bit
		pixel_max = 255
		
		cal = self.imp.getCalibration()
		x = cal.pixelWidth
		y = cal.pixelHeight
		area_per_pixel = 1/x*y
		area_sphere = 3.14*(0.5*self.track_diameter)*(0.5*self.track_diameter)
		
		total_intensity_full_sphere = pixel_max*area_per_pixel/(area_sphere)

		model = Model()

		# Swap Z and T dimensions if T=1
		dims = self.imp.getDimensions() # default order: XYCZT
		if (dims[4] == 1):
			self.imp.setDimensions( dims[2],dims[4],dims[3] )

		# Send all messages to ImageJ log window.
		model.setLogger(Logger.IJ_LOGGER)

		#------------------------
		# Prepare settings object
		#------------------------
		       
		settings = Settings()
		settings.setFrom(self.imp)
		
		
		       
		# Configure detector - We use the Strings for the keys
		settings.detectorFactory = LogDetectorFactory()
		settings.detectorSettings = { 
		    'DO_SUBPIXEL_LOCALIZATION' : True,
		    'RADIUS' : float(0.5*self.track_diameter),
		    'THRESHOLD' : self.track_threshold,
		    'DO_MEDIAN_FILTERING' : False,
		}  
		
		
		filter1 = FeatureFilter('QUALITY', 0.5, True)
		settings.addSpotFilter(filter1)
		
		filter2 = FeatureFilter('TOTAL_INTENSITY', total_intensity_full_sphere*0.8, True)
		settings.addSpotFilter(filter2)
		    
		# Configure tracker
		settings.trackerFactory = SparseLAPTrackerFactory()
		settings.trackerSettings = LAPUtils.getDefaultLAPSettingsMap()
		settings.trackerSettings['LINKING_MAX_DISTANCE'] = 20.0
		settings.trackerSettings['GAP_CLOSING_MAX_DISTANCE'] = 20.0
		settings.trackerSettings['MAX_FRAME_GAP']= 8


		settings.addSpotAnalyzerFactory(SpotIntensityAnalyzerFactory())
		settings.addSpotAnalyzerFactory(SpotRadiusEstimatorFactory())
		settings.addSpotAnalyzerFactory(SpotContrastAndSNRAnalyzerFactory())
		   
		# Add an analyzer for some track features, such as the track mean speed.
		settings.addTrackAnalyzer(TrackSpeedStatisticsAnalyzer())
		settings.addTrackAnalyzer(TrackDurationAnalyzer())
		settings.addTrackAnalyzer(TrackLocationAnalyzer())
		settings.addTrackAnalyzer(TrackSpotQualityFeatureAnalyzer())
		
		settings.addEdgeAnalyzer(EdgeVelocityAnalyzer())
		settings.addEdgeAnalyzer(EdgeTargetAnalyzer())
		settings.addEdgeAnalyzer(EdgeTimeLocationAnalyzer())
		
		
		filter3 = FeatureFilter('TRACK_DURATION', 3, True)
		settings.addTrackFilter(filter3)
		   
		settings.initialSpotFilterValue = 0
		  
		#----------------------
		# Instantiate trackmate
		#----------------------
		   
		trackmate = TrackMate(model, settings)
		      
		#------------
		# Execute all
		#------------
		   
		     
		ok = trackmate.checkInput()
		if not ok:
		    sys.exit(str(trackmate.getErrorMessage()))
		     
		ok = trackmate.process()
		if not ok:
		    sys.exit(str(trackmate.getErrorMessage()))
		   
		#----------------
		# Display results
		#----------------
		  
		model.getLogger().log('Found ' + str(model.getTrackModel().nTracks(True)) + ' tracks.')
		   
		selectionModel = SelectionModel(model)
		displayer =  HyperStackDisplayer(model, selectionModel, self.imp)

		displayer.render()
		displayer.refresh()
		  
		esta = ExportStatsToIJAction()
		esta.execute(trackmate)
        
		overlay = CaptureOverlayAction()
		overlay.execute(trackmate)

		# Export results
		if os.path.exists(self.analysis_path) == False:
			try:
			    os.mkdir(self.analysis_path)
			except OSError:
			    print ("Creation of the directory %s failed" % self.analysis_path)
			else:
			    print ("Successfully created the directory %s " % self.analysis_path)


		win_names = ["Spots in tracks statistics","Links in tracks statistics","Track statistics"]

		windows = [WindowManager.getWindow(i) for i in win_names]
		 
		for i in windows:
			WindowManager.setWindow(i)
			logging.debug("looking at window {}".format(i.title))
			IJ.saveAs("Results", os.path.join(self.analysis_path,i.title + ".csv"))
			IJ.run("Close", "")


		#Close log 
		WindowManager.setWindow(WindowManager.getWindow("Log"))

		 
		IJ.run("Close", "")

		
		self.export_movie(name=self.case_name+"_Tracking_analysis")


    def open_in_fiji(self):
        #IJ.run("Image Sequence...", "open=" + self.path + " use file=.tif sort")
        #self.imp = IJ.getImage()
        self.imp = FolderOpener.open(self.path, " file=.tif")
        #
        self.case_name = self.imp.getTitle()

        #IJ.getImage().close()
        self.imp.show()
      
        self.nrOfFrames = self.imp.getNSlices()

        logging.debug("Read {} frames".format(self.nrOfFrames))
        

        
    def export_snapshot(self,name,position = None,file_type = 'PNG'):

		if (position is None):
			position = int(0.5*self.nrOfFrames)

		self.imp.setPosition(1,position,1)
		logging.debug("Showing position {}".format(position))
		if os.path.exists(self.analysis_path) == False:
			try:
			    os.mkdir(self.analysis_path)
			except OSError:
			    print ("Creation of the directory %s failed" % self.analysis_path)
			else:
			    print ("Successfully created the directory %s " % self.analysis_path)

	
		IJ.saveAs(self.imp, 'PNG', os.path.join(self.analysis_path,name+'.png'))
		
		self.imp.setTitle(self.case_name)

    def export_movie(self,name, scalebar=True):

        movie_title = os.path.join(self.analysis_path, name + ".avi")
        if os.path.exists(movie_title) == False or self.overwrite_movie:
            if scalebar:
                IJ.run(self.imp, "Scale Bar...",
                       "width=1 color=Black background=None location=[Upper Right] bold overlay")
            IJ.run(self.imp, "AVI... ", "compression=JPEG frame=15 save=" + movie_title)
            
            if scalebar:
                IJ.run("Remove Overlay")

    def set_scalebar(self):

        child_scalebar_path = os.path.join(self.path, 'scalebar.txt')
        parent_scalebar_path = os.path.join(os.path.dirname(self.path), "scalebar.txt")

        if os.path.exists(child_scalebar_path):
            self.scalebar_string = get_scalebar_string(child_scalebar_path)
        elif os.path.exists(parent_scalebar_path):
            self.scalebar_string = get_scalebar_string(parent_scalebar_path)
        else:
            logging.critical("Warning: No scalebar found! Will not set scale")
            self.scalebar_string = None

        if self.scalebar_string is not None:
            logging.debug("\tScalebar set to {}".format(self.scalebar_string))
            IJ.run(self.imp, "Set Scale...", self.scalebar_string)

def get_scalebar_string(path):
    path = os.path.join(path, "")
    case_folder = os.path.join(path, "scalebar.txt")
    parent_folder = os.path.join(os.path.dirname(os.path.dirname(path)), "scalebar.txt")

    if os.path.exists(case_folder):
        scalebar_file = case_folder
    elif os.path.exists(parent_folder):
        scalebar_file = parent_folder
    else:
        scalebar_file = None

    if scalebar_file is not None:
        with open(scalebar_file) as f:
            scalebar_string = f.readline()
    else:
        scalebar_string = None
    return scalebar_string



main_folder = "/home/eidevag/VIPP/experiments/artic_falls_2019/test_data/small_data/"


sub_folders = [x[0] for x in os.walk(main_folder)][1:]



test = []
for root, dirs, files in os.walk(main_folder):
	
	[dirs.remove(d) for d in list(dirs) if d.startswith('.') or d in ["analysis","post",'Lib']]


	logging.debug("Looking into {}".format(root))
	if any('.tif' in x for x in files):
		case = pre_fiji_case(root,overwrite_settings=True)
		case.run_fiji_analysis()


Beautiful script! I see you are doing a lot of massaging and automated export.

The problem I see is that you do not create a GUI in your script. And the capture-overlay action needs it.

It would be impractical in your case to launch a GUI each time, which means I have to think a bit on how to make this action works without a GUI.

Do you care to create an issue on Github? https://github.com/fiji/TrackMate

I will work on it.

1 Like

Thank you for the compliment. Yes, I have quite a lot of data that I want to process so I thought its best to do it automated, I think at least it will work for 80-90% of the cases I have but I then need exports like overlay so I can verify that it works as intended.

I create an issue on GitHub for it now here https://github.com/fiji/TrackMate/issues/142 (I haven’t done it before so I hope I did it correctly)

Best regards
Tobias

1 Like

I also had/have the same issues of saving the overlay.
By now I am using this “trick” saving an xml file that could be open with the “load a trackmate file”
When you open it with “load a trackmate file” it will open for you also the corresponding image.

In this post some more details:

HTH,
emanuele martini

Check this commit:

With this change, you should be able to directly call a static method, like this:

final TrackMate trackmate = ...
final ImagePlus capture = CaptureOverlayAction.capture( trackmate, 15, 25 );
capture.show();

It should make it into a new TrackMate release this Friday, with several bugfixes (on the Kalman tracker, see the forum, and loading and saving the GUI states), plus @imagejan new LUT settings.
That is, if we find the time and energy :slight_smile:

2 Likes

Great, thank you!
I tried with the new commit https://github.com/fiji/TrackMate/releases/tag/TrackMate_-5.1.0
It is working very well in Jython running:

		capture = CaptureOverlayAction.capture(trackmate, -1, nrOfFrames)
		capture.show()

And then just saving to .avi or .png

1 Like

It’s on Fiji now :slight_smile:
Can you check the solution and close the topic please?

1 Like

Yes as I wrote above I checked the solution and it works perfectly :slight_smile:
I don’t know how to close the topic? I saw the github issue is already closed.

Best regards
Tobias

We don’t usually close topics on this forum, because we want to foster open discussion. So if someone else wants to reply on the same topic, they shouldn’t be closed out.

To indicate that the discussed problem is solved, I marked your post above as solution.

Hi @tinevez, Hi everyone,
I’m wondering if there’s a way to export the track movie when scripting ImageJ in headless mode?

Thanks and best regards,
mythracis

Hello @Mythracis_W

Did you test a call to the ExportOverlayAction in headless mode?
I

Thanks for your prompt reply tinevez!
I tried calling CaptureOverlayAction in headless mode
> imagej --ij2 --headless --run track_particle.py 2>&1 | tee test.log
Here’s what I got

File "track_particle.py", line 149, in main
    displayer.render()
	at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)
	at java.awt.Window.<init>(Window.java:536)
	at java.awt.Frame.<init>(Frame.java:420)
	at ij.gui.ImageWindow.<init>(ImageWindow.java:70)
	at ij.gui.StackWindow.<init>(StackWindow.java:28)
	at ij.gui.StackWindow.<init>(StackWindow.java:24)
	at ij.ImagePlus.show(ImagePlus.java:482)
	at ij.ImagePlus.show(ImagePlus.java:458)
	at fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer.render(HyperStackDisplayer.java:176)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
java.awt.HeadlessException: java.awt.HeadlessException

It seems to me that displayer (instance of HyperStackDisplayer) cannot run in headless mode. Meanwhile, the same script (track_particle.py) runs well without --headless option when calling ImageJ.

I also tried removing displayer.render(), but received:

File "track_particle.py", line 151, in main
    track_imp = CaptureOverlayAction.capture(trackmate, -1, imp.getNFrames())
	at fiji.plugin.trackmate.action.CaptureOverlayAction.capture(CaptureOverlayAction.java:143)
	at fiji.plugin.trackmate.action.CaptureOverlayAction.capture(CaptureOverlayAction.java:114)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
java.lang.NullPointerException: java.lang.NullPointerException

I couldn’t find ExportOverlayAction. Is it different from CaptureOverlayAction? Please tell me more about this class? Thank you!

No it was my mistake there isn’t another class.
This means that we cannot export an overlay in headless mode as is.

Thanks all the same! I read there’s a way to export spots to ROI (Export TrackMate spots to the ROI manager in 3D). I should be able to use that to overlay spots and even trajectories in headless.

1 Like