Skip to content

Commit 8f354b4

Browse files
ENH: interpolate between time points
To achieve specified framerate and time_dilation in Brain.save_movie()
1 parent 6df02e2 commit 8f354b4

File tree

3 files changed

+78
-61
lines changed

3 files changed

+78
-61
lines changed

examples/save_movie.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,12 @@
2929
data = stc['data']
3030

3131
"""
32-
time points in milliseconds
32+
Calculate sample time points
3333
"""
34-
time = 1e3 * np.linspace(stc['tmin'],
35-
stc['tmin'] + data.shape[1] * stc['tstep'],
36-
data.shape[1])
34+
times = np.arange(data.shape[1]) * stc['tstep'] + stc['tmin']
3735

3836
brain.add_data(data, colormap='hot', vertices=stc['vertices'],
39-
smoothing_steps=10, time=time, time_label='time=%0.2f ms',
37+
smoothing_steps=10, time=times, time_label='%0.3f s',
4038
hemi=hemi)
4139

4240
"""
@@ -45,11 +43,13 @@
4543
brain.scale_data_colormap(fmin=13, fmid=18, fmax=22, transparent=True)
4644

4745
"""
48-
Save movies with different combinations of views
49-
"""
50-
brain.save_movie('example_current.mov')
51-
brain.save_movie('example_single.mov', montage='single')
52-
brain.save_movie('example_h.mov', montage=['lat', 'med'], orientation='h')
53-
brain.save_movie('example_v.mov', montage=[['lat'], ['med']])
46+
Save movies with different combinations of views. Use a large value for
47+
time_dilation because the sample stc only covers 30 ms
48+
"""
49+
brain.save_movie('example_current.mov', time_dilation=30)
50+
brain.save_movie('example_single.mov', time_dilation=30, montage='single')
51+
brain.save_movie('example_h.mov', time_dilation=30, montage=['lat', 'med'],
52+
orientation='h')
53+
brain.save_movie('example_v.mov', time_dilation=30, montage=[['lat'], ['med']])
5454

5555
brain.close()

surfer/tests/test_viz.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -218,22 +218,17 @@ def test_movie():
218218
stc_fname = os.path.join(data_dir, 'meg_source_estimate-lh.stc')
219219
stc = io.read_stc(stc_fname)
220220
data = stc['data']
221-
vertices = stc['vertices']
222-
time = 1e3 * np.linspace(stc['tmin'],
223-
stc['tmin'] + data.shape[1] * stc['tstep'],
224-
data.shape[1])
225-
colormap = 'hot'
226-
time_label = 'time=%0.2f ms'
227-
brain.add_data(data, colormap=colormap, vertices=vertices,
228-
smoothing_steps=10, time=time, time_label=time_label)
221+
time = np.arange(data.shape[1]) * stc['tstep'] + stc['tmin']
222+
brain.add_data(data, colormap='hot', vertices=stc['vertices'],
223+
smoothing_steps=10, time=time, time_label='time=%0.2f ms')
229224
brain.scale_data_colormap(fmin=13, fmid=18, fmax=22, transparent=True)
230225

231226
# save movies with different options
232227
tempdir = mkdtemp()
233228
try:
234229
dst = os.path.join(tempdir, 'test')
235230
brain.save_movie(dst, montage='current')
236-
brain.save_movie(dst, montage='current', tstart=time[1], tstop=time[-1])
231+
brain.save_movie(dst, montage='current', tmin=0.081, tmax=0.102)
237232
brain.save_movie(dst, montage='single')
238233
# brain.save_movie(dst, montage=['lat', 'med'], orientation='v')
239234
# brain.save_movie(dst, montage=[['lat'], ['med']])

surfer/viz.py

Lines changed: 63 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import numpy as np
77
from scipy import stats, ndimage, misc
8+
from scipy.interpolate import interp1d
89
from matplotlib.colors import colorConverter
910

1011
import nibabel as nib
@@ -1542,13 +1543,16 @@ def scale_data_colormap(self, fmin, fmid, fmax, transparent, verbose=None):
15421543
data["transparent"] = transparent
15431544
self._toggle_render(True, views)
15441545

