-
Notifications
You must be signed in to change notification settings - Fork 5
/
DotsTrack.m
379 lines (355 loc) · 13.8 KB
/
DotsTrack.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
function p = DotsTrack(fName, p0, varargin)
%DOTSTRACK - track features through an image sequence
%
% usage: p = DotsTrack(fName, p0, ...)
%
% this procedure tracks a set of interactively selected image features (such as blue dots)
% through an image sequence specified by movie FNAME
%
% close the window while paused to return P, an array-of-structs for tracked points with fields
% POS - [X,Y] (pixel coordinates, relative to ULC origin)
% LABEL - point id string
% FRAME - 1-based frame number of fitted image (FRAME and TIME valid only for first point of set)
% TIME - 0-based offset of frame from beginning of movie in seconds
% STATUS - []: untracked; 0: valid; 1: failed tracking step; 2: user flagged invalid pt (tracking skipped)
% CONF - tracking step confidence value
%
% initial P0 [1 x nPoints] as returned by DOTSPLACE or an existing series [nFrames x nPoints]
% as returned by a previous call to DOTSTRACK (in this case only existing frames can be modified)
% by default will resume processing from the last completed frame; specify FRAMES to override
%
% the procedure will pause and display any frame where it has failed to successfully track
% a point; in this frame the problematic location(s) will be displayed as a red "+" symbol
% click within its expected tracking location to update
%
% right click on a point to toggle its status: 'invalid' points are ignored during tracking
%
% optional 'NAME',VALUE parameters:
% FRAMES - constrain tracking to these frame numbers (default all)
% PARAMS - override point tracker parameters (cf. help vision.pointTracker)
% TIMES - constrain tracking to these intevals (secs); [nInts x start,stop]
% XFORM - conditioning function applied to each frame (e.g., @(img) 255-uint8(mean(img,3)))
%
% to extract points as an [nFrames x X,Y x nPoints] array:
% p(cellfun(@isempty,{p(:,1).POS}),:) = []; % delete trailing empty frames
% xy = permute(reshape(cell2mat({p.POS}),[2,size(p,1),size(p,2)]),[2 1 3]);
% figure; pp2(xy); set(gca,'ydir','reverse'); legend({p(1,:).LABEL})
%
% to plot CONF:
% c = reshape(cell2mat({p(:,:).CONF}),size(p,2),size(p,1))'; figure; pp1(c);
%
% requires Computer Vision Toolbox
%
% see also DOTSPLACE, DOTSPLOT
% mkt 01/15
% mkt 10/15 support for names and structured points arrays
% mkt 06/17 allow stepping without tracking (to move through invalid regions)
% mkt 06/18 fix overrun on specified frames
% callback handlers
if nargin < 2, eval('help DotsTrack'); return; end;
if nargin >= 3,
switch varargin{1},
case 'CBPAUSE', % B0 pressed; interrupt active tracking
set(varargin{2},'value',1); % tbh
return;
case 'CBBTNGRP', % button group selection change
set(gcbf,'userData',get(p0.NewValue,'userData'));
uiresume;
return;
case 'CBMENU', % validity popup: change status (0 valid, 1 invalid)
if strcmp(get(gcbo,'checked'),'on'),
set(gcbo,'checked','off'); n = logical(0);
set(get(gcbo,'userdata'),'color','g');
else
set(gcbo,'checked','on'); n = logical(1);
set(get(gcbo,'userdata'),'color','k');
end;
invalid = get(gca,'userdata'); invalid(varargin{2}) = n; set(gca,'userdata',invalid);
return;
otherwise, % fall through
end;
end;
% parse args
frames = [];
times = [];
params = {'MaxBidirectionalError',3};
xform = [];
for ai = 2 : 2 : length(varargin),
switch upper(varargin{ai-1}),
case 'FRAMES', frames = varargin{ai};
case 'TIMES', times = varargin{ai};
case 'PARAMS', params = varargin{ai};
case 'XFORM', xform = varargin{ai};
otherwise, error('unrecognized parameter (%s)', varargin{ai-1});
end;
end;
% open the movie
try, mh = VideoReader(fName); catch, error('unable to open movie file %s', fName); end;
if verLessThan('matlab','8.5.0'),
nFrames = get(mh,'NumberOfFrames');
frameRate = get(mh,'FrameRate');
else,
frameRate = mh.FrameRate;
nFrames = floor(mh.Duration*frameRate);
end;
% map times to frames (ignore if frames also specified)
if ~isempty(times) && isempty(frames),
f = floor(times*frameRate)+1;
for k = 1 : size(f,1), frames = [frames , f(k,1):f(k,2)]; end;
end;
frames(frames > nFrames) = []; % delete out-of-range frames
if size(p0,1) == 1, % point template
if isempty(frames), frames = [1:nFrames]; end;
p(length(frames),size(p0,2)) = p0(1);
p(end,end).POS = []; p(end,end).LABEL = [];
p(1,:) = p0;
for fi = 1 : length(frames),
p(fi,1).FRAME = frames(fi);
p(fi,1).TIME = (frames(fi)-1)/frameRate;
end;
else, % point series: make sure requested frames available
p = p0;
pFrames = cell2mat({p(:,1).FRAME});
if isempty(frames),
frames = pFrames;
else,
unknownFrames = setdiff(frames,pFrames);
if ~isempty(unknownFrames),
unknownFrames
error('FRAMES not found in P0 series');
end;
end;
end;
if length(frames) > length(unique(frames)),
error('FRAMES contains duplicates (must be unique)');
end;
if isempty(frames),
error('no specified FRAMES available in %s', fName);
end;
% get starting frame
if size(p0,1) == 1,
idx = 1;
else,
k = find(cellfun(@isempty,{p.STATUS}),1);
if isempty(k), idx = 1; else idx = k-1; end; % last non-empty frame
end;
% display the first image
img = GetMovieFrame(mh,frames(idx));
if ~isempty(xform), f = xform{1}; a = xform(2:end); img = f(img,a{:}); end;
[fh,ih] = implot(img);
set(fh,'userdata',0,'menubar','none');
% display markers
xy = cell2mat({p(idx,:).POS}');
invalid = cell2mat({p(idx,:).STATUS});
if isempty(invalid),
invalid = logical(zeros(1,size(p,2))); % assume all untracked points valid initially
else,
invalid = logical(bitget(invalid,2)); % bit two defines user-specified invalidity
end;
set(gca,'userdata',invalid); % gca userdata holds copy of invalid map
labels = {p(idx,:).LABEL};
m = uicontextmenu; uimenu(m,'label','one'); uimenu(m,'label','two','callback','disp(gcbo)');
colors = 'cgrkm'; % provisional, good, bad track, invalid, bad+invalid
checked = {'off','on'}; % valid, invalid
for k = 1 : length(labels),
m = uicontextmenu; % menu handle userdata points to line handle for color change in callback
hh(k) = uimenu(m,'label','INVALID','checked',checked{invalid(k)+1},'callback',{@DotsTrack,'CBMENU',k});
if isempty(p(idx,k).STATUS), c = 1; else, c = p(idx,k).STATUS+2; end;
lh(k) = line(xy(k,1),xy(k,2),'color',colors(c),'marker','+','linestyle','none','TAG','DTRK','uicontextmenu',m);
set(hh(k),'userdata',lh(k));
th(k) = text(xy(k,1),xy(k,2),[' ',labels{k}],'color','w','TAG','DTRK','uicontextmenu',m,'interpreter','none');
end
% init controls: gcf userdata maps state
bgh = uibuttongroup(fh,'units','pixels','position',[5 5 131 20]);
uicontrol(bgh,'style','togglebutton','position',[2 2 25 15],'string','<<','userdata',-2);
uicontrol(bgh,'style','pushbutton','position',[27 2 25 15],'string','<','callback','set(gcbf,''userdata'',-1);uiresume');
b0 = uicontrol(bgh,'style','togglebutton','position',[52 2 25 15],'string','O','userdata',0);
uicontrol(bgh,'style','pushbutton','position',[77 2 25 15],'string','>','callback','set(gcbf,''userdata'',1);uiresume');
uicontrol(bgh,'style','togglebutton','position',[102 2 25 15],'string','>>','userdata',2);
set(bgh,'SelectedObject',b0,'SelectionChangeFcn',{@DotsTrack,'CBBTNGRP'},'interruptible','on');
bh = bgh;
bh(2) = uicontrol(fh,'style','edit','position',[5 27 51 18],'horizontalAlignment','right','enable','inactive','string','Frame: ');
bh(3) = uicontrol(fh,'style','edit','position',[54 27 81 18],'horizontalAlignment','left', ...
'string',sprintf(' %04d',frames(1)),'callback','set(gcbf,''userdata'',3);uiresume');
bh(4) = uicontrol(fh,'style','togglebutton','position',[5 47 73 20],'string','PAUSED','value',1,'callback','uiresume');
bh(5) = uicontrol(fh,'style','pushbutton','position',[78 47 58 20],'string','MODIFY','callback','set(gcbf,''userdata'',4);uiresume');
efh = bh(3); % frame field alias
tbh = bh(4); % tracking button alias
% allow b0 to turn off active tracking (broken in earlier versions)
if ~verLessThan('matlab','8.5.0'),
set(b0,'callback',{@DotsTrack,'CBPAUSE',tbh});
end;
% initialize the tracker
pt = vision.PointTracker;
set(pt,params{:});
xy(invalid,:) = [];
initialize(pt,xy,img);
% tracking loop
oIdx = idx; % last valid index
while 1,
if ~ishandle(fh), break; end;
% handle paused
if get(tbh,'value'),
set(tbh,'string','PAUSED');
while 1,
state = get(fh,'userData');
switch state,
case {-2,-1}, % reverse
idx = idx - 1;
if idx < 1, idx = 1; state = 0; end;
case 0, % stop cycling
case {1,2}, % forward
idx = idx + 1;
if idx > length(frames), idx = length(frames); state = 0; end;
case 3, % frame field
f = round(str2num(get(efh,'string')));
[~,idx] = min(abs(frames-f));
case 4, % modify point locations
set(fh,'name',sprintf('ADJUST DOTS IN FRAME %d...',frames(idx)));
set(lh,'visible','off');
set(th,'visible','off');
set(bh,'visible','off');
invalid = get(gca,'userdata');
p(idx,:) = DotsPlace(img,'IH',ih,'P0',p(idx,:));
oIdx = idx;
set(gca,'userdata','invalid');
xy = cell2mat({p(idx,:).POS}');
set(lh,'XData',xy(:,1),'YData',xy(:,2));
for k = 1 : size(xy,1), set(th(k),'position',xy(k,:)); end;
set(bh,'visible','on');
set(th,'visible','on');
set(lh,'visible','on');
set(fh,'name',sprintf('%s FRAME %04d / %04d (%.3f secs)', fName, frames(idx), frames(end), p(idx,1).TIME));
end;
% process new frame
if isempty(idx),
idx = oIdx;
state = 0;
set(efh,'string',sprintf(' %04d',frames(idx)));
else,
img = GetMovieFrame(mh,frames(idx));
if ~isempty(xform), f = xform{1}; a = xform(2:end); img = f(img,a{:}); end;
set(ih,'cdata',img);
set(efh,'string',sprintf(' %04d',frames(idx)));
set(fh,'name',sprintf('%s FRAME %04d / %04d (%.3f secs)', fName, frames(idx), frames(end), p(idx,1).TIME));
if isempty(p(idx,1).POS), % update frame using last available point locations
xy = cell2mat({p(oIdx,:).POS}');
else, % update frame from existing point locations
oIdx = idx;
xy = cell2mat({p(idx,:).POS}');
end;
for k = 1 : size(xy,1),
if isempty(p(idx,k).STATUS), c = 1; else, c = p(idx,k).STATUS+2; end;
set(lh(k),'XData',xy(k,1),'YData',xy(k,2),'color',colors(c));
set(th(k),'position',xy(k,:));
end;
end;
if abs(state) == 2, % rrev or ffwd
drawnow;
else,
state = 0; % wait for event
set(fh,'userdata',0);
set(bgh,'SelectedObject',b0);
if ~isempty(p(idx,1).STATUS),
invalid = logical(bitget(cell2mat({p(idx,:).STATUS}),2));
set(gca,'userdata',invalid);
for k = 1 : length(invalid), set(hh(k),'checked',checked{invalid(k)+1}); end;
end;
uiwait(fh);
if ~ishandle(fh), break; end;
invalid = get(gca,'userdata');
if ~get(tbh,'value'), % restart tracking
if isempty(p(idx,1).POS), % update frame using last available point locations
xy = cell2mat({p(oIdx,:).POS}');
else, % update frame from existing point locations
xy = cell2mat({p(idx,:).POS}');
end;
xy(invalid,:) = [];
release(pt);
initialize(pt,xy,img);
set(tbh,'string','TRACKING...'); % restart tracking
break;
elseif (4 == get(fh,'userdata')), % modify
set(fh,'name',sprintf('ADJUST DOTS IN FRAME %d...',frames(idx)));
set(lh,'visible','off');
set(th,'visible','off');
set(bh,'visible','off');
invalid = get(gca,'userdata');
if isempty(p(idx,1).POS), p(idx,:) = p(oIdx,:); end;
p(idx,:) = DotsPlace(img,'IH',ih,'P0',p(idx,:));
oIdx = idx;
set(gca,'userdata',invalid);
xy = cell2mat({p(idx,:).POS}');
set(lh,'XData',xy(:,1),'YData',xy(:,2));
for k = 1 : size(xy,1), set(th(k),'position',xy(k,:)); end;
set(bh,'visible','on');
set(th,'visible','on');
set(lh,'visible','on');
set(fh,'name',sprintf('%s FRAME %04d / %04d (%.3f secs)', fName, frames(idx), frames(end), p(idx,1).TIME), 'userdata',0);
end;
end;
end;
% tracking active
else,
img = GetMovieFrame(mh,frames(idx));
if ~isempty(xform), f = xform{1}; a = xform(2:end); img = f(img,a{:}); end;
set(ih,'cdata',img);
set(efh,'string',sprintf(' %04d',frames(idx)));
set(fh,'userdata',0,'name',sprintf('%s FRAME %04d / %04d (%.3f secs)', fName, frames(idx), frames(end), p(idx,1).TIME));
[xy,v,conf] = step(pt,img); % step the tracker, returning new points, validity, and confidence
% map tracked points onto full set (if any user-marked as invalid)
if any(invalid),
fxy = cell2mat({p(idx,:).POS}');
if isempty(fxy), fxy = cell2mat({p(oIdx,:).POS}'); end;
fv = ones(1,size(p,2));
fc = zeros(1,size(p,2));
k = find(~invalid);
fxy(k,:) = xy;
fv(k) = v;
fc(k) = conf;
xy = fxy; v = fv; conf = fc;
end;
% update point info
p(idx,1).FRAME = frames(idx);
p(idx,1).TIME = (frames(idx)-1)/frameRate;
for k = 1 : size(p,2),
p(idx,k).STATUS = uint8(~v(k)) + uint8(2*invalid(k));
set(lh(k),'XData',xy(k,1),'YData',xy(k,2),'color',colors(p(idx,k).STATUS+2));
set(th(k),'position',xy(k,:));
p(idx,k).POS = xy(k,:);
p(idx,k).LABEL = labels{k};
p(idx,k).CONF = conf(k);
end;
drawnow;
% fix mistrackings
if any(~v),
set(fh,'name',sprintf('RESET INVALID DOTS IN FRAME %d...',frames(idx)));
set(lh,'visible','off');
set(th,'visible','off');
set(bh,'visible','off');
p(idx,:) = DotsPlace(img,'IH',ih,'P0',p(idx,:));
if ~ishandle(fh), break; end;
for k = 1 : size(p,2), p(idx,k).STATUS = bitand(p(idx,k).STATUS,2); end;
xy = cell2mat({p(idx,:).POS}');
set(lh,'XData',xy(:,1),'YData',xy(:,2));
for k = 1 : size(xy,1), set(th(k),'position',xy(k,:)); end;
set(bh,'visible','on');
set(th,'visible','on');
set(lh,'visible','on');
set(tbh,'value',1); % force pause
oIdx = idx;
continue;
else,
xy(invalid,:) = [];
end;
oIdx = idx;
idx = idx + 1; % step frame index
if idx > length(frames),
idx = length(frames);
set(tbh,'value',1); % force pause
end;
end;
end;
release(pt);
delete(pt);
delete(mh);