diff --git a/src/main/java/bdv/BigDataViewer.java b/src/main/java/bdv/BigDataViewer.java
index 53b12391c..44913bad5 100644
--- a/src/main/java/bdv/BigDataViewer.java
+++ b/src/main/java/bdv/BigDataViewer.java
@@ -62,6 +62,7 @@
 import bdv.tools.InitializeViewerState;
 import bdv.tools.RecordMaxProjectionDialog;
 import bdv.tools.RecordMovieDialog;
+import bdv.tools.RecordPathMovieDialog;
 import bdv.tools.VisibilityAndGroupingDialog;
 import bdv.tools.bookmarks.Bookmarks;
 import bdv.tools.bookmarks.BookmarksEditor;
@@ -110,6 +111,8 @@ public class BigDataViewer
 
 	protected final RecordMovieDialog movieDialog;
 
+	protected final RecordPathMovieDialog pathMovieDialog;
+
 	protected final RecordMaxProjectionDialog movieMaxProjectDialog;
 
 	protected final VisibilityAndGroupingDialog activeSourcesDialog;
@@ -369,6 +372,10 @@ public BigDataViewer(
 		// this is just to get updates of window size:
 		viewer.getDisplay().addOverlayRenderer( movieDialog );
 
+		pathMovieDialog = new RecordPathMovieDialog( viewerFrame, viewer, bookmarks, progressWriter );
+		// this is just to get updates of window size:
+		viewer.getDisplay().addOverlayRenderer( pathMovieDialog );
+
 		movieMaxProjectDialog = new RecordMaxProjectionDialog( viewerFrame, viewer, progressWriter );
 		// this is just to get updates of window size:
 		viewer.getDisplay().addOverlayRenderer( movieMaxProjectDialog );
@@ -446,6 +453,10 @@ public boolean accept( final File f )
 		miMovie.setText( "Record Movie" );
 		menu.add( miMovie );
 
+		final JMenuItem miPathMovie = new JMenuItem( actionMap.get( BigDataViewerActions.RECORD_PATH_MOVIE ) );
+		miPathMovie.setText( "Record Path Movie" );
+		menu.add( miPathMovie );
+
 		final JMenuItem miMaxProjectMovie = new JMenuItem( actionMap.get( BigDataViewerActions.RECORD_MAX_PROJECTION_MOVIE ) );
 		miMaxProjectMovie.setText( "Record Max-Projection Movie" );
 		menu.add( miMaxProjectMovie );
@@ -532,6 +543,11 @@ public ManualTransformationEditor getManualTransformEditor()
 		return manualTransformationEditor;
 	}
 