1545-
def set_data_time_index(self, time_idx):
1546+
def set_data_time_index(self, time_idx, interpolation='linear'):
15461547
"""Set the data time index to show
15471548
15481549
Parameters
15491550
----------
1550-
time_idx : int
1551-
time index
1551+
time_idx : int | float
1552+
Time index. Floats will cause samples to be interpolated.
1553+
interpolation : str
1554+
Interpolation method (``scipy.interpolate.interp1d`` parameter,
1555+
default 'linear').
15521556
"""
15531557
if self.n_times is None:
15541558
raise RuntimeError('cannot set time index with no time data')
@@ -1559,7 +1563,14 @@ def set_data_time_index(self, time_idx):
15591563
for hemi in ['lh', 'rh']:
15601564
data = self.data_dict[hemi]
15611565
if data is not None:
1562-
plot_data = data["array"][:, time_idx]
1566+
# interpolation
1567+
if isinstance(time_idx, float):
1568+
times = np.arange(self.n_times)
1569+
ifunc = interp1d(times, data['array'], interpolation, 1)
1570+
plot_data = ifunc(time_idx)
1571+
else:
1572+
plot_data = data["array"][:, time_idx]
1573+
15631574
if data["smooth_mat"] is not None:
15641575
plot_data = data["smooth_mat"] * plot_data
15651576
for surf in data["surfaces"]:
@@ -1568,7 +1579,11 @@ def set_data_time_index(self, time_idx):
15681579

15691580
# Update time label
15701581
if data["time_label"]:
1571-
time = data["time"][time_idx]
1582+
if isinstance(time_idx, float):
1583+
ifunc = interp1d(times, data['time'])
1584+
time = ifunc(time_idx)
1585+
else:
1586+
time = data["time"][time_idx]
15721587
self.update_text(data["time_label"] % time, "time_label")
15731588
self._toggle_render(True, views)
15741589

