Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
671 lines (619 sloc)
28.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function [FreezingEpisodes] = SignX(InputFiles) | |
%%SignX - A GUI to perform freezing detection. | |
% | |
% Uses the motion output from the tracking GUI and the original movie | |
% to extract freezing episodes and display the results. Parameters can | |
% then be adjusted to give the best results. The same parameters are | |
% usually usable for different animals in the same conditions. | |
% | |
% - Uses a motion threshold to detect freezing episodes | |
% - Allows to merge episodes closer than a defined gap | |
% - Defines a minimum freezing duration | |
% - Allows manual editing/removal of single episodes | |
% (e.g. sometimes to remove grooming detected as freezing) | |
% | |
% Output: a new variable in the .mat tracking file with the start/end | |
% times of each freezing episode | |
% | |
% The current version has not been tested for compatibility and | |
% dependencies; in particular, it might not run under MATLAB versions | |
% older than R2018a. Also, unlike the tracking GUI, it needs the frames | |
% timestamps, that are currently either retrieved from the tracking | |
% file or a file generated by our acquisition system. This will soon be | |
% expanded to accommodate different systems. | |
% | |
% Future implementations/changes: | |
% - Expand the manual | |
% - Switch from function to class for more robustness and ease of | |
% use on successive files | |
% - Make the overall code less dependent on the recording system | |
% - Use the calibration option from the tracking GUI to get an even | |
% more absolute freezing threshold (here it is influenced by the | |
% real pixel size, that depends on the camera distance) | |
% - Take into account potential differences in screen | |
% resolutions/sizes | |
% - Add inputs checking | |
% - Adjust some parameters to get a smooth browsing (some movies | |
% have ridiculous FPS and resolution that are too heavy to handle | |
% like this) | |
% - Add the possibility to compute a "moving" background | |
% | |
% Copyright (C) 2019 Jérémy Signoret-Genest, DefenseCircuitsLab | |
% | |
% This program is free software: you can redistribute it and/or modify | |
% it under the terms of the GNU General Public License as published by | |
% the Free Software Foundation, either version 3 of the License, or | |
% (at your option) any later version. | |
% | |
% This program is distributed in the hope that it will be useful, | |
% but WITHOUT ANY WARRANTY; without even the implied warranty of | |
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
% GNU General Public License for more details. | |
% | |
% You should have received a copy of the GNU General Public License | |
% along with this program. If not, see <https://www.gnu.org/licenses/>. | |
if nargin == 0, | |
[FileName,FilePath] = uigetfile({'*.avi;*.mp4;*.m4v;*.mpg;*.mkv;*.wmv;*.mov'}); % Precise path here for more convenience | |
if isempty(FileName), | |
return | |
end | |
InputFiles = fullfile(FilePath,FileName); | |
else | |
% Check inputs | |
end | |
if ~iscell(InputFiles), | |
InputFiles = {InputFiles}; | |
end | |
for ir = 1 : numel(InputFiles), | |
OnlineReading = true; | |
MergeThreshold = 0.2; | |
FreezingMinTime = 2; | |
ThresholdMM = 1.5; | |
SmoothMM = 3; | |
[PathFile,FileName,~] = fileparts(InputFiles{ir}); | |
if contains(InputFiles{ir},'_IR'), | |
MovieType = 'Thermal'; | |
FileName = FileName(1:end-3); | |
AviName = [PathFile,'\',FileName,'_IR.avi']; | |
else | |
MovieType = 'RGB'; | |
AviName = [PathFile,'\',FileName,'.avi']; | |
end | |
%% Retrieve data | |
disp(['Processing file ' num2str(ir) ' out of ' num2str(numel(InputFiles)) '...']) | |
TrackingLog = [PathFile,'\',FileName,'_Tracking']; | |
Tracking = load(TrackingLog); | |
% numFrames = Tracking.(MovieType).FramesNum; | |
Contour = Tracking.(MovieType).Contour; | |
Center_GF = Tracking.(MovieType).Center; | |
if ~isfield(Tracking.(MovieType),'Times') | |
if strcmpi(MovieType,'RGB'), | |
MM_File = [PathFile '\' FileName '.DVT']; | |
DVTRead = csvread(MM_File); | |
Times = DVTRead(:,2); | |
else | |
error('No time information found.'); | |
end | |
else | |
Times = Tracking.(MovieType).Times; | |
end | |
if strcmpi(MovieType,'Thermal'), | |
Times = (round(Times-Times(1))/10)/100; | |
end | |
Frame0 = VideoReader(AviName); | |
FrameTimes = (1/Frame0.FrameRate : 1/Frame0.FrameRate : Frame0.Duration)-1/Frame0.FrameRate; | |
% if ~OnlineReading, | |
% disp(newline) | |
% disp('Retrieving all frames...') | |
% Frames = cell(numFrames,1); | |
% parfor F = 1 : numFrames-1, | |
% FrameF = Frame0; | |
% FrameF.CurrentTime = FrameTimes(F); | |
% Frames{F} = FrameF.readFrame; | |
% end | |
% end | |
% Get freezing events | |
MotionMeasure = Tracking.(MovieType).MotionMeasure; | |
MotionMeasure(isnan(MotionMeasure)) = 0; | |
Contour(isinf(MotionMeasure)) = {[NaN;NaN]}; | |
Contour(cellfun('isempty', Contour)) = {[NaN;NaN]}; | |
MotionMeasure(isinf(MotionMeasure)) = 100; | |
LoadedFE = false; | |
if isfield(Tracking.(MovieType),'Freezing'), | |
if isfield(Tracking.(MovieType).Freezing,'Ranges'), | |
if ~isempty(Tracking.(MovieType).Freezing.Ranges), | |
FreezingEpisodes = Tracking.(MovieType).Freezing.Ranges; | |
ThresholdMM = Tracking.(MovieType).Freezing.MotionMeasureThreshold; | |
MergeThreshold = Tracking.(MovieType).Freezing.MergingThreshold; | |
FreezingMinTime = Tracking.(MovieType).Freezing.Duration; | |
SmoothMM = Tracking.(MovieType).Freezing.Smoothing; | |
LoadedFE = true; | |
end | |
end | |
end | |
if ~LoadedFE | |
FindIndex = find(Smooth(MotionMeasure,SmoothMM)<ThresholdMM); | |
FreezingEpisodes = FindContinuousRange(FindIndex); | |
FreezingEpisodes = FindIndex(FreezingEpisodes(:,[1 2])); | |
FreezingEpisodes = Times(FreezingEpisodes); | |
% Merging | |
for KF = 2 : numel(FreezingEpisodes(:,1)) | |
if (FreezingEpisodes(KF,1)-FreezingEpisodes(KF-1,2))<MergeThreshold, | |
FreezingEpisodes(KF-1,2) = FreezingEpisodes(KF,2); | |
FreezingEpisodes(KF,1) = FreezingEpisodes(KF-1,1); | |
end | |
end | |
FreezingEpisodesLength = (FreezingEpisodes(:,2)-FreezingEpisodes(:,1)); | |
FreezingEpisodes(FreezingEpisodesLength<=FreezingMinTime,:) = []; | |
[~,FreezingEpisodesIndex] = unique(FreezingEpisodes(:,1)); | |
FreezingEpisodes = FreezingEpisodes(FreezingEpisodesIndex,:); | |
end | |
OriginalEpisodes = FreezingEpisodes; | |
%% Prepare figure and UI | |
Scrsz = get(0,'ScreenSize'); | |
Fig = figure('Position',[Scrsz(3)/10 50 4/5*Scrsz(3) Scrsz(4)-150]); | |
SubMovie = subplot('Position',[0.075 0.4290 0.4167 0.5161]); | |
SubMM = subplot('Position',[0.075 2*(1-0.9451) 1-0.0449-0.075 1-4*(1-0.9451)-0.5161]); | |
SubFreezing = subplot('Position',[SubMM.Position(1) SubMM.Position(2)+SubMM.Position(4) SubMM.Position(3) 0.5*(1-0.9451)]); | |
MouseID = uicontrol('Style','text','String',FileName,'FontSize',14,'FontName','Arial','FontWeight','bold',... | |
'Units','Normalized','Position',[0.075 0.95 0.2 0.025],'HorizontalAlignment','left'); | |
LockedZoom = uicontrol('Style','checkbox','String',' Lock Y-Axis','Value',true,'FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@YZoom,'Units','Normalized','Position',[0.8 0.6 0.15 0.025]); | |
CancelAdjustment = uicontrol('Style','pushbutton','String','Cancel last adjustment','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@RevertLastAdjustment,'Units','Normalized','Position',[0.55 0.525 0.175 0.05]); | |
CancelDeletion = uicontrol('Style','pushbutton','String','Cancel last deletion','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@CancelLastDeletion,'Units','Normalized','Position',[0.55 0.585 0.175 0.05]); | |
Delete = uicontrol('Style','pushbutton','String','Delete selected episode','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@DeleteEpisode,'Units','Normalized','Position',[0.55 0.645 0.175 0.05]); | |
PlayButton = uicontrol('Style','pushbutton','String','Play','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@SetPlay,'Units','Normalized','Position',[0.62 0.45 0.07 0.05]); | |
PauseButton = uicontrol('Style','pushbutton','String','Pause','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@PauseMovie,'Units','Normalized','Position',[0.69 0.45 0.07 0.05]); | |
DecreaseRateButton = uicontrol('Style','pushbutton','String','Slower','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@DecreaseFrameRate,'Units','Normalized','Position',[0.55 0.45 0.07 0.05]); | |
IncreaseRateButton = uicontrol('Style','pushbutton','String','Faster','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@IncreaseFrameRate,'Units','Normalized','Position',[0.76 0.45 0.07 0.05]); | |
ThresholdEdit = uicontrol('Style','edit','String',num2str(ThresholdMM),'FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@EditThreshold,'Units','Normalized','Position',[0.55 0.80 0.05 0.04]); | |
ThresholdLegend = uicontrol('Style','text','String','Freezing threshold','FontSize',14,'FontName','Arial','FontWeight','bold',... | |
'Units','Normalized','Position',[0.55 0.85 0.15 0.03],'HorizontalAlignment','left'); | |
ThresholdDrag = uicontrol('Style','pushbutton','String','Drag','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@DragThreshold,'Units','Normalized','Position',[0.55 0.75 0.05 0.04],'HorizontalAlignment','left'); | |
ThresholdEditSet = uicontrol('Style','pushbutton','String','Set','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@EditThresholdSet,'Units','Normalized','Position',[0.6005 0.80 0.05 0.04],'HorizontalAlignment','left'); | |
MergeThresholdEdit = uicontrol('Style','edit','String',num2str(MergeThreshold),'FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@MergeEditThreshold,'Units','Normalized','Position',[0.7 0.80 0.05 0.04]); | |
MergeThresholdLegend = uicontrol('Style','text','String','Merging threshold (s)','FontSize',14,'FontName','Arial','FontWeight','bold',... | |
'Units','Normalized','Position',[0.7 0.85 0.15 0.03],'HorizontalAlignment','left'); | |
MergeThresholdEditSet = uicontrol('Style','pushbutton','String','Set','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@MergeEditThresholdSet,'Units','Normalized','Position',[0.7505 0.80 0.05 0.04],'HorizontalAlignment','left'); | |
MinEpisodeLegend = uicontrol('Style','text','String','Minimum (s)','FontSize',14,'FontName','Arial','FontWeight','bold',... | |
'Units','Normalized','Position',[0.85 0.85 0.15 0.03],'HorizontalAlignment','left'); | |
MinEpisodeEdit = uicontrol('Style','edit','String',num2str(FreezingMinTime),'FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@EditMinEpisode,'Units','Normalized','Position',[0.85 0.80 0.05 0.04]); | |
MinEpisodeEditSet = uicontrol('Style','pushbutton','String','Set','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@EditMinEpisodeSet,'Units','Normalized','Position',[0.9005 0.80 0.05 0.04],'HorizontalAlignment','left'); | |
ValidateButton = uicontrol('Style','pushbutton','String','Validate','FontSize',16,'FontName','Arial','FontWeight','bold',... | |
'Callback',@Exit,'Units','Normalized','Position',[0.8850 0.01 0.0700 0.0500],'HorizontalAlignment','left'); | |
%% Initialize | |
% if ~OnlineReading, | |
% ImFrame = image(Frames{1},'Parent',SubMovie); | |
% else | |
if strcmpi(MovieType,'Thermal'), | |
ImFrame = imagesc(rgb2gray(Frame0.readFrame),'Parent',SubMovie); | |
HW = load('HotWhite'); | |
SubMovie.Colormap = HW.HotWhite; | |
SubMovie.CLim = [25 175]; | |
else | |
ImFrame = image(Frame0.readFrame,'Parent',SubMovie); | |
end | |
% end | |
hold(SubMovie,'on') | |
drawnow | |
ContourPlot = plot(Contour{1}(1,:),Contour{1}(2,:),'k','LineWidth',2.5,'Parent',SubMovie); | |
uistack(ContourPlot,'top') | |
CenterPlot = plot(Center_GF(1,1),Center_GF(1,2),'+k','LineWidth',2.5,'MarkerSize',5,'Parent',SubMovie); | |
uistack(CenterPlot,'top') | |
SpeedText = text(0.9*Frame0.Width,0.95*Frame0.Height,'x1','FontSize',22,'FontName','Arial','FontWeight','bold','Color','k','Parent',SubMovie); | |
uistack(SpeedText,'top') | |
if OnlineReading, | |
TimeText = text(0.05*Frame0.Width,0.95*Frame0.Height,'0','FontSize',22,'FontName','Arial','FontWeight','bold','Color','k','Parent',SubMovie); | |
end | |
uistack(TimeText,'top') | |
hold(SubMovie,'off') | |
SubMovie.XColor = 'none'; | |
SubMovie.YColor = 'none'; | |
plot(Times(2:end),Smooth(MotionMeasure,SmoothMM),'LineWidth',1.5,'Parent',SubMM); | |
hold(SubMM,'on') | |
drawnow | |
YlimMM = SubMM.YLim; | |
% Prepare a hidden plot for the threshold | |
ThresholdLine = plot(Times([2 end]),[1 1],'Color','none','LineWidth',1.5,'Parent',SubMM); | |
PimpAxis(SubMM,'xlabel','Time(s)','ylabel','Motion Measure') | |
SubMM.XLim = [-5 Times(end)]; | |
LineCurrentTime = plot([0 0],[0 500],'LineWidth',2.5,'Parent',SubMM,'ButtonDownFcn',@TimeLine); | |
SubMM.YLim = YlimMM; | |
linkaxes([SubMM SubFreezing],'x') | |
hold(SubFreezing,'on'); | |
FreezingHandles = arrayfun(@(x) plot([FreezingEpisodes(x,1) FreezingEpisodes(x,2)],[0 0],'k','LineWidth',3,'Parent',SubFreezing,'Tag',num2str(x),'ButtonDownFcn',@FreezingEdit),1:numel(FreezingEpisodes(:,1))); | |
SubFreezing.XColor = 'none'; | |
SubFreezing.YColor = 'none'; | |
SubFreezing.YLim = [-0.5 0.5]; | |
drawnow | |
ZoomHandleSubFreezing = zoom(SubFreezing); | |
ZoomHandleSubFreezing.Motion = 'horizontal'; | |
ZoomHandleSubFreezing.ActionPostCallback = @EvaluateWindow; | |
ZoomHandleSubMM = zoom(SubMM); | |
ZoomHandleSubMM.Motion = 'horizontal'; | |
ZoomHandleSubMM.ActionPostCallback = @EvaluateWindow; | |
Edition = false; | |
DeletionIndex = []; | |
SelectedEpisode = []; | |
LeftLimit = []; | |
RightLimit = []; | |
Dragging = false; | |
AdjustedRanges = []; | |
CurrentTime = 0; | |
Play = false; | |
PrePlayState = []; | |
FrameRate = Frame0.FrameRate; | |
ShiftTimeLine = 0.2; | |
drawnow | |
ReProcessFreezing; | |
waitfor(Fig); | |
end | |
function YZoom(src,~) | |
if src.Value==0, | |
ZoomHandleSubMM.Motion = 'both'; | |
else | |
ZoomHandleSubMM.Motion = 'horizontal'; | |
end | |
end | |
function IncreaseFrameRate(~,~) | |
FrameRate = FrameRate*2; | |
SpeedText.String = ['x' num2str(round(FrameRate/Frame0.FrameRate*10)/10)]; | |
uistack(SpeedText,'top') | |
drawnow | |
end | |
function SetPlay(~,~) | |
Play = true; | |
PlayMovie; | |
end | |
function DecreaseFrameRate(~,~) | |
FrameRate = FrameRate/2; | |
SpeedText.String = ['x' num2str(round(FrameRate/Frame0.FrameRate*10)/10)]; | |
uistack(SpeedText,'top') | |
drawnow | |
end | |
function FreezingEdit(src,~) | |
% Check if an episode is being edited | |
if ~Edition | |
% Make sure the playing has stopped | |
PrePlayState = Play; | |
Play = false; | |
% Get episode index | |
SelectedEpisode = str2double(src.Tag); | |
% Change line appearence | |
src.LineWidth = 2.5; | |
src.Color = [0.85,0.33,0.10]; | |
% Change callback | |
src.ButtonDownFcn = @FreezingEndEdit; | |
% Draw bars on limits | |
LeftLimit = plot([FreezingEpisodes(SelectedEpisode,1) FreezingEpisodes(SelectedEpisode,1)],[-0.25 0.25],'k','LineWidth',2,'ButtonDownFcn',@DragLimitLeft); | |
RightLimit = plot([FreezingEpisodes(SelectedEpisode,2) FreezingEpisodes(SelectedEpisode,2)],[-0.25 0.25],'k','LineWidth',2,'ButtonDownFcn',@DragLimitRight); | |
% Set status | |
Edition = true; | |
end | |
end | |
function DragLimitLeft(~,~) | |
if ~Dragging, | |
Dragging = true; | |
Fig.WindowButtonMotionFcn = @MovingCursorLeft; | |
Fig.WindowButtonUpFcn = @DragLimitLeft; | |
else | |
Dragging = false; | |
Fig.WindowButtonMotionFcn = []; | |
Fig.WindowButtonUpFcn = []; | |
AdjustedRanges = [AdjustedRanges;SelectedEpisode]; | |
FreezingEpisodes(SelectedEpisode,1) = LeftLimit.XData(1); | |
end | |
end | |
function MovingCursorLeft(~,~) | |
CurrentCursor = SubFreezing.CurrentPoint; | |
if CurrentCursor(1)<FreezingEpisodes(SelectedEpisode,2), | |
LeftLimit.XData = [CurrentCursor(1) CurrentCursor(1)]; | |
FreezingHandles(SelectedEpisode).XData(1) = CurrentCursor(1); | |
end | |
end | |
function DragLimitRight(~,~) | |
if ~Dragging, | |
Dragging = true; | |
Fig.WindowButtonMotionFcn = @MovingCursorRight; | |
Fig.WindowButtonUpFcn = @DragLimitRight; | |
else | |
Dragging = false; | |
Fig.WindowButtonMotionFcn = []; | |
Fig.WindowButtonUpFcn = []; | |
FreezingEpisodes(SelectedEpisode,2) = RightLimit.XData(1); | |
end | |
end | |
function MovingCursorRight(~,~) | |
CurrentCursor = SubFreezing.CurrentPoint; | |
if CurrentCursor(1)>FreezingEpisodes(SelectedEpisode,1), | |
RightLimit.XData = [CurrentCursor(1) CurrentCursor(1)]; | |
FreezingHandles(SelectedEpisode).XData(2) = CurrentCursor(1); | |
end | |
end | |
function RevertLastAdjustment(~,~) | |
if ~isempty(AdjustedRanges), | |
FreezingEpisodes(AdjustedRanges(end),:) = OriginalEpisodes(AdjustedRanges(end),:); | |
FreezingHandles(AdjustedRanges(end)).XData = OriginalEpisodes(AdjustedRanges(end),:); | |
AdjustedRanges(end) = []; | |
end | |
end | |
function FreezingEndEdit(src,~) | |
% Revert appearence | |
src.LineWidth = 3; | |
src.Color = 'k'; | |
% Set status | |
Edition = false; | |
AdjustedRanges = [AdjustedRanges;SelectedEpisode]; | |
FreezingEpisodes(SelectedEpisode,1) = LeftLimit.XData(1); | |
FreezingEpisodes(SelectedEpisode,2) = RightLimit.XData(1); | |
SelectedEpisode = []; | |
% Remove bars on limits | |
delete(LeftLimit) | |
delete(RightLimit) | |
% Change callback | |
src.ButtonDownFcn = @FreezingEdit; | |
% Restore playing state | |
Play = PrePlayState; | |
end | |
function DeleteEpisode(~,~) | |
if ~isempty(SelectedEpisode), | |
% Remove bars on limits | |
delete(LeftLimit) | |
delete(RightLimit) | |
FreezingHandles(SelectedEpisode).LineWidth = 3; | |
FreezingHandles(SelectedEpisode).ButtonDownFcn = []; | |
FreezingHandles(SelectedEpisode).Color = 'none'; | |
DeletionIndex = [DeletionIndex,SelectedEpisode]; | |
% Set status | |
Edition = false; | |
SelectedEpisode = []; | |
% Restore playing state | |
Play = PrePlayState; | |
end | |
end | |
function CancelLastDeletion(~,~) | |
if ~isempty(DeletionIndex), | |
FreezingHandles(DeletionIndex(end)).Color = 'k'; | |
FreezingHandles(DeletionIndex(end)).ButtonDownFcn = @FreezingEdit; | |
DeletionIndex(end) = []; | |
end | |
end | |
function TimeLine(~,~) | |
if ~Dragging, | |
PrePlayState = Play; | |
Play = false; | |
Dragging = true; | |
Fig.WindowButtonMotionFcn = @MovingTimeLine; | |
Fig.WindowButtonUpFcn = @TimeLine; | |
else | |
Dragging = false; | |
Fig.WindowButtonMotionFcn = []; | |
Fig.WindowButtonUpFcn = []; | |
Play = PrePlayState; | |
PlayMovie; | |
end | |
end | |
function MovingTimeLine(~,~) | |
CurrentCursor = SubMM.CurrentPoint; | |
if CurrentCursor(1)>=0 && CurrentCursor(1)<=Times(end), | |
if CurrentCursor(1) >= SubMM.XLim(2), | |
NewY = SubMM.XLim(2) + 0.025*diff(SubMM.XLim); | |
if NewY<=Times(end), | |
SubMM.XLim = SubMM.XLim + 0.025*diff(SubMM.XLim); | |
else | |
SubMM.XLim = SubMM.XLim+(Times(end)-SubMM.XLim(2)); | |
end | |
CurrentTime = SubMM.XLim(2); | |
elseif CurrentCursor(1) <= SubMM.XLim(1), | |
NewY = SubMM.XLim(1) - 0.025*diff(SubMM.XLim); | |
if NewY<=Times(end), | |
SubMM.XLim = SubMM.XLim - 0.025*diff(SubMM.XLim); | |
else | |
SubMM.XLim = SubMM.XLim - (SubMM.XLim(1)); | |
end | |
CurrentTime = SubMM.XLim(1); | |
else | |
CurrentTime = CurrentCursor(1); | |
end | |
end | |
LineCurrentTime.XData = [CurrentTime CurrentTime]; | |
FrameUpdate; | |
end | |
function FrameUpdate(~,~) | |
NewIndex = FindInInterval(Times,[CurrentTime CurrentTime]); | |
% if ~OnlineReading, | |
% ImFrame.CData = Frames{NewIndex(1)}; | |
% else | |
ContourPlot.XData = Contour{NewIndex(1)}(1,:); | |
ContourPlot.YData = Contour{NewIndex(1)}(2,:); | |
CenterPlot.XData = Center_GF(NewIndex(1),1); | |
CenterPlot.YData = Center_GF(NewIndex(1),2); | |
Frame0.CurrentTime = FrameTimes(NewIndex(1)); | |
if strcmpi(MovieType,'Thermal'), | |
ImFrame.CData = rgb2gray(Frame0.readFrame); | |
else | |
ImFrame.CData = (Frame0.readFrame); | |
end | |
TimeText.String = num2str(Times(NewIndex(1))); | |
% end | |
uistack(SpeedText,'top') | |
drawnow | |
end | |
function EvaluateWindow(~,~) | |
PrePlayState = Play; | |
Play = false; | |
if SubMM.XLim(1)<0, | |
SubMM.XLim(1) = 0; | |
elseif SubMM.XLim(2)>Times(end), | |
SubMM.XLim(2) = Times(end); | |
end | |
if CurrentTime<SubMM.XLim(1) || CurrentTime>SubMM.XLim(2), | |
CurrentTime = SubMM.XLim(1) + 0.5*diff(SubMM.XLim); | |
LineCurrentTime.XData = [CurrentTime CurrentTime]; | |
FrameUpdate; | |
end | |
Play = PrePlayState; | |
PlayMovie; | |
end | |
function PlayMovie(~,~) | |
if Play | |
% Only approximate framerate (for faster plotting) | |
CurrentTime = CurrentTime+1/FrameRate; | |
CurrentIndex = FindInInterval(Times,[CurrentTime CurrentTime]); | |
% Plot once to get a starting value for the delay | |
tic | |
% if ~OnlineReading, | |
% ImFrame.CData = Frames{CurrentIndex(1)}; | |
% else | |
CurrentIndex = FindInInterval(Times,[CurrentTime CurrentTime]); | |
ContourPlot.XData = Contour{CurrentIndex(1)}(1,:); | |
ContourPlot.YData = Contour{CurrentIndex(1)}(2,:); | |
CenterPlot.XData = Center_GF(CurrentIndex(1),1); | |
CenterPlot.YData = Center_GF(CurrentIndex(1),2); | |
Frame0.CurrentTime = FrameTimes(CurrentIndex(1)); | |
if strcmpi(MovieType,'Thermal'), | |
ImFrame.CData = rgb2gray(Frame0.readFrame); | |
else | |
ImFrame.CData = (Frame0.readFrame); | |
end | |
TimeText.String = num2str(Times(CurrentIndex(1))); | |
% end | |
uistack(SpeedText,'top') | |
LineCurrentTime.XData = [Times(CurrentIndex(1)) Times(CurrentIndex(1))]; | |
drawnow | |
end | |
while Play | |
TocT = toc; | |
EstimatedShift = TocT*FrameRate; | |
if EstimatedShift>1 | |
tic; | |
CurrentIndex = CurrentIndex + round(EstimatedShift); | |
CurrentTime = Times(CurrentIndex(1)); | |
% if ~OnlineReading, | |
% ImFrame.CData = Frames{CurrentIndex(1)}; | |
% else | |
ContourPlot.XData = Contour{CurrentIndex(1)}(1,:); | |
ContourPlot.YData = Contour{CurrentIndex(1)}(2,:); | |
CenterPlot.XData = Center_GF(CurrentIndex(1),1); | |
CenterPlot.YData = Center_GF(CurrentIndex(1),2); | |
Frame0.CurrentTime = FrameTimes(CurrentIndex(1)); | |
if strcmpi(MovieType,'Thermal'), | |
ImFrame.CData = rgb2gray(Frame0.readFrame); | |
else | |
ImFrame.CData = (Frame0.readFrame); | |
end | |
TimeText.String = num2str(Times(CurrentIndex(1))); | |
% end | |
uistack(SpeedText,'top') | |
LineCurrentTime.XData = [CurrentTime CurrentTime]; | |
if CurrentTime>=(SubMM.XLim(2)-ShiftTimeLine*diff(SubMM.XLim)), | |
SubMM.XLim = SubMM.XLim + CurrentTime-SubMM.XLim(2)+ShiftTimeLine*diff(SubMM.XLim); | |
end | |
drawnow | |
end | |
end | |
end | |
function PauseMovie(~,~) | |
Play = false; | |
end | |
function EditThreshold(src,~) | |
end | |
function EditThresholdSet(~,~) | |
if str2double(ThresholdEdit.String)>0, | |
ThresholdMM = str2double(ThresholdEdit.String); | |
ThresholdLine.Color = 'none'; | |
ThresholdLine.ButtonDownFcn = []; | |
ReProcessFreezing; | |
end | |
end | |
function DragThreshold(~,~) | |
ThresholdLine.Color = [0.85,0.33,0.10]; | |
ThresholdLine.ButtonDownFcn = @StartDragThreshold; | |
uistack(ThresholdLine,'top') | |
Play = false; | |
Dragging = false; | |
end | |
function StartDragThreshold(~,~) | |
if ~Dragging, | |
Fig.WindowButtonMotionFcn = @MovingThresholdLine; | |
Fig.WindowButtonUpFcn = @StartDragThreshold; | |
Dragging = true; | |
else | |
Dragging = false; | |
Fig.WindowButtonMotionFcn = []; | |
Fig.WindowButtonUpFcn = []; | |
end | |
end | |
function MovingThresholdLine(~,~) | |
CurrentCursor = SubMM.CurrentPoint; | |
if CurrentCursor(1,2)>=0 && CurrentCursor(1,2)<=SubMM.YLim(2), | |
ThresholdLine.YData = [CurrentCursor(1,2) CurrentCursor(1,2)]; | |
ThresholdEdit.String = num2str(CurrentCursor(1,2)); | |
drawnow; | |
end | |
end | |
function MergeEditThreshold(~,~) | |
end | |
function MergeEditThresholdSet(~,~) | |
if str2double(MergeThresholdEdit.String)>=0, | |
MergeThreshold = str2double(MergeThresholdEdit.String); | |
ReProcessFreezing; | |
end | |
end | |
function EditMinEpisode(~,~) | |
end | |
function EditMinEpisodeSet(~,~) | |
if str2double(MinEpisodeEdit.String)>0, | |
FreezingMinTime = str2double(MinEpisodeEdit.String); | |
ReProcessFreezing; | |
end | |
end | |
function ReProcessFreezing(~,~) | |
FindIndex = find(Smooth(MotionMeasure,SmoothMM)<ThresholdMM); | |
FreezingEpisodes = FindContinuousRange(FindIndex); | |
FreezingEpisodes = FindIndex(FreezingEpisodes(:,[1 2])); | |
FreezingEpisodes = Times(FreezingEpisodes); | |
% Merging | |
for KFS = 2 : numel(FreezingEpisodes(:,1)) | |
if (FreezingEpisodes(KFS,1)-FreezingEpisodes(KFS-1,2))<MergeThreshold, | |
FreezingEpisodes(KFS,1) = FreezingEpisodes(KFS-1,1); | |
FreezingEpisodes(KFS-1,:) = NaN(1,2); | |
end | |
end | |
FreezingEpisodes = FreezingEpisodes(~isnan(FreezingEpisodes(:,1)),:); | |
FreezingEpisodesLength = (FreezingEpisodes(:,2)-FreezingEpisodes(:,1)); | |
FreezingEpisodes(FreezingEpisodesLength<=FreezingMinTime,:) = []; | |
OriginalEpisodes = FreezingEpisodes; | |
delete(FreezingHandles(:)); | |
FreezingHandles = arrayfun(@(x) plot([FreezingEpisodes(x,1) FreezingEpisodes(x,2)],[0 0],'k','LineWidth',3,'Parent',SubFreezing,'Tag',num2str(x),'ButtonDownFcn',@FreezingEdit),1:numel(FreezingEpisodes(:,1))); | |
SubFreezing.XColor = 'none'; | |
SubFreezing.YColor = 'none'; | |
SubFreezing.YLim = [-0.5 0.5]; | |
end | |
function Exit(~,~) | |
Play = false; | |
FreezingEpisodes(DeletionIndex,:) = []; | |
% Add to logfile | |
Tracking.(MovieType).Freezing.MotionMeasureThreshold = ThresholdMM; | |
Tracking.(MovieType).Freezing.MergingThreshold = MergeThreshold; | |
Tracking.(MovieType).Freezing.Duration = FreezingMinTime; | |
Tracking.(MovieType).Freezing.Smoothing = SmoothMM; | |
Tracking.(MovieType).Freezing.Ranges = FreezingEpisodes; | |
save(TrackingLog,'-struct','Tracking') | |
close(Fig) | |
end | |
end |