+	public Bookmarks getBookmarks()
+	{
+		return bookmarks;
+	}
+
 	public boolean tryLoadSettings( final String xmlFilename )
 	{
 		proposedSettingsFile = null;
diff --git a/src/main/java/bdv/BigDataViewerActions.java b/src/main/java/bdv/BigDataViewerActions.java
index 8119212a4..c36f2f4d5 100644
--- a/src/main/java/bdv/BigDataViewerActions.java
+++ b/src/main/java/bdv/BigDataViewerActions.java
@@ -41,6 +41,7 @@
 import bdv.tools.HelpDialog;
 import bdv.tools.RecordMaxProjectionDialog;
 import bdv.tools.RecordMovieDialog;
+import bdv.tools.RecordPathMovieDialog;
 import bdv.tools.ToggleDialogAction;
 import bdv.tools.VisibilityAndGroupingDialog;
 import bdv.tools.bookmarks.BookmarksEditor;
@@ -58,6 +59,7 @@ public class BigDataViewerActions extends Actions
 	public static final String SAVE_SETTINGS = "save settings";
 	public static final String LOAD_SETTINGS = "load settings";
 	public static final String RECORD_MOVIE = "record movie";
+	public static final String RECORD_PATH_MOVIE = "record path movie";
 	public static final String RECORD_MAX_PROJECTION_MOVIE = "record max projection movie";
 	public static final String SET_BOOKMARK = "set bookmark";
 	public static final String GO_TO_BOOKMARK = "go to bookmark";
@@ -70,6 +72,7 @@ public class BigDataViewerActions extends Actions
 	static final String[] RECORD_MAX_PROJECTION_MOVIE_KEYS = new String[] { "F8" };
 	static final String[] CROP_KEYS                        = new String[] { "F9" };
 	static final String[] RECORD_MOVIE_KEYS                = new String[] { "F10" };
+	static final String[] RECORD_PATH_MOVIE_KEYS           = new String[] { "shift F10" };
 	static final String[] SAVE_SETTINGS_KEYS               = new String[] { "F11" };
 	static final String[] LOAD_SETTINGS_KEYS               = new String[] { "F12" };
 	static final String[] GO_TO_BOOKMARK_KEYS              = new String[] { "B" };
@@ -99,6 +102,7 @@ public static void installActionBindings(
 		actions.dialog( bdv.helpDialog );
 		actions.dialog( bdv.cropDialog );
 		actions.dialog( bdv.movieDialog );
+		actions.dialog( bdv.pathMovieDialog );
 		actions.dialog( bdv.movieMaxProjectDialog );
 		actions.bookmarks( bdv.bookmarkEditor );
 		actions.manualTransform( bdv.manualTransformationEditor );
@@ -144,6 +148,11 @@ public void dialog( final RecordMovieDialog recordMovieDialog )
 		toggleDialogAction( recordMovieDialog, RECORD_MOVIE, RECORD_MOVIE_KEYS );
 	}
 
+	public void dialog( final RecordPathMovieDialog recordMovieDialog )
+	{
+		toggleDialogAction( recordMovieDialog, RECORD_PATH_MOVIE, RECORD_PATH_MOVIE_KEYS );
+	}
+
 	public void dialog( final RecordMaxProjectionDialog recordMaxProjectionDialog )
 	{
 		toggleDialogAction( recordMaxProjectionDialog, RECORD_MAX_PROJECTION_MOVIE, RECORD_MAX_PROJECTION_MOVIE_KEYS );
diff --git a/src/main/java/bdv/tools/RecordPathMovieDialog.java b/src/main/java/bdv/tools/RecordPathMovieDialog.java
new file mode 100644
index 000000000..4cb0b19a3
--- /dev/null
+++ b/src/main/java/bdv/tools/RecordPathMovieDialog.java
@@ -0,0 +1,457 @@
+/*
+ * #%L
+ * BigDataViewer core classes with minimal dependencies
+ * %%
+ * Copyright (C) 2012 - 2016 Tobias Pietzsch, Stephan Saalfeld, Stephan Preibisch,
+ * Jean-Yves Tinevez, HongKee Moon, Johannes Schindelin, Curtis Rueden, John Bogovic
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.tools;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.BoxLayout;
+import javax.swing.InputMap;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.WindowConstants;
+
+import bdv.cache.CacheControl;
+import bdv.export.ProgressWriter;
+import bdv.tools.bookmarks.Bookmarks;
+import bdv.util.Affine3DHelpers;
+import bdv.util.Prefs;
+import bdv.viewer.ViewerPanel;
+import bdv.viewer.animate.AbstractTransformAnimator;
+import bdv.viewer.animate.SimilarityTransformAnimator;
+import bdv.viewer.animate.TranslationAnimator;
+import bdv.viewer.overlay.ScaleBarOverlayRenderer;
+import bdv.viewer.render.MultiResolutionRenderer;
+import bdv.viewer.state.ViewerState;
+import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.ui.OverlayRenderer;
+import net.imglib2.ui.PainterThread;
+import net.imglib2.ui.RenderTarget;
+
+public class RecordPathMovieDialog extends JDialog implements OverlayRenderer
+{
+
+	private static final String TRANSLATION = "Translation";
+
+	private static final String ROTATION = "Translation + Rotation";
+
+	private static final String SIMILARITY = "Translation + Rotation + Zoom";
+
+	private static final String CURRENT = "current";
+
+	private static final long serialVersionUID = 1L;
+
+	private final ViewerPanel viewer;
+
+	private final Bookmarks bookmarks;
+
+	private final int maxTimepoint;
+
+	private final ProgressWriter progressWriter;
+
+	private final JTextField pathTextField;
+
+	private final JSpinner spinnerMaxTimepoint;
+
+	private final JSpinner spinnerWidth;
+
+	private final JSpinner spinnerHeight;
+
+	private final JComboBox< String > startMark;
+
+	private final JComboBox< String > endMark;
+
+	private final JComboBox< String > transformType;
+
+	public RecordPathMovieDialog( final Frame owner, final ViewerPanel viewer, final Bookmarks bookmarks, final ProgressWriter progressWriter )
+	{
+		super( owner, "record movie", false );
+		this.viewer = viewer;
+		this.bookmarks = bookmarks;
+		maxTimepoint = viewer.getState().getNumTimepoints() - 1;
+		this.progressWriter = progressWriter;
+
+		final JPanel boxes = new JPanel();
+		getContentPane().add( boxes, BorderLayout.NORTH );
+		boxes.setLayout( new BoxLayout( boxes, BoxLayout.PAGE_AXIS ) );
+
+		final JPanel saveAsPanel = new JPanel();
+		saveAsPanel.setLayout( new BorderLayout( 0, 0 ) );
+		boxes.add( saveAsPanel );
+
+		saveAsPanel.add( new JLabel( "save to" ), BorderLayout.WEST );
+
+		pathTextField = new JTextField( "./record/" );
+		saveAsPanel.add( pathTextField, BorderLayout.CENTER );
+		pathTextField.setColumns( 20 );
+
+		final JButton browseButton = new JButton( "Browse" );
+		saveAsPanel.add( browseButton, BorderLayout.EAST );
+
+		final JPanel bookmarksPanel = new JPanel();
+		bookmarksPanel.setLayout( new GridLayout( 3, 1 ) );
+		boxes.add( bookmarksPanel );
+
+		bookmarksPanel.add( new JLabel( "start position (bookmark):" ) );
+		startMark = new JComboBox<>();
+		startMark.setVisible( true );
+		bookmarksPanel.add( startMark );
+
+		bookmarksPanel.add( new JLabel( "end position (bookmark):" ) );
+		endMark = new JComboBox<>();
+		endMark.setVisible( true );
+		bookmarksPanel.add( endMark );
+
+		bookmarksPanel.add( new JLabel( "Transform type:" ) );
+		transformType = new JComboBox<>();
+		transformType.setVisible( true );
+		transformType.addItem( SIMILARITY );
+		transformType.addItem( ROTATION );
+		transformType.addItem( TRANSLATION );
+		bookmarksPanel.add( transformType );
+
+		final JPanel timepointsPanel = new JPanel();
+		boxes.add( timepointsPanel );
+
+		timepointsPanel.add( new JLabel( "number of frames: " ) );
+
+		spinnerMaxTimepoint = new JSpinner();
+		spinnerMaxTimepoint.setModel( new SpinnerNumberModel( 100, 0, Integer.MAX_VALUE, 1 ) );
+		timepointsPanel.add( spinnerMaxTimepoint );
+
+		final JPanel widthPanel = new JPanel();
+		boxes.add( widthPanel );
+		widthPanel.add( new JLabel( "width" ) );
+		spinnerWidth = new JSpinner();
+		spinnerWidth.setModel( new SpinnerNumberModel( 800, 10, 5000, 1 ) );
+		widthPanel.add( spinnerWidth );
+
+		final JPanel heightPanel = new JPanel();
+		boxes.add( heightPanel );
+		heightPanel.add( new JLabel( "height" ) );
+		spinnerHeight = new JSpinner();
+		spinnerHeight.setModel( new SpinnerNumberModel( 600, 10, 5000, 1 ) );
+		heightPanel.add( spinnerHeight );
+
+		final JPanel buttonsPanel = new JPanel();
+		boxes.add( buttonsPanel );
+		buttonsPanel.setLayout(new BorderLayout(0, 0));
+
+		final JButton recordButton = new JButton( "Record" );
+		buttonsPanel.add( recordButton, BorderLayout.EAST );
+
+		final JFileChooser fileChooser = new JFileChooser();
+		fileChooser.setMultiSelectionEnabled( false );
+		fileChooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY );
+
+		browseButton.addActionListener( new ActionListener()
+		{
+			@Override
+			public void actionPerformed( final ActionEvent e )
+			{
+				fileChooser.setSelectedFile( new File( pathTextField.getText() ) );
+				final int returnVal = fileChooser.showSaveDialog( null );
+				if ( returnVal == JFileChooser.APPROVE_OPTION )
+				{
+					final File file = fileChooser.getSelectedFile();
+					pathTextField.setText( file.getAbsolutePath() );
+				}
+			}
+		} );
+
+		recordButton.addActionListener( new ActionListener()
+		{
+			@Override
+			public void actionPerformed( final ActionEvent e )
+			{
+				final String dirname = pathTextField.getText();
+				final File dir = new File( dirname );
+				if ( !dir.exists() )
+					dir.mkdirs();
+				if ( !dir.exists() || !dir.isDirectory() )
+				{
+					System.err.println( "Invalid export directory " + dirname );
+					return;
+				}
+				final int maxTimepointIndex = ( Integer ) spinnerMaxTimepoint.getValue();
+				final int width = ( Integer ) spinnerWidth.getValue();
+				final int height = ( Integer ) spinnerHeight.getValue();
+				new Thread()
+				{
+					@Override
+					public void run()
+					{
+						try
+						{
+							recordButton.setEnabled( false );
+							recordMovie( width, height, maxTimepointIndex,
+									getTransform( startMark, viewer ),
+									getTransform( endMark, viewer ),
+									dir );
+							recordButton.setEnabled( true );
+						}
+						catch ( final Exception ex )
+						{
+							ex.printStackTrace();
+						}
+					}
+				}.start();
+			}
+		} );
+
+		final ActionMap am = getRootPane().getActionMap();
+		final InputMap im = getRootPane().getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
+		final Object hideKey = new Object();
+		final Action hideAction = new AbstractAction()
+		{
+			@Override
+			public void actionPerformed( final ActionEvent e )
+			{
+				setVisible( false );
+			}
+
+			private static final long serialVersionUID = 1L;
+		};
+		im.put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), hideKey );
+		am.put( hideKey, hideAction );
+
+		pack();
+		setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
+	}
+
+	private AffineTransform3D getTransform( final JComboBox<String> choice, final ViewerPanel viewer )
+	{
+		String key = choice.getItemAt( choice.getSelectedIndex());
+		if( key.equals( CURRENT ))
+		{
+			final double cX = viewer.getDisplay().getWidth() / 2.0;
+			final double cY = viewer.getDisplay().getHeight() / 2.0;
+
+			AffineTransform3D t = new AffineTransform3D();
+			viewer.getState().getViewerTransform( t );
+			t.set( t.get( 0, 3 ) - cX, 0, 3 );
+			t.set( t.get( 1, 3 ) - cY, 1, 3 );
+			return t;
+		}
+		else
+			return bookmarks.get( key );
+	}
+
+	private TranslationAnimator setupTranslationAnimator( final AffineTransform3D start, final AffineTransform3D end, final double cX, final double cY, final int maxTime )
+	{
+		double[] startPt = new double[ 3 ];
+		start.applyInverse( startPt, new double[] { cX, cY, 0 } );
+
+		// store the end point in translation temporarily
+		double[] endPt = new double[ 3 ];
+		end.applyInverse( endPt, new double[] { cX, cY, 0 } );
+
+		double[] t = new double[ 3 ];
+		for ( int i = 0; i < 3; i++ )
+			t[ i ] = ( endPt[ i ] / 2 ) - ( startPt[ i ] / 2 );
+
+		AffineTransform3D si = start.copy().inverse();
+		si.translate( t );
+
+		double[] translation = new double[] { si.inverse().get( 0, 3 ), si.inverse().get( 1, 3 ), si.inverse().get( 2, 3 )
+		};
+
+		return new TranslationAnimator( start, translation, maxTime );
+	}
+
+	@Override
+	public void setVisible( boolean visible )
+	{
+		super.setVisible( visible );
+
+		if( visible )
+		{
+			startMark.removeAllItems();
+			endMark.removeAllItems();
+
+			startMark.addItem( "current" );
+			endMark.addItem( "current" );
+			for( String mark : bookmarks.keySet() )
+			{
+				startMark.addItem( mark );
+				endMark.addItem( mark );
+			}
+
+			spinnerWidth.setValue( viewer.getSize().getWidth() );
+			spinnerHeight.setValue( viewer.getSize().getHeight() );
+		}
+	}
+
+	public void recenter( final AffineTransform3D affine,
+			double ccW, double ccH, double cW, double cH )
+	{
+		affine.set( affine.get( 0, 3 ) - ccW, 0, 3 );
+		affine.set( affine.get( 1, 3 ) - ccH / 2, 1, 3 );
+		affine.scale( ( double ) cW / ccW );
+		affine.set( affine.get( 0, 3 ) + cW , 0, 3 );
+		affine.set( affine.get( 1, 3 ) + cH, 1, 3 );
+	}
+
+	public void recordMovie( final int width, final int height, final int maxTimepointIndex, final AffineTransform3D start, final AffineTransform3D end, final File dir ) throws IOException
+	{
+		System.out.println("Record movie");
+
+		final ViewerState renderState = viewer.getState();
+		String xfmType = transformType.getItemAt( transformType.getSelectedIndex());
+
+		final int canvasW = viewer.getDisplay().getWidth();
+		final int canvasH = viewer.getDisplay().getHeight();
+
+		final double ccX = canvasW / 2.0;
+		final double ccY = canvasH / 2.0;
+
+		final double cX = width / 2.0;
+		final double cY = height / 2.0;
+
+		recenter( start, ccX, ccY, cX, cY );
+		recenter( end, ccX, ccY, cX, cY );
+
+		final AffineTransform3D endCopy;
+		if ( xfmType.equals( ROTATION ) )
+		{
+			endCopy = end.copy();
+			double endscale = 0;
+			endscale += Affine3DHelpers.extractScale( end, 0 );
+			endscale += Affine3DHelpers.extractScale( end, 1 );
+			endscale += Affine3DHelpers.extractScale( end, 2 );
+
+			double startscale = 0;
+			startscale += Affine3DHelpers.extractScale( start, 0 );
+			startscale += Affine3DHelpers.extractScale( start, 1 );
+			startscale += Affine3DHelpers.extractScale( start, 2 );
+
+			// can skip division by three since we end with a ratio of start and
+			// end scales
+			endCopy.scale( startscale / endscale );
+		}
+		else
+			endCopy = end;
+
+		renderState.setViewerTransform( start );
+
+		final ScaleBarOverlayRenderer scalebar = Prefs.showScaleBarInMovie() ? new ScaleBarOverlayRenderer() : null;
+
+		class MyTarget implements RenderTarget
+		{
+			BufferedImage bi;
+
+			@Override
+			public BufferedImage setBufferedImage( final BufferedImage bufferedImage )
+			{
+				bi = bufferedImage;
+				return null;
+			}
+
+			@Override
+			public int getWidth()
+			{
+				return width;
+			}
+
+			@Override
+			public int getHeight()
+			{
+				return height;
+			}
+		}
+
+		final AbstractTransformAnimator animator;
+		switch( xfmType )
+		{
+		case TRANSLATION:
+			animator = setupTranslationAnimator( start, endCopy, cX, cY, maxTimepointIndex );
+			break;
+		default:
+			animator = new SimilarityTransformAnimator( start, endCopy, cX, cY, maxTimepointIndex );
+			break;
+		}
+
+		final MyTarget target = new MyTarget();
+		final MultiResolutionRenderer renderer = new MultiResolutionRenderer( target, new PainterThread( null ), new double[] { 1 }, 0, false, 1, null, false, viewer.getOptionValues().getAccumulateProjectorFactory(), new CacheControl.Dummy() );
+		progressWriter.setProgress( 0 );
+		for ( int timepoint = 0; timepoint <= maxTimepointIndex; ++timepoint )
+		{
+			double t = ( ( double ) timepoint ) / maxTimepointIndex;
+			renderState.setViewerTransform( animator.get( t ) );
+
+			renderer.requestRepaint();
+			renderer.paint( renderState );
+
+			if ( Prefs.showScaleBarInMovie() )
+			{
+				final Graphics2D g2 = target.bi.createGraphics();
+				g2.setClip( 0, 0, width, height );
+				scalebar.setViewerState( renderState );
+				scalebar.paint( g2 );
+			}
+
+			ImageIO.write( target.bi, "png", new File( String.format( "%s/img-%03d.png", dir, timepoint ) ) );
+			progressWriter.setProgress( t );
+		}
+	}
+
+	@Override
+	public void drawOverlays( final Graphics g )
+	{}
+
+	@Override
+	public void setCanvasSize( final int width, final int height )
+	{
+		spinnerWidth.setValue( width );
+		spinnerHeight.setValue( height );
+	}
+}
diff --git a/src/main/java/bdv/tools/bookmarks/Bookmarks.java b/src/main/java/bdv/tools/bookmarks/Bookmarks.java
index 45e197354..d370350e7 100644
--- a/src/main/java/bdv/tools/bookmarks/Bookmarks.java
+++ b/src/main/java/bdv/tools/bookmarks/Bookmarks.java
@@ -31,6 +31,7 @@
 
 import java.util.HashMap;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import mpicbg.spim.data.XmlHelpers;
 import net.imglib2.realtransform.AffineTransform3D;
@@ -87,5 +88,10 @@ public AffineTransform3D get( final String key )
 	{
 		return bookmarks.get( key );
 	}
+
+	public Set< String > keySet()
+	{
+		return bookmarks.keySet();
+	}
 }