@@ -1932,7 +1947,8 @@ def save_imageset(self, prefix, views, filetype='png', colorbar='auto',
19321947

19331948
def save_image_sequence(self, time_idx, fname_pattern, use_abs_idx=True,
19341949
row=-1, col=-1, montage='single', orientation='h',
1935-
border_size=15, colorbar='auto'):
1950+
border_size=15, colorbar='auto',
1951+
interpolation='linear'):
19361952
"""Save a temporal image sequence
19371953
19381954
The files saved are named "fname_pattern % (pos)" where "pos" is a
@@ -1966,6 +1982,9 @@ def save_image_sequence(self, time_idx, fname_pattern, use_abs_idx=True,
19661982
For 'auto', the colorbar is shown in the middle view (default).
19671983
For int or list of int, the colorbar is shown in the specified
19681984
views. For ``None``, no colorbar is shown.
1985+
interpolation : str
1986+
Interpolation method (``scipy.interpolate.interp1d`` parameter,
1987+
default 'linear').
19691988
19701989
Returns
19711990
-------
@@ -1976,7 +1995,7 @@ def save_image_sequence(self, time_idx, fname_pattern, use_abs_idx=True,
19761995
images_written = list()
19771996
rel_pos = 0
19781997
for idx in time_idx:
1979-
self.set_data_time_index(idx)
1998+
self.set_data_time_index(idx, interpolation)
19801999
fname = fname_pattern % (idx if use_abs_idx else rel_pos)
19812000
if montage == 'single':
19822001
self.save_single_image(fname, row, col)
@@ -2068,28 +2087,27 @@ def save_montage(self, filename, order=['lat', 'ven', 'med'],
20682087
cb.visible = colorbars_visibility[cb]
20692088
return out
20702089

2071-
def save_movie(self, fname, tstart=None, tstop=None, step=1,
2072-
time_idx=None, montage='current', orientation='h',
2073-
border_size=15, colorbar='auto', framerate=10,
2090+
def save_movie(self, fname, time_dilation=4., tmin=None, tmax=None,
2091+
interpolation='linear', montage='current', orientation='h',
2092+
border_size=15, colorbar='auto', framerate=25,
20742093
codec='mpeg4', row=-1, col=-1):
20752094
"""Save a movie (for data with a time axis)
20762095
20772096
Parameters
20782097
----------
20792098
fname : str
20802099
Path at which to save the movie.
2081-
tstart : None | float
2100+
time_dilation : float
2101+
Factor by which to stretch time (default 4). For example, an epoch
2102+
from -100 to 600 ms lasts 700 ms. With ``time_dilation=4`` this
2103+
would result in a 2.8 s long movie.
2104+
tmin : float
20822105
First time point to include (default: all data).
2083-
tstop : None | float
2084-
Time point at which to stop the movie (exclusive; default: all
2085-
data).
2086-
step : int
2087-
Number of data frames to step forward between movie frames
2088-
(default 1).
2089-
time_idx : None | array
2090-
Index that selects time points form the time axis from which to
2091-
make the movie. If time_idx is specified, neither of tstart, tstop
2092-
or tstep should be specified.
2106+
tmax : float
2107+
Last time point to include (default: all data).
2108+
interpolation : str
2109+
Interpolation method (``scipy.interpolate.interp1d`` parameter,
2110+
default 'linear').
20932111
montage: 'current' | 'single' | list
20942112
Views to include in the images: 'current' (default) uses the
20952113
currently displayed image; 'single' uses a single view, specified
@@ -2105,7 +2123,7 @@ def save_movie(self, fname, tstart=None, tstop=None, step=1,
21052123
For int or list of int, the colorbar is shown in the specified
21062124
views. For ``None``, no colorbar is shown.
21072125
framerate : float
2108-
Framerate of the movie (frames per second).
2126+
Framerate of the movie (frames per second, default 25).
21092127
codec : str
21102128
Codec to use (default 'mpeg4').
21112129
row : int
@@ -2119,36 +2137,40 @@ def save_movie(self, fname, tstart=None, tstop=None, step=1,
21192137
"downlaoded from http://ffmpeg.org/download.html.")
21202138
raise RuntimeError(err)
21212139

2122-
if tstart is not None:
2123-
start = self.index_for_time(tstart, rounding='up')
2124-
else:
2125-
start = 0
2126-
2127-
if tstop is not None:
2128-
stop = self.index_for_time(tstop, rounding='up')
2129-
else:
2130-
stop = self.n_times
2131-
2132-
if all(x is None for x in (tstart, tstop, step)):
2133-
if time_idx is None:
2134-
time_idx = np.arange(self.n_times)
2135-
elif time_idx is None:
2136-
time_idx = np.arange(start, stop, step)
2140+
if tmin is None:
2141+
tmin = self._times[0]
2142+
elif tmin < self._times[0]:
2143+
raise ValueError("tmin=%r is smaller than the first time point "
2144+
"(%r)" % (tmin, self._times[0]))
2145+
2146+
if tmax is None:
2147+
tmax = self._times[-1]
2148+
elif tmax >= self._times[-1]:
2149+
raise ValueError("tmax=%r is greater than the latest time point "
2150+
"(%r)" % (tmax, self._times[-1]))
2151+
2152+
# find indexes at which to create frames
2153+
tstep = 1. / (framerate * time_dilation)
2154+
if (tmax - tmin) % tstep == 0:
2155+
tstop = tmax + tstep / 2.
21372156
else:
2138-
err = ("Both slice parameters (tstart, tstop, step) and "
2139-
"time_idx can not be specified at the same time.")
2140-
raise TypeError(err)
2157+
tstop = tmax
2158+
times = np.arange(tmin, tstop, tstep)
2159+
interp_func = interp1d(self._times, np.arange(self.n_times))
2160+
time_idx = interp_func(times)
21412161

21422162
n_times = len(time_idx)
21432163
if n_times == 0:
21442164
raise ValueError("No time points selected")
21452165

2166+
logger.debug("Save movie for time points/samples\n%s\n%s"
2167+
% (times, time_idx))
21462168
tempdir = mkdtemp()
21472169
frame_pattern = 'frame%%0%id.png' % (np.floor(np.log10(n_times)) + 1)
21482170
fname_pattern = os.path.join(tempdir, frame_pattern)
21492171
self.save_image_sequence(time_idx, fname_pattern, False, row,
21502172
col, montage, orientation, border_size,
2151-
colorbar)
2173+
colorbar, interpolation)
21522174
ffmpeg(fname, fname_pattern, framerate, codec)
21532175

21542176
def animate(self, views, n_steps=180., fname=None, use_cache=False,

0 commit comments

Comments
 (0)