https://github.com/smitdaniel/BFPtool
Tip revision: cf5f3012e9395846ebddafa7ffe193482cb3fd3f authored by Daniel Smit on 07 March 2017, 10:25:35 UTC
Added PDF of the associated BMC article.
Added PDF of the associated BMC article.
Tip revision: cf5f301
BFPGUI.m
%% BFPGUI: Function running the BFPClass GUI
% Manages the analysis of a BFP experiment recording. Takes inputs from
% the user and calls appropriate computational methods to track and
% calculate forces. It performs many maintainance tasks (e.g. basic
% fitting, measurements, fine-tuning of the detection), and import-export
% tools.
% *OUT:*
% hfig: handle to the main GUI figure, returned
% backdoorObj: is a handle superclass object, which is connected to some of
% the important parameters within the BFPClass and BFPGUI. It allows user
% to change these parameters from the Matlab command line. It is usually
% not necessary to access these paremeters. (not passed bus assigned to
% the base workspace)
% *IN:*
% loadData (Optional): allows user to start GUI instance with data
% imported from an older MAT file form earlier session or another machine
% ======================================================================
function [ hfig ] = BFPGUI( varargin )
%% Add help path into Matlab path
% if already present, remove and add the path again to have it parsed
[apppath,~,~] = fileparts(mfilename('fullpath')); % path to the current M-file (BFPGUI.m)
apphtmls = numel(strfind(apppath,'html')); % find possible html strings in app path (would usually be 0)
allpath=genpath(apppath); % get all the subfolders below BFPGUI folder (avoid direct path manipulation because of platform problems)
while(allpath)
[helpstr,allpath]= strtok(allpath,':'); % tokenize all subfolders
fphtml = strfind(helpstr,'html'); % find positions of htmls in the full path
fphtmls = numel(fphtml); % number of htmls in the full path
if fphtmls-apphtmls > 0; % a html exist after app path
helpstr = helpstr(1:fphtml(apphtmls+1)+3); % cut the string after the first html subfolder
break; % stop when outer 'html' folder is found
end;
helpstr=[]; % delete string if nothing found
end
if ~isempty(helpstr)
if strfind(path,helpstr); rmpath(helpstr); end % remove from path if already there
addpath(helpstr); % add path to html folder; will be parsed by the Documentation
end
%% Importing data
% verify if passed input is a MAT file
function [is] = isMatFile(file)
is = false;
if exist(file,'file')
[~,~,ext] = fileparts(file);
if strcmp(ext,'.mat'); is = true; end;
end;
end
inpMain = inputParser();
noLoad = false;
addOptional(inpMain,'loadData', noLoad, @isMatFile)
inpMain.parse(varargin{:});
loadData = inpMain.Results.loadData;
% ===================================================================
%% UI settings
handles.verbose = true; % sets UI to provide more (true) or less (false) messages
handles.selecting = false; % indicates, if selection is under way and blocks other callbacks of selection
handles.fitfontsize = 0.07; % normalized size of the font in equations
handles.labelfontsize = 0.04; % normalized size of the font in (some) labels
handles.fontsize = 0; % absolute size in pixels, if necessary
% handles.verbose is a switch for a 'warn' method. It decides whether some of the
% warnings appear as warning dialogues, or just command line warnings
% create backdoor object to modify hidden parameters
backdoorObj = BFPGUIbackdoor(@backdoorFunction);
assignin('base','BackDoor',backdoorObj); % assign a handle to the 'base'
% turn off LibTIFF warnings
warning('off','MATLAB:imagesci:tiffmexutils:libtiffWarning');
% constants
RBC = 2.5;
PIP = 1;
CON = 0.75;
%% Variables related to tracking
handles.pattern = []; % pattern to be tracked over the video; an image
handles.lastlistpat = []; % the last pattern chosen for the list
handles.patternlist = []; % list of selected patterns
handles.bead = []; % coordinates of bead to be tracked over the video
handles.lastlistbead = []; % the last bead chosen for the list
handles.beadlist = []; % the list of inicial coordinates of beads for tracking
handles.beadradius = [8,18]; % limits on radius of the bead
handles.beadbuffer = 5; % limit on grace period if bead cannot be detected (in frames)
handles.pipbuffer = 5; % limit on grace period if pipette pattern can't be detected (in frames)
handles.dilate = [2,2]; % erode/dilate parameters
handles.beadsensitivity = 0.8; % circle finding method sensitivity
handles.beadgradient = 0.2; % circle finding method gradient threshold
handles.beadmetricthresh = 0.8; % circle finding method metric threshold
handles.pipmetricthresh = 0.95; % pipette tracking correlation threshold
handles.contrasthresh = 0.95; % contrast quality threshold
handles.overLimit = false; % indicates if currently calculated force is within linear approx. limit of not
%% Variables related to video file
vidObj = []; % video wrapper, to open videos (AVI,MP4,...) and TIFF alike
handles.videopath = pwd; % path to the video file
handles.vidFrameNo = 0; % number of the currently displayed frame
handles.frame = []; % the currently displayed frame
handles.playing = true; % video is allowed to play
handles.disptrack = false; % display track marks on the video
handles.outframerate = 10; % framerate of the output video
handles.outsampling = 1; % each n-th frame of original video is taken for output
%% Experimental parameters
handles.pressure = 200; % aspirating pressure of the pipette
handles.RBCradius = RBC; % radius of RBC
handles.PIPradius = PIP; % inner pipette radius
handles.CAradius = CON; % radius of contact between streptabead and RBC
handles.P2M = 0.1024; % pixels to microns coefficient
handles.stiffness = 200; % RBC stiffness, in pN/micron
%% Tracking data structures
intfields = { 'pattern', 'patcoor', 'beadcoor', 'patsubcoor', 'contrast','reference','frames' };
colnames = {'Range|Start', 'Range|End', 'Bead|X-coor', 'Bead|Y-coor', 'Pipette|X-coor', 'Pipette|Y-coor', 'Anchor|X-coor', 'Anchor|Y-coor', 'Remove'};
colformat = {'numeric', 'numeric','numeric', 'numeric','numeric', 'numeric','numeric', 'numeric','logical'};
axesposition = [0.05,0.05,0.5,0.5];
handles.tmpbeadframe = []; % stores value of current bead frame selected for the interval
handles.tmppatframe = []; % ... and the same for the pipette pattern
handles.interval = struct('frames', [1,1],'pattern',[]); % currently assembled interval (not in the list yet)
handles.intervallist = []; % list of tracking intervals with settings
BFPobj = []; % BFPClass object containing the calculation/tracking
handles.remove = []; % list of interval entries to remove from the complete list
handles.updpatframe = 0; % pipette pattern originating frame, updated during addition process
handles.calibint = []; % single-frame interval in case a calibration frame needs to be set up
%% Plotting and fitting settings variables
handles.lowplot = 1; % lower bound of plotted data
handles.highplot = 10; % upper bound of plotted data
handles.toPlot = 1; % quantity to be plotted (1=contrast, 2=track (3D), 3=trajectories (2D), 4=force, 5=metrics)
handles.thisPlot = []; % quantity currently displayed in the handles.hgraph (+6=outer graph)
handles.thisRange = []; % current range of frames of the plot
handles.fitInt = []; % interval of data to which apply the fitting procedure
handles.kernelWidth = 5; % width of the differentiating kernel in plateau detection
handles.noiseThresh = sqrt(2); % multiple of derivative's std to be considered noise (pleatea)
handles.minLength = 30; % minimal number of frames to constitute a plateau
handles.hzeroline = []; % handle of a line representing a zero force
handles.pushtxt = []; % texthandle for a pushing region descriptor ...
handles.pulltxt = []; % ... and the like for pulling region
handles.contype = 1; % type of contrast metric to display (1=SD2, 2=rSD2; see documentation)
handles.calibrated= false; % flags if the calculated force is calibrated (before force calculation if always false)
handles.anot = []; % handle to LaTeX annotation showing k and Dk
%% Lists of UI handles for export/import; mutable handles
GUIflags.Strings = {'hvideopath', 'hdispframe', 'hfrmrate', 'hsampling', ...
'hpatternlist', 'hpatcoortxt', 'hbeadinilist', 'hbeadcoortxt',...
'hcorrthreshtxt', 'hcontrasthreshtxt', 'hpipbuffer',...
'hminrad', 'hmaxrad', 'hbuffer', 'hsensitivitytxt', ...
'hgradtxt', 'hmetrictxt','hstartint','hendint', 'hrefframe',...
'hpatternint','hbeadint', 'hpatsubcoor','hpressure','hRBCrad',...
'hPIPrad','hCArad','hP2M', 'hvidheight', 'hvidwidth',...
'hvidduration','hvidframes','hvidframerate','hvidname','hvidformat',...
'hlowplot','hhighplot','hgetplatwidth','hplatwidth','hgetplatthresh','hplatthresh',...
'hgetplatmin','hplatmin','hfitint','herode','hdilate','hreframeedt', 'hreframebtn' };
GUIflags.Values = {'hmoviebar', 'hpatternlist', 'hcorrthresh','hcontrasthresh', 'hbeadinilist',...
'hsensitivitybar', 'hgradbar', 'hmetric','hgraphbead','hgraphpip','hverbose','hhideexp',...
'hhidelist','hhidedet', 'hdisptrack', 'hgraphitem', 'hSD2', 'hrSD2'};
GUIflags.Enables = {'hmoviebar', 'hplaybutton', 'hrewindbtn', 'hffwdbutton', ...
'hcontrast', 'hgenfilm', 'hstartint', 'hshowframe', 'hendint', 'hrefframe',...
'hgetrefframe', 'hshowpattern','hgetpattern', 'hselectpat', 'hgetbead',...
'hselectbead', 'hgetpatsubcoor', 'haddinterval', 'heraseint',...
'hupdate','hruntrack','hrunforce','hgraphplot','hgraphitem','hgraphbead',...
'hgraphpip','hlowplot','hhighplot','hreport','hlinearinfo','hfitline',...
'hfitexp','hfitplateau','hplatwidth','hplatthresh','hplatmin','hexport',...
'himport','hreframebtn','hstiffbtn'};
GUIflags.Visibles = {'hpatterns', 'hbeadmethods', 'hpipdetection','hbeaddetection',...
'hexpdata','hgetplatwidth','hgetplatthresh','hgetplatmin','hreframeedt',...
'hreframebtn'};
%% List of GUI variables, mutables
GUIdata = {'verbose', 'selecting', 'fitfontsize', 'labelfontsize', 'pattern', ...
'lastlistpat', 'patternlist', 'bead', 'lastlistbead', 'beadlist', ...
'beadradius', 'beadbuffer', 'pipbuffer', 'beadsensitivity',...
'beadgradient', 'beadmetricthresh', 'pipmetricthresh', 'contrasthresh',...
'overLimit', 'videopath', 'vidFrameNo', 'frame', 'playing',...
'disptrack', 'outframerate', 'outsampling', 'pressure', 'RBCradius',...
'PIPradius', 'CAradius', 'P2M', 'stiffness', 'tmpbeadframe',...
'tmppatframe', 'interval', 'intervallist', 'remove', 'updpatframe',...
'calibint', 'lowplot', 'highplot', 'toPlot', 'thisPlot', 'thisRange',...
'fitInt', 'kernelWidth', 'noiseThresh', 'minLength', 'hzeroline',...
'pushtxt', 'pulltxt', 'contype', 'calibrated', 'dilate','anot' };
%% ================= SETTING UP GUI CONTROLS =========================
% contans parameters for the figure, movie axes, graphing axes and film bar
hfig = figure('Name', 'Pattern tracking','Units', 'normalized', 'OuterPosition', [0,0,1,1], ...
'Visible', 'on', 'Selected', 'on','WindowScrollWheelFcn',{@mouseplay_callback},...
'PaperPositionMode', 'auto','DeleteFcn',{@deleteFigure_callback},...
'CloseRequestFcn',{@closeFigure_callback});
handles.haxes = axes('Parent',hfig,'Units', 'normalized', 'Position', axesposition,...
'Visible', 'on','FontUnits','normalized');
handles.hgraph = axes('Parent',hfig,'Units','normalized', 'Position', [0.6,0.6,0.35,0.35],...
'ButtonDownFcn',{@getcursor_callback},'FontUnits','normalized');
handles.hmoviebar = uicontrol('Parent',hfig, 'Style', 'slider', 'Max', 1, 'Min', 0, 'Value', 0, ...
'Units', 'normalized', 'Enable', 'off',...
'SliderStep', [0.01, 1], 'Position', [0.05, 0.005, 0.5, 0.015],...
'Callback', {@videoslider_callback});
% ===================================================================
%% ================= OPENNING A VIDEO FILE ===========================
% set panel to open, browse and input video path
handles.hopenvideo = uibuttongroup('Parent', hfig, 'Title','Open a video', 'Position', [0.05, 0.56, 0.25, 0.075]);
handles.hvideopath = uicontrol('Parent',handles.hopenvideo, 'Style', 'edit', ...
'Units', 'normalized',...
'String', handles.videopath, 'Position', [0, 0.5, 1, 0.5],...
'Enable', 'on','Callback',{@videopath_callback});
handles.hvideobutton = uicontrol('Parent',handles.hopenvideo,'Style','pushbutton',...
'Units', 'normalized', 'Position', [0,0,0.2,0.5], ...
'String', 'Open', 'Callback',{@openvideo_callback});
handles.hvideobrowse = uicontrol('Parent',handles.hopenvideo,'Style','pushbutton',...
'Units', 'normalized', 'Position', [0.2,0,0.2,0.5], ...
'String', 'Browse', 'Callback',{@browsevideo_callback});
set([handles.hvideobutton,handles.hvideobrowse,handles.hvideopath],'FontUnits','normalized');
% ====================================================================
%% ================= WORKING WITH THE VIDEO ===========================
% video control buttons like, Play, Stop, FFW, RWD, analyze contrast, go to
% frame, generate film, sampling, output framerate etc.
handles.husevideo = uibuttongroup('Parent', hfig, 'Title', 'Video commands', 'Units', 'normalized',...
'Position', [0.31, 0.56, 0.24, 0.1]);
handles.hplaybutton = uicontrol('Parent', handles.husevideo,'Style','pushbutton', ...
'Units', 'normalized', 'Position', [0.2,0.5,0.2,0.5],'FontUnits','normalized',...
'String', 'Play','Interruptible','on', 'Callback', {@playvideo_callback,1});
handles.hrewindbtn = uicontrol('Parent', handles.husevideo,'Style','pushbutton', ...
'Units', 'normalized', 'Position', [0,0.5,0.2,0.5],'FontUnits','normalized',...
'String', 'Rewind','Interruptible','on', 'Callback', {@fastvideo_callback,-5});
handles.hffwdbutton = uicontrol('Parent', handles.husevideo,'Style','pushbutton', ...
'Units', 'normalized', 'Position', [0.4,0.5,0.2,0.5],'FontUnits','normalized',...
'String', '<HTML><center>Fast<br>forward</HTML>','Interruptible','on', 'Callback', {@fastvideo_callback,5});
uicontrol('Parent',handles.husevideo, 'Style','text', 'Units', 'normalized','FontUnits','normalized',...
'Position', [0.6, 0.75, 0.2, 0.25],'String','Frame: ','HorizontalAlignment','left');
handles.hcontrast = uicontrol('Parent',handles.husevideo, 'Style','pushbutton','Units', 'normalized',...
'String', '<HTML><center>Analyse<br>contrast</HTML>', 'Position', [0,0,0.2,0.5],'FontUnits','normalized','Callback',{@getcontrast_callback,'analysis'},...
'TooltipString', 'Calculates contrast measure curve. Useful if splitting video into intervals.');
handles.hdispframe = uicontrol('Parent',handles.husevideo, 'Style','pushbutton', 'Units', 'normalized','FontUnits','normalized',...
'Position', [0.8, 0.75, 0.2, 0.25],'String','0/0','Callback',@gotoframe_callback,...
'Enable','off');
handles.hdisptrack = uicontrol('Parent', handles.husevideo, 'Style', 'checkbox', 'Units', 'normalized',...
'String', 'Display track info', 'Position', [0.6, 0.5, 0.4, 0.25],...
'HorizontalAlignment','left','TooltipString','Displays tracking results on top of the video',...
'Value', handles.disptrack, 'Callback', {@disptrack_callback});
handles.hgenfilm = uicontrol('Parent', handles.husevideo', 'Style', 'pushbutton', 'Units', 'normalized', ...
'String',{'<HTML><center>Generate<br>film'}, 'Position', [0.2,0,0.2,0.5], 'FontUnits','normalized','Callback', {@generatefilm_callback},...
'TooltipString','Generates a video file as an overlay of the open video and tracking marks');
handles.hframeratetxt = uicontrol('Parent', handles.husevideo, 'Style', 'text','Units','normalized', ...
'String','Framerate:','Position',[0.4,0.25,0.2,0.25],'HorizontalAlignment', 'left');
handles.hsamplingtxt = uicontrol('Parent', handles.husevideo, 'Style', 'text','Units','normalized',...
'String','Sampling:','Position',[0.4,0,0.2,0.25],'HorizontalAlignment', 'left',...
'TooltipString','<HTML>The number signifies each n-th frame of the original video to be processed.<br>Note that processing long videos can be time demanding.</HTML>');
handles.hfrmrate = uicontrol('Parent',handles.husevideo,'Style','edit','Units','normalized',...
'String',num2str(10),'Position', [0.6,0.25,0.2,0.25],'Callback',{@outvideo_callback,10,1});
handles.hsampling = uicontrol('Parent',handles.husevideo,'Style','edit','Units','normalized',...
'String',num2str(1),'Position',[0.6,0,0.2,0.25],'Callback',{@outvideo_callback,1,2});
set([handles.hplaybutton,handles.hrewindbtn,handles.hffwdbutton,handles.hcontrast,handles.hgenfilm],'Enable','off');
set([handles.hdisptrack,handles.hframeratetxt,handles.hsamplingtxt,handles.hfrmrate,handles.hsampling], 'FontUnits','normalized');
% ====================================================================
%% ================= PATTERN COLLECTION ===============================
% set pipette tip pattern, create pattern list, add and remove from the
% list, miniaxes to display a pattern, diplay pattern info
handles.hpatterns = uibuttongroup('Parent', hfig, 'Title', 'Pipette patterns', 'Units', 'normalized',...
'Position', [0.56, 0.29, 0.1, 0.26],'Visible','off');
handles.hpatternlist = uicontrol('Parent', handles.hpatterns, 'Style', 'popup', 'String', {'no data'}, 'Value', 1, ...
'Units', 'normalized', 'Position', [0,0.8,1, 0.2], 'FontUnits','normalized','Callback', {@pickpattern_callback});
handles.haddpatternbtn = uicontrol('Parent', handles.hpatterns, 'Style', 'pushbutton', 'Units','normalized',...
'Position', [0,0.5,0.5,0.15], 'String', 'Add', 'Callback', {@addpattern_callback});
handles.hrmpatternbtn = uicontrol('Parent', handles.hpatterns, 'Style', 'pushbutton', 'Units','normalized',...
'Position', [0.5,0.5,0.5,0.15],'String', 'Remove', 'Callback', {@rmpattern_callback});
handles.hrectbutton = uicontrol('Parent',handles.hpatterns,'Style','pushbutton',...
'Units', 'normalized', 'Interruptible', 'off','BusyAction','cancel',...
'Position', [0,0.65,0.5,0.15], 'String', 'Select', 'Callback',{@getrect_callback,'list'});
handles.hpatcoortxt = uicontrol('Parent', handles.hpatterns, 'Style', 'text' , 'String', {'[.,.;,]'},...
'Units', 'normalized', 'Position', [0.5,0.65,0.5,0.15],'FontUnits','normalized');
handles.hminiaxes = axes('Parent',handles.hpatterns, 'Units', 'normalized', 'Position', [0.05, 0.05, 0.9, 0.44],'FontUnits','normalized',...
'Visible', 'on', 'XTickLabel', '', 'YTicklabel', '' );
align(handles.hpatcoortxt,'Left','Middle');
set([handles.haddpatternbtn,handles.hrmpatternbtn,handles.hrectbutton],'FontUnits','normalized');
% ====================================================================
%% ================= BEAD DETECTION METHODS ===========================
% set bead detection methods, create bead list, select ini. coordinate,
% add and remove items from the list
handles.hbeadmethods = uipanel('Parent', hfig, 'Title', 'Bead tracking', 'Units', 'normalized',...
'Position', [0.56, 0.05, 0.1, 0.24],'Visible','off');
handles.hbeadinilist = uicontrol('Parent', handles.hbeadmethods, 'Style', 'popup', 'String', {'no data'},'Value',1,...
'Units', 'normalized', 'Position', [0,0.3,1, 0.25],'FontUnits','normalized', 'Callback', {@pickbead_callback});
handles.hpointbtn = uicontrol('Parent', handles.hbeadmethods, 'Style', 'pushbutton', 'Units', 'normalized',...
'Position', [0,0.15,0.5,0.15], 'Interruptible', 'off','BusyAction','cancel', ...
'String', 'Select', 'Callback', {@getpoint_callback, 'list'});
handles.hbeadcoortxt = uicontrol('Parent', handles.hbeadmethods, 'Style', 'text' , 'String', {'[.,.;,]'},...
'Units', 'normalized', 'Position', [0.5,0.15,0.5,0.15],'FontUnits','normalized');
handles.haddbeadbtn = uicontrol('Parent', handles.hbeadmethods, 'Style', 'pushbutton', 'Units','normalized',...
'Position', [0,0,0.5,0.15], 'String', 'Add', 'Callback', {@addbead_callback});
handles.hrmbeadbtn = uicontrol('Parent', handles.hbeadmethods, 'Style', 'pushbutton', 'Units','normalized',...
'Position', [0.5,0,0.5,0.15],'String', 'Remove', 'Callback', {@rmbead_callback});
set([handles.hpointbtn,handles.haddbeadbtn,handles.hrmbeadbtn], 'FontUnits','normalized');
% ====================================================================
%% ================= PIPETTE DETECTION SETTINGS =======================
% set pipette detection, correlation and contrast thresholds, failed frame
% buffer
handles.hpipdetection = uibuttongroup('Parent', hfig, 'Title', 'Pipette detection settings', 'Units', 'normalized',...
'Position', [0.66, 0.29, 0.1, 0.26],'Visible','off');
handles.hcorrthreshtxt = uicontrol('Parent',handles.hpipdetection, 'Style', 'text', 'String', {'Correlation'; strjoin({'thresh:', num2str(handles.pipmetricthresh)})},...
'TooltipString', 'Lower limit on cross correlation matching', 'Units','normalized',...
'Position', [0,0.8,0.5,0.2],'FontUnits','normalized');
handles.hcorrthresh = uicontrol('Parent',handles.hpipdetection,'Style','slider','Max',1,'Min',0,'Value',handles.pipmetricthresh,...
'Units','normalized','Enable','on','SliderStep',[0.01,0.1],'Position',[0.5,0.8,0.5,0.2],...
'Callback',{@pipmetric_callback});
handles.hcontrasthreshtxt = uicontrol('Parent',handles.hpipdetection, 'Style', 'text', 'String', {'Contrast'; strjoin({'thresh:', num2str(handles.contrasthresh)})},...
'TooltipString', 'Lower limit on contrast decrease', 'Units', 'normalized',...
'Position', [0,0.6,0.5,0.2],'FontUnits','normalized');
handles.hcontrasthresh = uicontrol('Parent',handles.hpipdetection,'Style','slider','Max',1,'Min',0,'Value',handles.contrasthresh,...
'Units','normalized','Enable','on','SliderStep',[0.01,0.1],'Position',[0.5,0.6,0.5,0.2],...
'Callback',{@pipcontrast_callback});
handles.hpipbufftxt = uicontrol('Parent',handles.hpipdetection, 'Style', 'text', 'String', 'Buffer frames',...
'TooltipString', 'Number of consecutive frames of failed detection, allowing procedure to try to recover',...
'Units','normalized','Position', [0,0.4,0.5,0.2]);
handles.hpipbuffer = uicontrol('Parent',handles.hpipdetection', 'Style', 'edit', 'String', num2str(handles.pipbuffer),...
'Units','normalized','Position',[0.5,0.4,0.5,0.2],'Callback',{@pipbuffer_callback});
handles.hdilatetxt = uicontrol('Parent',handles.hpipdetection, 'Style', 'text', 'String', {'Erode'; 'Dilate'},...
'TooltipString', 'Number of pixels for erosion/dilatation of pipette pattern',...
'Units','normalized','Position', [0,0.2,0.5,0.2]);
handles.herode = uicontrol('Parent',handles.hpipdetection', 'Style', 'edit', 'String', num2str(handles.dilate(1)),...
'Units','normalized','Position',[0.5,0.2,0.25,0.2],'Callback',{@dilate_callback});
handles.hdilate = uicontrol('Parent',handles.hpipdetection', 'Style', 'edit', 'String', num2str(handles.dilate(2)),...
'Units','normalized','Position',[0.75,0.2,0.25,0.2],'Callback',{@dilate_callback});
set([handles.hpipbufftxt,handles.hpipbuffer,handles.hdilatetxt],'FontUnits','normalized');
% ====================================================================
%% ================= BEAD DETECTION SETTINGS ==========================
% set pipette detection, radius range, edge and metric sensitivity, failed
% frame buffer, metric threshold
handles.hbeaddetection = uibuttongroup('Parent', hfig, 'Title', 'Bead detection settings', 'Units', 'normalized',...
'Position', [0.66, 0.05, 0.1, 0.24],'Visible','off');
handles.hradtxt = uicontrol('Parent', handles.hbeaddetection, 'Style', 'pushbutton', 'String','<HTML><center>Radius range</HTML>',...
'Units', 'normalized','Position', [0,0.8,0.5,0.2],'Callback',{@getradrange_callback,'beadrad'},'Enable','off');
handles.hminrad = uicontrol('Parent', handles.hbeaddetection, 'Style', 'edit', 'String', num2str(handles.beadradius(1)),...
'Units', 'normalized','Position', [0.5,0.8,0.2,0.2],'Callback', {@setrad_callback},'TooltipString','Input positive interger value');
handles.hmaxrad = uicontrol('Parent', handles.hbeaddetection, 'Style', 'edit', 'String', num2str(handles.beadradius(2)),...
'Units', 'normalized','Position', [0.75,0.8,0.2,0.2],'Callback', {@setrad_callback},'TooltipString','Input positive interger value');
handles.hbuffertxt = uicontrol('Parent', handles.hbeaddetection, 'Style', 'text', 'String', 'Buffer frames',...
'TooltipString', 'Number of frames allowed to pass without successful bead detection',...
'Units', 'normalized','Position', [0,0.6,0.5,0.2]);
handles.hbuffer = uicontrol('Parent', handles.hbeaddetection, 'Style', 'edit', 'String', num2str(handles.beadbuffer),...
'Units', 'normalized','Position', [0.5,0.6,0.2,0.2],'Callback', {@setbuffer_callback});
handles.hsensitivitytxt = uicontrol('Parent', handles.hbeaddetection, 'Style', 'text', 'String', {'Sensitivity: ';num2str(round(handles.beadsensitivity,2))},...
'TooltipString', 'Higher sensitivity detects more circular objects, including weak and obscured',...
'Units', 'normalized','Position', [0,0.4,0.5,0.2]);
handles.hsensitivitybar = uicontrol('Parent',handles.hbeaddetection, 'Style', 'slider', 'Max', 1, 'Min', 0, 'Value', handles.beadsensitivity, ...
'Units', 'normalized', 'Enable', 'on',...
'SliderStep', [0.01, 0.1], 'Position', [0.5, 0.4, 0.5, 0.2],...
'Callback', {@beadsensitivity_callback});
handles.hgradbar = uicontrol('Parent',handles.hbeaddetection, 'Style', 'slider', 'Max', 1, 'Min', 0, 'Value', handles.beadgradient, ...
'Units', 'normalized', 'Enable', 'on',...
'SliderStep', [0.01, 0.1], 'Position', [0.5, 0.2, 0.5, 0.2],...
'Callback', {@beadgrad_callback});
handles.hgradtxt = uicontrol('Parent', handles.hbeaddetection, 'Style', 'text', 'String', {'Gradient: ';num2str(round(handles.beadgradient,2))},...
'TooltipString', 'Lower gradient threshold detects more circular objects, including weak and obscured',...
'Units', 'normalized','Position', [0,0.2,0.5,0.2]);
handles.hmetrictxt = uicontrol('Parent',handles.hbeaddetection,'Style','text','String',{'Metric';strjoin({'thresh:',num2str(handles.beadmetricthresh)})},...
'TooltipString', 'Lower threshold gives more credibility to less certain findings',...
'Units','normalized','Position',[0,0,0.5,0.2]);
handles.hmetric = uicontrol('Parent',handles.hbeaddetection,'Style','slider','Max',2,'Min',0,'Value',handles.beadmetricthresh,...
'Units','normalized','Enable','on','SliderStep',[0.005,0.1],'Position',[0.5,0,0.5,0.2],...
'Callback',{@beadmetric_callback});
set([handles.hradtxt,handles.hminrad,handles.hmaxrad,handles.hbuffertxt, handles.hbuffer,handles.hsensitivitytxt,handles.hgradtxt,handles.hmetrictxt,handles.hmetric],'FontUnits','normalized');
% ====================================================================
%% ==================== SELECTING INTERVALS TO TRACK ==================
% major part of the UI, set the chain of intervals to track, input interval
% of frames, initial bead position (from list or pick in frame), pipette
% pattern (from list of pick in frame), show pattern, select anchor point
% on the pattern, detect reference frame, add to interval
handles.hintervals = uitabgroup('Parent', hfig, 'Units','normalized', 'Position', [0.05, 0.635, 0.25, 0.225]);
handles.hsetinterval = uitab('Parent', handles.hintervals, 'Title', 'Set interval', 'Units', 'normalized');
handles.hintervaltxt= uicontrol('Parent',handles.hsetinterval, 'Style', 'text', 'String', 'Interval:', ...
'TooltipString', 'Interval of interest for tracking, in frames',...
'Units','normalized','Position', [0,0.75,0.25,0.25],'HorizontalAlignment','left' );
handles.hstartint = uicontrol('Parent', handles.hsetinterval, 'Style', 'edit', 'String', [],'Enable','off',...
'Units','normalized','Position', [0.25,0.75,0.15,0.25],'Callback',{@setintrange_callback,1,1});
handles.hshowframe = uicontrol('Parent',handles.hsetinterval, 'Style', 'pushbutton', 'String', 'Show', ...
'Units','normalized','Position', [0.4,0.75,0.1,0.25],'Enable','off',...
'Callback',{@gotointframe_callback});
handles.hendint = uicontrol('Parent', handles.hsetinterval, 'Style', 'edit', 'String', [],'Enable','off',...
'Units','normalized','Position', [0.5,0.75,0.25,0.25],'Callback',{@setintrange_callback,1,2});
handles.hrefframe = uicontrol('Parent',handles.hsetinterval, 'Style', 'edit', 'String', [],'Enable','off',...
'Units','normalized','Position', [0.75,0.75,0.15,0.25],...
'Callback',{@setrefframe_callback,0});
handles.hgetrefframe = uicontrol('Parent',handles.hsetinterval, 'Style', 'pushbutton', 'String', {'Get';'current'}, ...
'Units','normalized','Position', [0.9,0.75,0.1,0.25],'Enable','off',...
'TooltipString', 'Searches pattern''s reference frame in previous records',...
'Callback', {@getrefframe_callback});
handles.hpatterntxt = uicontrol('Parent',handles.hsetinterval, 'Style', 'text', 'String', 'Selected pattern:', ...
'TooltipString', 'Pattern to be tracked over the interval',...
'Units','normalized','Position', [0,0.5,0.25,0.25],'HorizontalAlignment','left' );
handles.hpatternint = uicontrol('Parent',handles.hsetinterval, 'Style', 'text', 'String', '[.,.;.]', ...
'TooltipString', 'Coordinates of the selected pattern',...
'Units','normalized','Position', [0.25,0.5,0.25,0.25],'HorizontalAlignment','center' );
handles.hshowpattern = uicontrol('Parent',handles.hsetinterval, 'Style', 'pushbutton', 'String', 'Show', ...
'Units','normalized','Position', [0.75,0.5,0.25,0.25],'Enable','off',...
'Callback',{@showintpattern_callback});
handles.hgetpattern = uicontrol('Parent',handles.hsetinterval, 'Style', 'pushbutton', 'String', 'List', ...
'Units','normalized','Position', [0.5,0.5,0.125,0.25],...
'Enable','off','Callback',{@getintpat_callback});
handles.hselectpat = uicontrol('Parent',handles.hsetinterval, 'Style', 'pushbutton', 'String', 'Select', ...
'Units','normalized','Position', [0.625,0.5,0.125,0.25],...
'Enable','off','Callback',{@getrect_callback,'interval'});
handles.hbeadtxt = uicontrol('Parent',handles.hsetinterval, 'Style', 'text', 'String', 'Selected bead:', ...
'TooltipString', 'Bead to be tracked over the interval',...
'Units','normalized','HorizontalAlignment','left','Position', [0,0.25,0.25,0.25] );
handles.hbeadint = uicontrol('Parent',handles.hsetinterval, 'Style', 'text', 'String', '[.,.;.]', ...
'TooltipString', 'Coordinates of the selected bead',...
'Units','normalized','Position', [0.25,0.25,0.25,0.25],'HorizontalAlignment','center' );
handles.hgetbead = uicontrol('Parent',handles.hsetinterval, 'Style', 'pushbutton', 'String', 'List', ...
'Units','normalized','Position', [0.5,0.25,0.125,0.25],...
'Enable','off','Callback',{@getintbead_callback});
handles.hselectbead = uicontrol('Parent',handles.hsetinterval, 'Style', 'pushbutton', 'String', 'Select', ...
'Units','normalized','Position', [0.625,0.25,0.125,0.25],...
'Enable','off','Callback',{@getpoint_callback,'interval'});
handles.hpatanchortxt= uicontrol('Parent',handles.hsetinterval, 'Style', 'text', 'String', 'Pattern anchor:', ...
'TooltipString', 'Precise point on the pattern, whose position in time should be reported',...
'Units','normalized','Position', [0,0,0.25,0.25],'HorizontalAlignment','left' );
handles.hpatsubcoor = uicontrol('Parent',handles.hsetinterval, 'Style', 'text', 'String', '[.,.]', ...
'TooltipString', 'Precise point on the pattern to be tracked',...
'Units','normalized','Position', [0.25,0,0.25,0.25],'HorizontalAlignment','center');
handles.hgetpatsubcoor = uicontrol('Parent',handles.hsetinterval, 'Style','pushbutton', 'String', 'Select',...
'Units','normalized','Position', [0.5,0,0.25,0.25], 'Enable','off',...
'Callback', {@getpatsubcoor_callback});
handles.haddinterval = uicontrol('Parent',handles.hsetinterval, 'Style','pushbutton','String',{'Add to list'},...
'Units','normalized','Position', [0.75,0,0.25,0.5],...
'Enable','off','Callback',{@addinterval_callback});
set([handles.hshowframe,handles.hrefframe,handles.hgetrefframe,...
handles.hshowpattern, handles.hgetpattern, handles.hselectpat, handles.hgetbead,...
handles.hselectbead, handles.hgetpatsubcoor, handles.haddinterval], 'FontUnits','normalized');
set([handles.hintervaltxt, handles.hpatterntxt, handles.hpatanchortxt, handles.hbeadtxt,handles.hstartint,...
handles.hendint,handles.hbeadint,handles.hpatternint,handles.hpatsubcoor], 'FontUnits','normalized','FontSize',0.3);
% ====================================================================
%% ============== TABLE OF INTERVALS ==================================
% table showing selected intervals, allows to delete individual intervals
handles.hlistinterval = uitab('Parent', handles.hintervals, 'Title', 'List of intervals', 'Units', 'normalized');
handles.heraseint = uicontrol('Parent',handles.hlistinterval,'Style','pushbutton', 'String', 'Erase', 'Units',...
'normalized', 'Position', [0.9,0.5,0.1,0.5], 'FontUnits','normalized', 'Enable', 'off',...
'Callback',{@eraseint_callback});
% ====================================================================
%% ============== EXPERIMENTAL PARAMETERS =============================
% set, by input or measurement, radii (RBC, contact, pipette), pressute,
% pixel to micron ratio
handles.hexpdata = uibuttongroup('Parent', hfig, 'Title','Experimental parameters', 'Units','normalized',...
'Position', [0.86,0.05,0.1,0.5],'Visible','off');
handles.hprestxt = uicontrol('Parent', handles.hexpdata, 'Style', 'text', 'String', 'Pressure:',...
'Units', 'normalized','Position', [0,0.6,0.5,0.12]);
handles.hpressure = uicontrol('Parent', handles.hexpdata, 'Style', 'edit', 'String', num2str(handles.pressure),...
'Units', 'normalized','Position', [0.5,0.6,0.45,0.2],'Callback', {@setexpdata_callback,handles.pressure});
handles.hRBCtxt = uicontrol('Parent', handles.hexpdata, 'Style', 'pushbutton', 'String','<HTML><center>RBC<br>radius:</HTML>',...
'Units', 'normalized','Position', [0,0.4,0.5,0.2],'Callback',@measureRBC_callback);
handles.hRBCrad = uicontrol('Parent', handles.hexpdata, 'Style', 'edit', 'String', num2str(handles.RBCradius),...
'Units', 'normalized','Position', [0.5,0.4,0.45,0.2],'Callback', {@setexpdata_callback,handles.RBCradius});
handles.hPIPtxt = uicontrol('Parent', handles.hexpdata, 'Style', 'pushbutton', 'String', '<HTML><center>Pipette<br>radius:</HTML>',...
'Units', 'normalized','Position', [0,0.2,0.5,0.2],'Callback',{@measureLength_callback,'pipette'});
handles.hPIPrad = uicontrol('Parent', handles.hexpdata, 'Style', 'edit', 'String', num2str(handles.PIPradius),...
'Units', 'normalized','Position', [0.5,0.2,0.45,0.2],'Callback', {@setexpdata_callback,handles.PIPradius});
handles.hCAtxt = uicontrol('Parent', handles.hexpdata, 'Style', 'pushbutton', 'String', '<HTML><center>Contact<br>radius:</HTML>',...
'Units', 'normalized','Position', [0,0,0.5,0.2],'Callback',{@measureLength_callback,'contact'});
handles.hCArad = uicontrol('Parent', handles.hexpdata, 'Style', 'edit', 'String', num2str(handles.CAradius),...
'Units', 'normalized','Position', [0.5,0,0.45,0.2],'Callback', {@setexpdata_callback,handles.CAradius});
handles.hP2Mtxt = uicontrol('Parent', handles.hexpdata, 'Style', 'pushbutton', 'String', '<HTML><center>Pixel to<br>micron:</HTML>',...
'Units', 'normalized','Position', [0,0.8,0.5,0.2],'Callback', {@measureLength_callback,'scale'});
handles.hP2M = uicontrol('Parent', handles.hexpdata, 'Style', 'edit', 'String', num2str(handles.P2M),...
'Units', 'normalized','Position', [0.5,0.8,0.45,0.2],'Callback', {@setexpdata_callback,handles.P2M});
set([handles.hRBCtxt,handles.hPIPtxt,handles.hCAtxt,handles.hP2Mtxt,handles.hprestxt],'HorizontalAlignment','center','FontUnits','normalized');
set([handles.hpressure,handles.hRBCrad,handles.hPIPrad,handles.hCArad,handles.hP2M],'FontUnits','normalized');
% ====================================================================
%% ================= VIDEO INFORMATION ================================
% only information about the video; read only fields
handles.hvidinfo = uipanel('Parent', hfig,'Title','Video information', 'Units','normalized',...
'Position', [0.05, 0.86, 0.25, 0.1]);
handles.hreframebtn = uicontrol('Parent', handles.hvidinfo, 'Style', 'pushbutton', 'String', 'Reframerate',...
'Units','normalized', 'Position', [0.5,0.75,0.5,0.25],'Callback',{@reframe_callback,'btn'},...
'Enable','off', 'TooltipString','Click to change video framerate','Visible','on');
handles.hreframeedt = uicontrol('Parent', handles.hvidinfo, 'Style', 'edit', 'String', [],...
'Units','normalized', 'Position', [0.5,0.75,0.5,0.25],'Callback',{@reframe_callback,'data'},...
'Visible','off','TooltipString','Input new framerate value');
handles.hvidheight = uicontrol('Parent', handles.hvidinfo, 'Style', 'text', 'Units', 'normalized',...
'HorizontalAlignment','left','String', 'Height:', 'Position', [0,0.75,0.5,0.25]);
handles.hvidwidth = uicontrol('Parent', handles.hvidinfo, 'Style', 'text', 'Units', 'normalized',...
'HorizontalAlignment','left','String', 'Width:', 'Position', [0,0.5,0.5,0.25]);
handles.hvidduration= uicontrol('Parent', handles.hvidinfo, 'Style', 'text', 'Units', 'normalized',...
'HorizontalAlignment','left','String', 'Duration:', 'Position', [0,0.25,0.5,0.25]);
handles.hvidframes = uicontrol('Parent', handles.hvidinfo, 'Style', 'text', 'Units', 'normalized',...
'HorizontalAlignment','left','String', 'Frames:', 'Position', [0,0,0.5,0.25]);
handles.hvidframerate = uicontrol('Parent', handles.hvidinfo, 'Style', 'text', 'Units', 'normalized',...
'HorizontalAlignment','left','String', 'Framerate:', 'Position', [0.5,0,0.5,0.25]);
handles.hvidname = uicontrol('Parent', handles.hvidinfo, 'Style', 'text', 'Units', 'normalized',...
'HorizontalAlignment','left','String', 'Name:', 'Position', [0.5,0.25,0.5,0.25]);
handles.hvidformat = uicontrol('Parent', handles.hvidinfo, 'Style', 'text', 'Units', 'normalized',...
'HorizontalAlignment','left','String', 'Format:', 'Position', [0.5,0.5,0.5,0.25]);
set([handles.hvidheight,handles.hvidwidth,handles.hvidduration,handles.hvidframes,handles.hvidframerate,...
handles.hvidname,handles.hvidformat,handles.hreframebtn,handles.hreframeedt],'FontUnits','normalized');
% ====================================================================
%% ================= RUNNING CALCULATION ==============================
% set calculation, buttons to create BFPClass object, run tracking, get
% force, plotting interface, select contrast,
handles.hcalc = uipanel('Parent', hfig, 'Title', 'Tracking', 'Units', 'normalized',...
'Position', [0.76,0.05,0.1,0.5]);
handles.hupdate = uicontrol('Parent', handles.hcalc, 'Style','pushbutton','String', 'Update', 'Units', 'normalized',...
'TooltipString','Commit modifications of tracking settings',...
'Position', [0, 0.85, 0.5, 0.15], 'Enable', 'off', 'Callback', {@update_callback});
handles.hruntrack = uicontrol('Parent', handles.hcalc, 'Style', 'pushbutton', 'String', 'Track', 'Units', 'normalized',...
'TooltipString','Start tracking procedure for commited intervals',...
'Position', [0.5, 0.85,0.5,0.15], 'Enable', 'off','Callback', {@runtrack_callback});
handles.hrunforce = uicontrol('Parent', handles.hcalc, 'Style', 'pushbutton', 'String', '<HTML><center>Get<br>Force</HTML>', 'Units', 'normalized',...
'TooltipString','Calculate stiffness and force time profile',...
'Position', [0.5, 0.7,0.5,0.15], 'Enable', 'off','Callback', {@runforce_callback});
handles.hgraphplot = uicontrol('Parent', handles.hcalc, 'Style', 'pushbutton', 'String', 'Plot', 'Units', 'normalized',...
'Position', [0,0.5, 0.5, 0.15], 'Enable', 'off', 'Callback', {@graphplot_callback});
handles.hgraphitem = uicontrol('Parent', handles.hcalc, 'Style', 'popup', 'String', {'Contrast', 'Tracks (3D)', 'Trajectories (2D)', 'Force', 'Metrics'},...
'Units','normalized','Position', [0,0.35,0.5,0.15], 'Enable', 'off',...
'Callback', {@graphpopup_callback});
handles.hgraphbead = uicontrol('Parent', handles.hcalc, 'Style', 'checkbox', 'String', 'Bead', 'Enable', 'off',...
'Units','normalized','Position', [0.5, 0.35, 0.5, 0.075]);
handles.hgraphpip = uicontrol('Parent',handles.hcalc, 'Style', 'checkbox', 'String', 'Pipette', 'Enable', 'off',...
'Units','normalized','Position', [0.5, 0.425, 0.5, 0.075]);
handles.hlowplot = uicontrol('Parent', handles.hcalc,'Style','edit','String', num2str(handles.lowplot), 'Units','normalized',...
'Position', [0.5,0.5,0.25,0.15], 'Enable','off','Callback', {@plotrange_callback,handles.lowplot,1});
handles.hhighplot = uicontrol('Parent', handles.hcalc,'Style','edit','String', num2str(handles.highplot), 'Units','normalized',...
'Position', [0.75,0.5,0.25,0.15],'Enable','off', 'Callback', {@plotrange_callback,handles.highplot,2});
handles.hreport = uicontrol('Parent', handles.hcalc,'Style','pushbutton', 'String', '<HTML><center>View<br>Report</HTML>',...
'Tooltipstring', 'Displays summary of the last tracking, illustrating intervals with poor trackability',...
'Units','normalized','Position',[0,0.25,0.5,0.1],'Enable','off','Callback', {@getreport_callback});
handles.hlinearinfo = uicontrol('Parent', handles.hcalc,'Style','pushbutton','String','<HTML><center><font size="3" color="black">?</font></HTML>',...
'Units','normalized','Position',[0,0.65,0.25,0.05],'Enable','off','Callback',{@lininfo_callback},...
'TooltipString','Information on reliability of linear approximation of force');
handles.hstiffbtn = uicontrol('Parent', handles.hcalc,'Style','pushbutton','String','<HTML><center><font size="3" color="black">k</font></HTML>',...
'Units','normalized','Position',[0.25,0.65,0.25,0.05],'Enable','off','Callback',{@stiffinfo_callback},...
'TooltipString','Click here, if stiffness is not displayed correctly');
% contrast type selection button group + radio buttons
handles.hcontype = uibuttongroup('Parent', handles.hcalc, 'Units', 'normalized',...
'Position', [0.5,0.25,0.5,0.1],'SelectionChangedFcn', {@contype_callback});
handles.hSD2 = uicontrol('Parent', handles.hcontype, 'Style', 'radiobutton', 'Units','normalized',...
'String', 'SD2', 'Value', 1,'Position', [0,0.5,1,0.5]);
handles.hrSD2 = uicontrol('Parent', handles.hcontype, 'Style', 'radiobutton', 'Units','normalized',...
'String', 'rSD2', 'Value', 0, 'Position', [0,0,1,0.5]);
set([handles.hupdate,handles.hruntrack,handles.hrunforce,handles.hgraphplot,...
handles.hgraphitem,handles.hgraphbead,handles.hgraphpip,handles.hlowplot,...
handles.hhighplot,handles.hreport,handles.hlinearinfo,handles.hcontype,...
handles.hSD2, handles.hrSD2],'FontUnits','normalized');
% ====================================================================
%% ========================= BASIC FITTING ============================
% set basic fitting, fit line, exponentiel, plateaux, set interval, set
% plateaux detection parameters (plat width etc)
handles.hfit = uipanel('Parent',hfig,'Title','Basic Fitting', 'Units','normalized',...
'Position', [0.45,0.66,0.1,0.30]);
handles.hfitline = uicontrol('Parent',handles.hfit,'Style','pushbutton','String','<HTML><center>Fit<br>line</HTML>',...
'Units','normalized','Position',[0,0.85,1,0.15],'Enable','off',...
'Callback',{@fit_callback,'line',false});
handles.hfitexp = uicontrol('Parent',handles.hfit,'Style','pushbutton','String','<HTML><center>Fit<br>exponentiel</HTML>',...
'Units','normalized','Position',[0,0.7,1,0.15],'Enable','off',...
'Callback',{@fit_callback,'exp',false});
handles.hfitplateau = uicontrol('Parent',handles.hfit,'Style','pushbutton','String','<HTML><center>Fit<br>plateau</HTML>',...
'Units','normalized','Position',[0,0.55,1,0.15],'Enable','off',...
'Callback',{@fit_callback,'plat',false});
handles.hgetplatwidth = uicontrol('Parent',handles.hfit,'Style','edit','String', num2str(handles.kernelWidth),...
'TooltipString', strcat('<HTML>Defines the sensitivity of differentiating kernel.<br>',...
'The kernel is derivative of Gaussian. Sensitivity is then std of the original Gaussian.</HTML>'),...
'Units','normalized','Position',[0,0.4,0.4,0.15],'Callback',{@getplat_callback,1},...
'Visible','off');
handles.hplatwidth = uicontrol('Parent',handles.hfit,'Style','pushbutton','String',strcat('<HTML><center>Sensitivity<br>',...
num2str(round(handles.kernelWidth)),'</HTML>'), 'TooltipString', strcat('<HTML>Defines the sensitivity of differentiating kernel.<br>',...
'The kernel is derivative of Gaussian. Sensitivity is then &sigma of the original Gaussian.</HTML>'),...
'Units','normalized','Position',[0,0.4,0.4,0.15],'Callback',{@platswitch_callback,handles.hgetplatwidth},...
'Enable','off');
handles.hgetplatthresh= uicontrol('Parent',handles.hfit,'Style','edit','String', num2str(handles.noiseThresh),...
'TooltipString', strcat('<HTML>Defines the threshold of noise.<br>',...
'Multiple of std of force derivative to be still considered noise.</HTML>'),...
'Units','normalized','Position',[0.4,0.4,0.3,0.15],'Callback',{@getplat_callback,2},...
'Visible','off');
handles.hplatthresh = uicontrol('Parent',handles.hfit,'Style','pushbutton','String',strcat('<HTML><center>Thresh<br>',...
num2str(round(handles.noiseThresh,1)),'</HTML>'), 'TooltipString', strcat('<HTML>Defines the threshold of noise.<br>',...
'Multiple of std of force derivative to be still considered noise.</HTML>'),...
'Units','normalized','Position',[0.4,0.4,0.3,0.15],'Callback',{@platswitch_callback,handles.hgetplatthresh},...
'Enable','off');
handles.hgetplatmin = uicontrol('Parent',handles.hfit,'Style','edit','String', num2str(handles.minLength),...
'TooltipString', strcat('<HTML>Defines minimal length of plateau.<br>',...
'Minimal number of continuous frames with derivative below threshold, to constitute a plateau.</HTML>'),...
'Units','normalized','Position',[0.7,0.4,0.3,0.15],'Callback',{@getplat_callback,3},...
'Visible','off');
handles.hplatmin = uicontrol('Parent',handles.hfit,'Style','pushbutton','String',strcat('<HTML><center>Length<br>',...
num2str(round(handles.minLength)),'</HTML>'), 'TooltipString', strcat('<HTML>Defines minimal length of plateau.<br>',...
'Minimal number of continuous frames with derivative below threshold, to constitute a plateau.</HTML>'),...
'Units','normalized','Position',[0.7,0.4,0.3,0.15],'Callback',{@platswitch_callback,handles.hgetplatmin},...
'Enable','off');
handles.hfitint = uicontrol('Parent',handles.hfit,'Style','pushbutton','String','<HTML><center>Choose<br>interval</HTML>',...
'Units','normalized','Position',[0,0,1,0.15],'Callback',{@fitint_callback});
set([handles.hfitline,handles.hfitexp,handles.hfitplateau,handles.hgetplatwidth,handles.hplatwidth,...
handles.hgetplatthresh, handles.hplatthresh,handles.hgetplatmin,handles.hplatmin,handles.hfitint],'FontUnits','normalized');
% ====================================================================
%% ================= IMPORT,EXPORT,UI SETTINGS ============================
% set IO setting, from-to fields, import-export buttons
handles.hio = uipanel('Parent',hfig,'Title','Import, export, UI settings', 'Units','normalized',...
'Position', [0.31,0.66,0.14,0.30]);
handles.hvar = uicontrol('Parent', handles.hio, 'Style', 'popupmenu', 'Units', 'normalized', 'String',...
{'force & tracks'; 'frame'; 'graph'; 'session'}, 'Enable', 'on', 'Position',...
[0,0.9,1,0.1], 'Callback', {@port_callback});
handles.htar = uicontrol('Parent', handles.hio, 'Style', 'popupmenu', 'Units', 'normalized', 'String',...
{'workspace'; 'data file'; 'figure/media'}, 'Enable', 'on', 'Position',...
[0,0.6,1,0.1], 'Callback', {@port_callback});
handles.hexport = uicontrol('Parent', handles.hio, 'Style','pushbutton','Units','normalized','String',...
strcat('Export',char(8595)),...
'Position', [0,0.75,0.5,0.15],'Callback',{@export_callback}, 'Enable','on');
handles.himport = uicontrol('Parent', handles.hio, 'Style','pushbutton','Units','normalized','String',...
strcat('Import',char(8593)),...
'Position', [0.5,0.75,0.5,0.15],'Callback',{@import_callback}, 'Enable','off');
handles.hverbose = uicontrol('Parent', handles.hio, 'Style','checkbox','Units','normalized','String','Verbose output',...
'Min', 0, 'Max', 1, 'Value',1 ,'Position', [0,0.5,1,0.1],'Callback',{@verbose_callback},...
'TooltipString','Verbose output means more warnings, suggestions, dialog windows etc.');
handles.hhideexp = uicontrol('Parent',handles.hio,'Style','togglebutton','Min',0, 'Max',1,'Value',0,'Units','normalized',...
'Position',[0,0.25,1,0.1],'String','Show experimental data panel','Callback',{@hidepanel_callback,'experimental data',handles.hexpdata});
handles.hhidelist= uicontrol('Parent',handles.hio,'Style','togglebutton','Min',0, 'Max',1,'Value',0,'Units','normalized',...
'Position',[0,0.15,1,0.1],'String','Show tracking list panel','Callback',{@hidepanel_callback,'tracking list',[handles.hpatterns,handles.hbeadmethods]});
handles.hhidedet = uicontrol('Parent',handles.hio,'Style','togglebutton','Min',0, 'Max',1,'Value',0,'Units','normalized',...
'Position',[0,0.05,1,0.1],'String','Show advanced detection panel','Callback',{@hidepanel_callback,'advanced detection',[handles.hpipdetection,handles.hbeaddetection]});
handles.hsetfont = uicontrol('Parent', handles.hio,'Style','edit','String', num2str(handles.fontsize),...
'TooltipString', 'Input new font size. Number >1 for pixels, <1 for normalized.',...
'Units','normalized', 'Position',[0.5,0.4,0.5,0.1],'Callback',{@setFontsize_callback});
handles.htxtfont = uicontrol('Parent', handles.hio,'Style','text','String', 'UI Fontsize:',...
'TooltipString', 'Input font size in pixels.','Units','normalized',...
'Position',[0,0.4,0.5,0.1],'HorizontalAlignment','left');
set([handles.hvar,handles.htar,handles.hexport,handles.himport,handles.hverbose,...
handles.hhideexp,handles.hhidelist,handles.hhidedet, handles.hsetfont,...
handles.htxtfont],'FontUnits','normalized');
%% ============ LOAD OLDER SESSION PASSED AS ARGUMENT =================
% if the data exist, load the saved environment
% it is only called here, after the GUI is constructed
if loadData;
loadEnvironment(loadData);
end
%% ============ DELETE FIGURE CLEAN MEMORY ============================
% cleans memory after figure is deleted
% is figure handle is left in the base workspace and reused for the
% next function call, it all gets terribly messed up. Hope this settles
% it. Collateral damage
function deleteFigure_callback(~,~)
backdoorObj.delete;
hfig.delete;
evalin('base','clear ''all'' '); % delete handles from the base
clear handles;
clear hfig;
end
function closeFigure_callback(~,~)
choice = questdlg(strjoin({'Do You want to close the window?',...
'Note that after closing the window, the base workspace',...
'of Matlab is cleared (for technical reasons) including all',...
'previous data or data exported during the GUI session.'}),...
'Close figure request',...
'Yes','No','Yes');
switch choice,
case 'Yes',
deleteFigure_callback(0,0);
case {'No',''}
return;
end
end
%% ====================================================================
%% ================= CALLBACK FUNCTIONS ===============================
%% Change font size figurewide
% changes font size to user specified input; input > 1 uses pixel
% units, input in range (0,1] uses normalized units
function [] = setFontsize_callback(src,~)
newval = round(str2double(src.String),1); % one decimal place
if (isnan(newval) || newval <= 0) % if input is incorrect, revert
src.String = num2str(handles.fontsize);
warndlg(strjoin({'The input must be a positive number. Numbers',...
'larger than 1 stand for fontsize units in pixels, positive',...
'numbers below 1 stand for normalized fontsize.'}),...
'Incorrect input','replace');
return;
end
handles.fontsize = newval; % set new value otherwise
src.String=newval; % print rounded string
allhandles=fields(handles); % get all handles
if newval <= 1; % normalized input
unitype='normalized';
else % input in pixels
unitype='pixels';
end
for h=1:numel(allhandles)
if isgraphics(handles.(allhandles{h}),'uicontrol') % pick uicontrols
set(handles.(allhandles{h}),'FontUnits',unitype,'FontSize',handles.fontsize);
end
end
end
%% Move mouse wheel to advance/rewind the video frame
% mouse wheel action to advance or roll back the video frames
function mouseplay_callback(~,data)
if isempty(vidObj); return; end; % without video, do nothing
c = hfig.CurrentPoint; % get coordinate at wheel time
newframe = data.VerticalScrollCount + vidObj.CurrentFrame; % calculate new frame to display
if newframe <= 0 || newframe > vidObj.Frames % check if in bounds
return;
end
if ( c(1) >= axesposition(1) && c(1) <= axesposition(1)+axesposition(3) ) ...
&& ( c(2) >= axesposition(2) && c(2) <= axesposition(2)+axesposition(4) )
setframe(newframe); % move video to the new frame
end
end
%% Hide GUI panel
% allows to hide a panel of functions (particularly for experimental
% data panel, tracking lists panel and tracking parameters panel
function hidepanel_callback(source,~,name,hpanel)
val=source.Value;
if val==1
set(hpanel,'Visible','on');
source.String = strjoin({'Hide',name,'panel'});
elseif val==0
set(hpanel,'Visible','off');
source.String = strjoin({'Show',name,'panel'});
end
end
%% Import function
% controls various import possibilities
function import_callback(~,~)
var = handles.hvar.Value; % which variable is to be imported; 1=force & track, 2=frame, 3=graph, 4=session
src = handles.htar.Value; % target of the import; 1=workspace, 2=datafile, 3=figure
if isempty(BFPobj) % BFPobj was not yet instantiated
BFPobj = BFPClass(); % call default constructor
end;
dlgstr = [];
% nested function; prepares import for various input types
function importFeed(dataString)
fileName = getFileName(strcat(dataString,'.dat'));
inData = dlmread(fileName);
BFPobj.importData(dataString,inData)
set([handles.hgraphitem, handles.hgraphplot, handles.hlowplot, handles.hhighplot], 'Enable', 'on');
end
switch var
case 1 % force & tracks
if src==1 % workspace - no action
elseif src==2 % datafiles
if handles.verbose
dlgstr = strjoin({'Please be aware, that importing the force data or tracking data',...
'will overwrite the whole data structure calculated so far.',...
'There is no undo option. You would need to load Your settings again,',...
'using the button ''Update'' and perform the tracking and calculation.'});
end
choice = questdlg(strcat({dlgstr;'Please select the type of data You want to import'}),...
'Import data type','Force','Pipette track', 'Bead track', 'Force');
switch choice
case 'Force'
importFeed('force');
case 'Pipette track'
importFeed('pipPositions');
handles.hgraphpip.Enable = 'on';
case 'Bead track'
importFeed('beadPositions');
handles.hgraphbead.Enable = 'on';
otherwise % (X) option
return;
end
elseif tar==3; % figure/image - no action
end
case 2 % frame
% no import of individual frames; as for now, support of
% import of frames from workspace of mediafiles might be
% added
case 3 % graph
if src==1 % workspace
options=struct( 'Resize', 'off', 'WindowStyle','normal','Interpreter','tex' );
inHandle = inputdlg('Input name of the handle to the{\bf figure} You want to import:',...
'Import graph from workspace',1,{''},options);
if isempty(inHandle); return; end;
himportaxes = findobj(evalin('base',inHandle{1}),'Type','axes'); % gets handle of imported axes
cla(handles.hgraph); % clear the axes
himportgraph = copyobj(allchild(himportaxes), handles.hgraph); % copy axes into handles.hgraph axes
set(himportgraph, 'HitTest','off');
handles.thisPlot = 6; % flag saying fitting is possible, but data is outer
elseif src==2 % datafile
% no datafile import - data can be easily imported and
% then plotted using GUI
elseif src==3 % figure/image
inipath = fullfile(pwd,'graph.fig');
[name, dir] = uigetfile({'*.fig;','Figure files (*.fig)'},...
'Select a figure file for import',inipath);
if isempty(name) || isempty(dir); return; end; % if empty, return
figName = fullfile(dir,name);
importfig = openfig(figName,'new','invisible');
himportaxes = findobj(importfig,'Type','axes');
cla(handles.hgraph);
himportgraph = copyobj(allchild(himportaxes), handles.hgraph); % copy axes into handles.hgraph axes
set(himportgraph, 'HitTest','off');
handles.thisPlot = 6;
delete(importfig);
end
case 4 % session
if src==1 % workspace - no action
elseif src==2 % datafile (here .mat file)
fileName = getFileName('BFPsession.mat');
loadEnvironment(fileName);
assignin('base','BFPbackdoor',backdoorObj); % send the new backdoor to the base WS
elseif src==3 % figure/image - no action
end
end
end
%% Export function
% controls various combinations of figure elements and targets of export
function export_callback(~,~)
var = handles.hvar.Value; % which variable is to be exported; 1=force & track, 2=frame, 3=graph, 4=session
tar = handles.htar.Value; % target of the export; 1=workspace, 2=datafile, 3=figure
switch var
case 1 % force & tracks
if tar==1 % workspace
assignin('base','force',BFPobj.force);
assignin('base','pipPositions',BFPobj.pipPositions);
assignin('base','beadPositions',BFPobj.beadPositions);
elseif tar==2 % datafiles
fileName = putFileName('force.dat');
if isequal(fileName,0); return; end; % return if cancelled
force = zeros(BFPobj.trackedFrames,2); % prealocate
i=1;
for frm=BFPobj.minFrame:BFPobj.maxFrame
force(i,:) = [frm, BFPobj.getByFrame(frm,'force')];
i=i+1;
end
dlmwrite(fileName, force);
fileName = putFileName('pipPositions.dat');
if isequal(fileName,0); return; end; % return if cancelled
pipPos = zeros(BFPobj.trackedFrames,3);
i=1;
for frm=BFPobj.minFrame:BFPobj.maxFrame
pipPos(i,:) = [frm, BFPobj.getByFrame(frm,'pipette')];
i=i+1;
end
dlmwrite(fileName, pipPos);
fileName = putFileName('beadPositions.dat');
if isequal(fileName,0); return; end; % return if cancelled
beadPos = zeros(BFPobj.trackedFrames,3);
i=1;
for frm=BFPobj.minFrame:BFPobj.maxFrame
beadPos(i,:) = [frm, BFPobj.getByFrame(frm,'bead')];
i=i+1;
end
dlmwrite(fileName, beadPos);
elseif tar==3; % figure/image - no action
end
case 2 % frame
if tar==1 % workspace
capture = getframe(handles.haxes);
assignin('base','capturedFrame', capture);
elseif tar==2; % datafile - no action
elseif tar==3 % figure/media
htempfig = figure('Name','transient','Visible','on');
hnewaxes = copyobj(handles.haxes,htempfig);
set(hnewaxes, 'Units','normalized','OuterPosition',[0,0,1,1]); % save access, fill the whole figure
colormap(hnewaxes,gray); % makes sure colormap is gray
fileName = putFileName('frame.bmp');% call to set up filepath
if isequal(fileName,0); return; end; % return if cancelled
saveas(htempfig,fileName); % save the trans. figure
delete(htempfig);
end
case 3 % graph
if tar==1 % workspace
hexportfig = figure('Name','BFP - graph');
hexportgraph = copyobj(handles.hgraph, hexportfig);
set(hexportgraph, 'Units', 'normalized', 'OuterPosition', [0,0,1,1]);
set(allchild(hexportgraph),'HitTest','on');
assignin('base','BFPgraph',hexportfig);
elseif tar==2 % datafile
if isempty(handles.thisPlot);
if handles.verbose; helpdlg('Nothing plotted. Try to replot.','Empty graph');end;
return;
else
% delete descriptive line (these are not exported)
if ~isempty(handles.hzeroline); handles.hzeroline.delete; end;
if ~isempty(handles.pushtxt); handles.pushtxt.delete; end;
if ~isempty(handles.pulltxt); handles.pulltxt.delete; end;
lines = findobj(handles.hgraph,'type','line'); % get lines in the graph
if isempty(lines); % abort if no graphlines
warn('No data object to export found in the graph.');
return;
end;
graphData = []; % declare, empty
switch handles.thisPlot
case 1 % contrast; any non-contrast line is discarded! (e.g. fit lines)
graphData.name = putFileName('contrastGraph.dat');
if isequal(graphData.name,0); return; end; % return if cancelled
if numel(lines) > 1; % contrast should be a single line
tcont = vidObj.getContrastByFrame(handles.thisRange(1),handles.contype);
for child = 1:numel(lines)
if lines(child).YData(1) == tcont;
graphData.coor(:,1) = lines(child).XData;
graphData.coor(:,2) = lines(child).YData;
break;
end
end
else
graphData.coor(:,1) = lines.XData;
graphData.coor(:,2) = lines.YData;
end
case { 2, 3 } % trajectories and metrics
for child = 1:numel(lines) % 2 lines
if ~isempty(lines(child).ZData)
graphData(child).coor(:,1) = get(lines(child),'ZData');
else
graphData(child).coor(:,1) = handles.thisRange(1):handles.thisRange(2);
end;
graphData(child).coor(:,2) = get(lines(child),'XData');
graphData(child).coor(:,3) = get(lines(child),'YData');
if graphData(child).coor(1,2:3) == BFPobj.getByFrame(handles.thisRange(1),'bead');
graphData(child).name = putFileName('beadGraph.dat');
if isequal(graphData(child).name,0); return; end; % return if cancelled
elseif graphData(child).coor(1,2:3) == BFPobj.getByFrame(handles.thisRange(1),'pipette');
graphData(child).name = putFileName('pipGraph.dat');
if isequal(graphData(child).name,0); return; end; % return if cancelled
end
end
case 4 % force
graphData.name = putFileName('forceGraph.dat');
if isequal(graphData.name,0); return; end; % return if cancelled
graphData.coor(:,1) = lines.XData;
graphData.coor(:,2) = lines.YData;
case 5 % metrics
tmetrics = BFPobj.getByFrame(handles.thisRange(1),'metric'); % read values for the first frame
for child = 1:numel(lines) % 2 lines
graphData(child).coor(:,1) = lines(child).XData;
graphData(child).coor(:,2) = lines(child).YData;
if graphData(child).coor(1,2) == tmetrics(1);
graphData(child).name = putFileName('beadmetricGraph.dat');
if isequal(graphData(child).name,0); return; end; % return if cancelled
elseif graphData(child).coor(1,2) == tmetrics(2);
graphData(child).name = putFileName('pipmetricGraph.dat');
if isequal(graphData(child).name,0); return; end; % return if cancelled
else
warn('Export failed, graph could not be matched with underlying data');
graphData = []; % delete
return; % and abort
end
end
end
for child = 1:numel(graphData)
dlmwrite(graphData(child).name, graphData(child).coor);
end
end
elseif tar==3 % figure/image
htempfig = figure('Name','transient','Visible','on');
hnewaxes = copyobj(handles.hgraph,htempfig);
hax2 = findobj('Tag','deformationaxis');
if ~isempty(hax2)
hax2 = copyobj(hax2,htempfig);
end
set(hnewaxes, 'Units','normalized','OuterPosition',[0,0,1,1]); % save access, fill the whole figure
hnewaxes.YLim = handles.hgraph.YLim; % this does not copy well otherwise
hpos = hnewaxes.Position;
set(hax2,'Units','normalized','Position',[hpos(1)+hpos(3),hpos(2),0,hpos(4)]);
hax2.YLim = handles.hgraph.YLim/BFPobj.k;
fileName = putFileName('BFPgraph.fig'); % call to set up filepath
if isequal(fileName,0); return; end; % return if cancelled
saveas(htempfig,fileName); % save the trans. figure
delete(htempfig);
end
case 4 % session
if tar==1 % workspace - no action
elseif tar==2 % datafile (here .mat file)
fileName = putFileName('BFPsession.mat');
if isequal(fileName,0); return; end; % return if cancelled
saveEnvironment(fileName); % saves the whole workspace (all variables) to the file
elseif tar==3 % figure/image - no action
end
end
end
%% I/O settings for various in/out target combinations
% choosing various combinations of target/source for import/export; not
% all the combinations are possible
function port_callback(~,~)
var = handles.hvar.Value;
tar = handles.htar.Value;
switch var % variable switch
case 1 % force & tracks
switch tar
case 1 % workspace
handles.hexport.Enable = 'on';
handles.himport.Enable = 'off'; %!!!
case 2 % datafile
handles.hexport.Enable = 'on';
handles.himport.Enable = 'on';
case 3 % figure; no IO for data <-> figure
handles.hexport.Enable = 'off';
handles.himport.Enable = 'off';
if handles.verbose;
helpdlg(strjoin({'Export of data into figure and visa versa is not possible.',...
'If You wish to use data to produce figure, You can export them to the Matlab basic workspace.'}),...
'Unsupported export/import');
end;
end
case 2 % frame
switch tar
case 1 % workspace
handles.hexport.Enable = 'on';
handles.himport.Enable = 'off';
case 2 % datafile
handles.hexport.Enable = 'off'; % no frame export to data
handles.himport.Enable = 'off';
if handles.verbose;
helpdlg(strjoin({'Export of frame into datafile (i.e. not image or figure) is not possible',...
'If You wish to export current frame into external file, use media or figure file.'}),...
'Unsupported export/import');
end
case 3 % figure/media
handles.hexport.Enable = 'on';
handles.himport.Enable = 'off';
end
case 3 % graph
switch tar
case 1 % workspace
handles.hexport.Enable = 'on';
handles.himport.Enable = 'on';
case 2 % datafile
handles.hexport.Enable = 'on'; % exports underlying data into datafile
handles.himport.Enable = 'off'; % no import of data into graph, they can plot them elsewhere
case 3 % figure/media
handles.hexport.Enable = 'on';
handles.himport.Enable = 'on'; % figure only
end
case 4 % session
switch tar
case 1 % workspace
handles.hexport.Enable = 'off';
handles.himport.Enable = 'off';
if handles.verbose;
helpdlg('Export and import of session between workspaces is currently not possible.',...
'Unsupported import/export');
end
case 2 % datafile
handles.hexport.Enable = 'on';
handles.himport.Enable = 'on';
case 3
handles.hexport.Enable = 'off';
handles.himport.Enable = 'off';
if handles.verbose;
helpdlg('Export of session into figure and visa versa is not possible.', 'Unsupported export/import');
end;
end
end
end
%% Set verbose or more silent input
% sets the 'handles.verbose' flag; this usually switches warning
% dialogs to command line warnings
function verbose_callback(source,~)
handles.verbose = logical(source.Value);
end
%% Change UI control (mostly button) for input (edit field)
% changes UI to allow user input; for plateaux detection
function platswitch_callback(source,~,setter)
source.Visible = 'off';
setter.Visible = 'on';
end
%% Read input value (edit field) and set variable
% reads and sets the value from the UI element; plateaux detection
function getplat_callback(source,~,var)
val = str2double(source.String);
if ( isnan(val) || val < 0 )
warndlg('The input must be a positive number.','Incorrect input','replace');
switch var
case 1
source.String = num2str(handles.kernelWidth);
case 2
source.String = num2str(handles.noiseThresh);
case 3
source.String = num2str(handles.minLength);
end
return;
end
source.Visible = 'off';
switch var
case 1
handles.kernelWidth = val;
handles.hplatwidth.String = strcat('<HTML><center>Sensitivity<br>',...
num2str(round(handles.kernelWidth)),'</HTML>');
handles.hplatwidth.Visible = 'on';
case 2
handles.noiseThresh = val;
handles.hplatthresh.String = strcat('<HTML><center>Thresh<br>',...
num2str(round(handles.noiseThresh,1)),'</HTML>');
handles.hplatthresh.Visible = 'on';
case 3
handles.minLength = val;
handles.hplatmin.String = strcat('<HTML><center>Length<br>',...
num2str(round(handles.minLength)),'</HTML>');
handles.hplatmin.Visible = 'on';
end
end
%% Fitting function
% fitting the graph; only one type of fitting line at the time; fitted
% line is redrawn; every separate line object is fittend by a line
% (i.e. several fitting lines for discontiguous graph)
function fit_callback(~,~,type,limit)
% delete descriptive objects
if ~isempty(handles.hzeroline); handles.hzeroline.delete; end;
if ~isempty(handles.pushtxt); handles.pushtxt.delete; end;
if ~isempty(handles.pulltxt); handles.pulltxt.delete; end;
getcursor_callback(0,0,true); % delete possible point marker
% fitted lines are persistent; erased every time fitting is called
persistent hfitplot;
persistent hsublimplot;
if numel(hfitplot);
for p=1:numel(hfitplot); % erase graphical elements from ...
hfitplot(p).ph.delete; % ... the plotter area
hfitplot(p).txt.delete;
end;
hfitplot = []; % clear now empty structure
end
if numel(hsublimplot);
for p=1:numel(hsublimplot);
hsublimplot(p).ph.delete;
hsublimplot(p).txt.delete;
end;
hsublimplot = [];
end
if handles.thisPlot ~= 4 && handles.thisPlot ~= 1 && handles.thisPlot ~=5 && handles.thisPlot ~= 6
choice = questdlg(['The fitting procedure is available only for force, contrast, metrics and imported outer graph',...
'Would You like to switch graph?'],'Data fitting','Force','Contrast','Metrics', 'Force');
switch choice
case 'Force'
handles.toPlot = 4;
handles.hgraphitem.Value = handles.toPlot;
graphplot_callback(0,0);
case 'Contrast'
handles.toPlot = 1;
handles.hgraphitem.Value = handles.toPlot;
graphplot_callback(0,0);
case 'Metrics'
handles.toPlot = 5;
handles.hgraphitem.Value = handles.toPlot;
graphplot_callback(0,0);
otherwise % cancelled
return;
end
end
% set up descriptive strings
switch handles.thisPlot
case 4 % force
units = '\; pN$$';
unit = ' pN/s';
lunits = '\frac{pN}{s}$$';
quant = '$$\bar{F}=';
rnd = 1;
case 1 % contrast
units = '$$';
unit = ' per second';
lunits = '\; s^{-1}$$';
quant = '$$\bar{C}=';
rnd = 3;
case 5 % metrics
units = '$$';
unit = ' per second';
lunits = '\; s^{-1}$$';
quant = '$$\bar{\mu}=';
rnd = 3;
end
eunits = '\; s^{-1}$$'; % the same for all cases
% set fitting interval
if isempty(handles.fitInt);
handles.fitInt = [ handles.hgraph.XLim(1), 0; handles.hgraph.XLim(2), 0] ; % if none provided, select current graph limits
handles.hfitint.String = strcat('<HTML><center>Change<br>[',num2str(round(handles.fitInt(1,1))),',',...
num2str(round(handles.fitInt(2,1))),']</HTML>'); % save the info about the current fitting interval
end
iniInt = [handles.fitInt(1,1), handles.fitInt(2,1)]; % set the selected interval
xdata = struct('data',[]); % initialize variable for data range
ydata = struct('data',[]); % initialize variable for the data to be fit
hplotline = findobj(handles.hgraph,'Type','line'); % find the data line
for l = 1:numel(hplotline)
xdata(l).data = (max(hplotline(l).XData(1),iniInt(1)):min(hplotline(l).XData(end),iniInt(2)))';
if isempty(xdata(l).data) || strcmp(hplotline(l).Tag,'intbound');
xdata(l).data = [];
ydata(l).data = [];
else ydata(l).data = hplotline(l).YData(xdata(l).data - hplotline(l).XData(1)+1)';
end
end;
% prune empty items
for l=numel(xdata):-1:1
if isempty(xdata(l).data);
xdata(l) = [];
ydata(l) = [];
end
end
nextPlateau = 1;
hold(handles.hgraph,'on');
for l=1:numel(ydata) % for all fitted data lines
switch type
case 'line'
[ coeff, err ] = polyfit( xdata(l).data, ydata(l).data, 1);
[ ffrc, ~ ] = polyval( coeff, xdata(l).data, err );
disp(strcat('Fitted slope: ',num2str(coeff(1)*vidObj.Framerate), unit) );
hfitplot(l).ph = plot(handles.hgraph, xdata(l).data, ffrc, 'r', 'HitTest', 'off');
str = strcat('$$r=',num2str(round(coeff(1)*vidObj.Framerate,2,'significant')),lunits);
pos = 0.5*[ (xdata(l).data(end) + xdata(l).data(1)), (ffrc(end)+ffrc(1)) ];
hfitplot(l).txt = text( 'Parent', handles.hgraph, 'interpreter', 'latex', 'String', str, ...
'Units', 'data', 'Position', pos, 'Margin', 1, 'FontUnits','normalized',...
'LineStyle','none', 'HitTest','off','FontSize',handles.fitfontsize, 'FontWeight','bold','Color','red',...
'VerticalAlignment','top');
case 'exp'
if ~isempty(vidObj) % make sure vidObj exist, if it doesn't, outer data are being fit
if (vidObj.Framerate ~= 0)
[ coeff, ffrc ] = expfit( xdata(l).data, ydata(l).data, 'framerate', vidObj.Framerate );
end
else % case for imported data
[ coeff, ffrc ] = expfit( xdata(l).data, ydata(l).data );
end
disp(strcat('Time constant: ',num2str(coeff(1)),{' '},'s') );
str = strcat('$$\eta=',num2str(round(1/coeff(1),3,'significant')),eunits);
pos = 0.5*[ (xdata(l).data(end) + xdata(l).data(1)), (ffrc(end)+ffrc(1)) ];
hfitplot(l).txt = text( 'Parent', handles.hgraph, 'interpreter', 'latex', 'String', str, ...
'Units', 'data', 'Position', pos, 'Margin', 1, 'FontUnits','normalized',...
'LineStyle','none', 'HitTest','off','FontSize',handles.fitfontsize, 'FontWeight','bold','Color','red',...
'VerticalAlignment','middle');
hfitplot(l).ph = plot(handles.hgraph, xdata(l).data, ffrc, 'r', 'HitTest', 'off');
case 'plat'
sf = backdoorObj.edgeDetectionKernelSemiframes; % default 10
if numel(xdata(l).data) < (2*sf + 5) % require at least 4 points with analysis
warndlg('Interval is too short for analysis','Insufficient data', 'replace');
return;
end
locint = xdata(l).data(sf:end-sf); % crop the ends; dfrc at those frames would be padded
sw = 2 * handles.kernelWidth^2; % denominator of Gaussian
dom = -sf:sf; % domain (in number of frames)
gauss = exp(-dom.^2/sw); % the gaussian
dgauss = diff(gauss); % differentiating gaussian kernel to get edge detector
dfrc = abs(conv(ydata(l).data,dgauss,'valid')); % differentiating the force; keeping only unpadded values
thresh = handles.noiseThresh*std(dfrc); % threshold for noise
ffrc = (dfrc < thresh); % any slope below noise is plateaux
limits = [0,0];
if ~exist('plateaux','var') || isempty(plateaux)
plateaux(1).limits = limits;
else
plateaux(end+1).limits = limits;
end;
for i=1:numel(ffrc)
if (ffrc(i) && limits(1) == 0) % plateau and not counting yet; first frame
if i > sf; limits(1) = i; end; % plateau can't start in a padded zone
elseif ( (ffrc(i) && i < numel(ffrc)) && limits(1) ~= 0) % plateau and counting; add a frame
limits(2) = i;
elseif ( (~ffrc(i) || i==numel(ffrc)) && limits(1) ~= 0) % not plateau and counting; the last frame
if (limits(2) - limits(1)) > handles.minLength; % if plateau long enough
plateaux(end).limits = limits; % add to list
plateaux(end+1).limits = [0,0]; % new default range
end
limits = [0,0];
end
end
% erase the last prepared
if numel(plateaux); plateaux(end) = []; end;
% testing if plateaux are under the contrast limit,
% used for initial contrast analysis
if limit
sub = (ydata(l).data < backdoorObj.contrastPlateauDetectionLimit);
dsub = diff(sub);
dsub_s = (find(dsub == 1)+1);
dsub_e = (find(dsub == -1));
if sub(1); dsub_s = [1;dsub_s]; end;
if sub(end); dsub_e = [dsub_e; numel(sub)]; end;
subints = [dsub_s, dsub_e];
for k=size(subints,1):-1:1
if(subints(k,2)-subints(k,1) < backdoorObj.contrastPlateauDetectionLimitLength)
subints(k,:) = []; % erase too short sublimit intervals
else
hsublimplot(end+1).ph = ...
plot(handles.hgraph, subints(k,1):subints(k,2),...
backdoorObj.contrastPlateauDetectionLimit*ones(1,subints(k,2)-subints(k,1)+1),...
'b', 'HitTest', 'off','LineWidth',2);
pos = [ 0.5*(subints(k,1)+subints(k,2)), backdoorObj.contrastPlateauDetectionLimit ];
if (mod(k,2)==0); va='bottom'; else va='top';end;
hsublimplot(end).txt = text( 'Parent', handles.hgraph, 'String', strcat('[',num2str(subints(k,1)),':',num2str(subints(k,2)),']'), ...
'Units', 'data', 'Position', pos, 'Margin', 1,'interpreter', 'latex', 'FontUnits','normalized', ...
'LineStyle','none', 'HitTest','off','FontSize',handles.fitfontsize, 'Color','blue',...
'VerticalAlignment',va, 'HorizontalAlignment','center');
disp(strcat('Low contrast warning: [',num2str(subints(k,1)),',',num2str(subints(k,2)),']'));
end
end
end
% generally fitting plateaux
fitted = ydata(l).data(sf:end-sf); % crop the fits to match data with frames
for p=nextPlateau:numel(plateaux)
lim = plateaux(p).limits;
plateaux(p).avgfrc = mean( fitted(lim(1):lim(2)) );
if handles.thisPlot==1 && limit % if there's limit on contrast
if plateaux(p).avgfrc > backdoorObj.contrastPlateauDetectionLimit;
continue; % proceed to the next plateau
end;
end;
pOnScreen = numel(hfitplot)+1; % make sure there are no gaps in line list
hfitplot(pOnScreen).ph = plot(handles.hgraph, locint(lim(1):lim(2)), plateaux(p).avgfrc*ones(1,lim(2)-lim(1)+1), 'r', 'HitTest', 'off','LineWidth',2);
disp(strcat('Average plateau value [',num2str(locint(lim(1))),',',num2str(locint(lim(2))),']:',num2str(plateaux(p).avgfrc)) );
str = strcat(quant,num2str(round(plateaux(p).avgfrc,rnd)),units);
pos = [ 0.5*(locint(lim(1))+locint(lim(2))), plateaux(p).avgfrc ];
if (mod(pOnScreen,2)==0); va='bottom'; else va='top';end;
hfitplot(pOnScreen).txt = text( 'Parent', handles.hgraph, 'interpreter', 'latex', 'String', str, ...
'Units', 'data', 'Position', pos, 'Margin', 1, 'FontUnits','normalized',...
'LineStyle','none', 'HitTest','off','FontSize',handles.fitfontsize, 'FontWeight','bold','Color','black',...
'VerticalAlignment', va, 'HorizontalAlignment','center');
end
nextPlateau = numel(plateaux)+1;
end
end
end
%% Fitting interval selection
% this is a bit tricky with preserving correct settings for hit test,
% video frame-graph click connection, no freeze etc
% select interval for fitting; make sure to try/catch
function fitint_callback(~,~)
if handles.selecting; % there is a strange interplay between uiwait/waitfor functions ...
warn('select'); % ...this approach is a bit awkward, but fail-safe
return;
else handles.selecting = true;
end;
set([handles.hfitline,handles.hfitexp,handles.hfitplateau],'Enable','off'); % suspend fitting
handles.hgraph.ButtonDownFcn = []; % suppress button-down callback for the graph
handles.hfitint.String = '<HTML><center>Accept<br>Interval</HTML>';
handles.hfitint.Tag = 'wait';
handles.hfitint.Callback = @(src,~)(set(src,'Tag','continue'));
hold(handles.hgraph,'on');
BCfunction = makeConstrainToRectFcn('impoint',get(handles.hgraph,'XLim'),get(handles.hgraph,'YLim'));
Ymid = (handles.hgraph.YLim(2)+handles.hgraph.YLim(1))*0.5;
if isempty(handles.fitInt)
Xlen = handles.hgraph.XLim(2)-handles.hgraph.XLim(1);
XC = [handles.hgraph.XLim(1)+0.25*Xlen, handles.hgraph.XLim(1)+0.75*Xlen];
oldInt = round( [handles.hgraph.XLim(1), 0;...
handles.hgraph.XLim(2), 0] );
else
oldInt = handles.fitInt;
XC = [max(handles.fitInt(1,1),handles.hgraph.XLim(1)),min(handles.fitInt(2,1),handles.hgraph.XLim(2))];
end
intpoint(1) = impoint(handles.hgraph,XC(1),Ymid,'PositionConstraintFcn',BCfunction);
intpoint(2) = impoint(handles.hgraph,XC(2),Ymid,'PositionConstraintFcn',BCfunction);
intpoint(1).addNewPositionCallback(@(pos) fitintNewPosition_callback(pos,1));
intpoint(2).addNewPositionCallback(@(pos) fitintNewPosition_callback(pos,2));
waitfor(handles.hfitint,'Tag','continue');
try
handles.fitInt = round([ intpoint(1).getPosition(); intpoint(2).getPosition() ]);
catch
warn('interrupt',...
'Original interval was restored (or set to default in case of an empty original interval)');
handles.fitInt = oldInt;
end
intpoint(1).delete; % remove points
intpoint(2).delete;
fitintNewPosition_callback(0,0); % remove red lines and assoc. coordinates
set([handles.hfitline,handles.hfitexp,handles.hfitplateau,handles.hplatwidth,handles.hplatthresh,handles.hplatmin],'Enable','on'); % activate fitting buttons
handles.hfitint.String = strcat('<HTML><center>Change<br>[',num2str(round(handles.fitInt(1,1))),',',...
num2str(round(handles.fitInt(2,1))),']</HTML>');
handles.hfitint.Callback = @fitint_callback;
handles.hgraph.ButtonDownFcn = {@getcursor_callback}; % return the the general button callback
set([handles.hfitline,handles.hfitexp,handles.hfitplateau],'Enable','on'); % restore fitting
handles.selecting = false;
end
%% Support function for fitting interval setting
% callback called when impoint gets new position
function fitintNewPosition_callback(coor,var)
nvar = mod(var,2)+1;
persistent hline;
if isempty(hline);
hline = struct('ph',[],'cx',[]);
hline(2) = hline(1);
end;
if(var==0) % remove both; delete the ph graphs
if ~isempty(hline(1).ph); hline(1).ph.delete; end; % delete graphs
if ~isempty(hline(2).ph); hline(2).ph.delete; end;
if ~isempty(hline(1).cx); hline(1).cx = []; end; % remove coordinates
if ~isempty(hline(2).cx); hline(2).cx = []; end;
return;
end
if ~isempty(hline(var).ph); hline(var).ph.delete; end; % delete old line
yl = handles.hgraph.YLim;
hline(var).ph = plot(handles.hgraph, [coor(1), coor(1)], handles.hgraph.YLim, 'r', 'HitTest','off');
hline(var).ph.Tag = 'intbound';
ylim(handles.hgraph,yl);
hline(var).cx = coor(1);
if isempty(hline(nvar).cx); hline(nvar).cx = handles.fitInt(nvar,1); end;
handles.hfitint.String = strcat('<HTML><center><font color="red">Accept<br>[',num2str(round(hline(1).cx)),',',...
num2str(round(hline(2).cx)),']</HTML>');
end
%% Returns position of the cursor click, draws vertical line in the graph
% return coordinates of cursor on the graph; delcall is only deleting
% the marking in the graph
function getcursor_callback(source,~,delcall)
if ~exist('delcall','var'); delcall = false; end; % for full calls set to false
if isempty(handles.thisPlot); return; end; % if there is no plot yet, ignore the call
persistent hline;
persistent hdot;
if ~isempty(hline); hline.delete; end; % delete old selection, if any
if ~isempty(hdot); hdot.delete; end; % delete old selection, if any
if delcall; return; end; % if only delete call, stop here
hold(handles.hgraph,'on');
coor = get(source, 'CurrentPoint');
if (coor(1,1) < handles.hgraph.XLim(1) || coor(1,1) > handles.hgraph.XLim(2)); return; end; % ignore clicks outside the canvas
if (handles.thisPlot == 1 || handles.thisPlot==4 || handles.thisPlot==5)
if handles.thisPlot == 1; % contrast
Ycoor = vidObj.getContrastByFrame(round(coor(1,1)),handles.contype);
elseif handles.thisPlot == 4; % force
Ycoor = BFPobj.getByFrame(round(coor(1,1)),'force');
elseif handles.thisPlot == 5; % metrics
Ycoor = BFPobj.getByFrame(round(coor(1,1)),'metric'); % returns [bead, pipette]
end
yl = ylim(handles.hgraph);
hline = plot(handles.hgraph, [coor(1,1), coor(1,1)], handles.hgraph.YLim, 'r','HitTest','off');
hdot = plot(handles.hgraph, coor(1,1), Ycoor(1),'or','MarkerSize',10, 'LineWidth',2, 'HitTest','off');
ylim(handles.hgraph,yl); % block Y-axis rescaling
if numel(Ycoor)==2;
hdot(2) = plot(handles.hgraph, coor(1,1), Ycoor(2),'or','MarkerSize',10, 'LineWidth',2, 'HitTest','off');
disp( ['Metrics (bead,pipette): [' num2str(round(coor(1,1))),',', num2str(Ycoor(1)),',', num2str(Ycoor(2)),']'] );
else
disp( ['Coordinate: [' num2str(round(coor(1,1))),',', num2str(Ycoor),']'] );
end;
setframe(round(coor(1,1))); % case of contrast, force, metrics
elseif handles.thisPlot == 3
disp( ['Coordinate: [' num2str(coor(1,1)),',', num2str(coor(1,2)),']'] );
hdot = plot(handles.hgraph, coor(1,1),coor(1,2),'or','MarkerSize',10, 'LineWidth',2);
end
handles.hgraph.ButtonDownFcn = {@getcursor_callback};
end
%% Call to generate tracking fidelity report
% displays report of the last tracking, illustrating poorly trackable
% intervals, showing metrics as an overlay
function getreport_callback(~,~)
BFPobj.generateReport();
end
%% Select item to plot
% selection of plotted quantity from drop-down menu
function graphpopup_callback(source,~)
handles.toPlot = source.Value;
end
%% Set frame range of plot
% set the range for plot
function plotrange_callback(source,~,oldval,var)
handles.lowplot = round(str2double(handles.hlowplot.String)); % save the values
handles.highplot = round(str2double(handles.hhighplot.String));
handles.hlowplot.Callback = {@plotrange_callback,handles.lowplot,1}; % reset old values in callback
handles.hhighplot.Callback = {@plotrange_callback,handles.highplot,2};
% revert for incorrect input
if (isnan(handles.lowplot)||isnan(handles.highplot)||handles.lowplot > handles.highplot||handles.lowplot < 1||handles.highplot > vidObj.Frames)
warndlg({'Input values must be numeric, positive, low value smaller than high value';
'Please correct the input and retry'},'Incorrect input', 'replace');
source.String = num2str(oldval);
if (var==1); handles.lowplot = oldval;
else handles.highplot = oldval; end;
return;
end;
end
%% Plotting control function
% plot selected quantity (numbered 1-5), #1 contrast measure (SD2,
% rSD2), #2 tracks (3D) with 3rd axis as time, #3 trajectories (2D), #4
% force (+ right y-axis as deformation), #5 metrics
function graphplot_callback(~,~)
camup(handles.hgraph,'auto');
campos(handles.hgraph,'auto');
rotate3d off;
grid(handles.hgraph,'off');
switch handles.toPlot
case 1 % contrast
reset(handles.hgraph);
handles.fitInt = [handles.lowplot,0;handles.highplot,0];
getcontrast_callback(0,0,'user'); % calls contrast procedure to calculate and plot contrast
case 2 % tracks
if (handles.hgraphpip.Value || handles.hgraphbead.Value)
BFPobj.plotTracks(handles.hgraph,handles.lowplot,handles.highplot,logical(handles.hgraphpip.Value),logical(handles.hgraphbead.Value),'Style','3D'); % call plotting function with lower and upper bound
handles.thisRange = [handles.lowplot, handles.highplot];
grid(handles.hgraph,'on');
camup(handles.hgraph,[-1, -1, 1]);
campos(handles.hgraph,[handles.hgraph.XLim(2),handles.hgraph.YLim(2),handles.hgraph.ZLim(2)]);
hrot = rotate3d(handles.hgraph);
hrot.Enable = 'on';
setAllowAxesRotate(hrot,handles.haxes,false);
else
warndlg('Neither pipetter nor bead tracks selected to be plotted.','Nothing to plot','replace');
return;
end
case 3 % trajectories
if (handles.hgraphpip.Value || handles.hgraphbead.Value)
BFPobj.plotTracks(handles.hgraph,handles.lowplot,handles.highplot,logical(handles.hgraphpip.Value),logical(handles.hgraphbead.Value),'Style','2D'); % call plotting function with lower and upper bound
handles.thisRange = [handles.lowplot, handles.highplot];
else
warndlg('Neither pipetter nor bead tracks selected to be plotted.','Nothing to plot','replace');
return;
end
case 4 % force
BFPobj.plotTracks(handles.hgraph,handles.lowplot,handles.highplot,false,false,'Style','F','Calibration',handles.calibrated);
handles.thisRange = [handles.lowplot, handles.highplot];
if numel(BFPobj.force)~=0;
plotZeroLine(); % plots dashed red line at y=0 to indicate pulling and pushing
end;
case 5 % metrics
if (handles.hgraphpip.Value || handles.hgraphbead.Value)
BFPobj.plotTracks(handles.hgraph,handles.lowplot,handles.highplot,logical(handles.hgraphpip.Value),logical(handles.hgraphbead.Value),'Style','M');
handles.thisRange = [handles.lowplot,handles.highplot];
else
warndlg('Neither pipetter nor bead tracks selected to be plotted.','Nothing to plot','replace');
return;
end
end
handles.thisPlot = handles.toPlot;
handles.hgraph.ButtonDownFcn = {@getcursor_callback};
end
%% Generate LaTeX annotation with stiffness intformation in a new window
% displays information about stiffness in a new window
function stiffinfo_callback(~,~)
boxoptions.Interpreter = 'latex';
boxoptions.WindowStyle = 'modal';
hmb=msgbox(strjoin({'$$k=',num2str(round(BFPobj.k)),'\frac{pN}{\mu m}$$',char(10),...
'$$\Delta k=\pm',num2str(round(BFPobj.Dk)),'\frac{pN}{\mu m}$$'}),...
'Stiffness info', boxoptions);
end
%% Display info about linear equation for RBC stiffness validity
% displays information about reliability of the linear approximation of
% force-extension relation
function lininfo_callback(~,~)
if handles.overLimit
warndlg(strjoin({'The detected extensions of the RBC suggest, that the force-extension',...
'relation might be out of the linear regime. Reliability depends on many parameters,'...
'as a rule of a thumb,',num2str(BFPobj.linearLimit),'microns thershold was chosen.'...
'Linear approximation tends to over-estimate the force, but even for extensions nearing'...
'one micron, error would be around 20%. In such cases, infotext reporting stiffness is'...
'displayed in red.'}),'RBC extension over linear limit','replace');
else
warndlg(strjoin({'The detected extensions of the RBC are within the boundaries of'...
'linear approximation. In such cases, infotext reporting stiffness is displayed'...
'in blue.'}),'RBC extension within linear limit','replace');
end
end
%% Call to start force calculation
% gets parameters for calculation, calculates (& shows) 'k', gets force
% checks if initial probe parameters were modified and issues warning
function runforce_callback(~,~)
% if verbose and geometric parameters were not changed, warn
if (handles.RBCradius == RBC && handles.PIPradius == PIP && handles.CAradius == CON) % nothing measured
if handles.verbose % report
choice = questdlg(strjoin({'This action runs force calculation. The force, however,',...
'must be calibrated (i.e. stiffness ''k'' calculated) using experiment settings dependent parameters',...
'adjustable in ''Experimental parameters'' panel. Initially, it contains only order of magnitude',...
'values, to give an idea of force time dependence. If You want to have results properly',...
'calibrated for Your experiment, please review these values before the calculation.'}),...
'Parameters for force calculation', 'Review', 'Proceed', 'Review');
if (strcmp(choice,'Review')||strcmp(choice,'')); % cancel means no action
return;
end;
end;
handles.calibrated = false; % mark not calibrated and continue
else
handles.calibrated = true; % likely calibration occured; set force to calibrated
end
BFPobj.getParameters(handles.RBCradius, handles.CAradius, handles.PIPradius, handles.pressure);
handles.stiffness = BFPobj.k;
handles.overLimit = BFPobj.getForce(handles.hgraph, handles.calibrated);
handles.hlinearinfo.Enable = 'on';
handles.hstiffbtn.Enable = 'on';
handles.toPlot = 4;
handles.thisPlot = 4;
handles.hgraphitem.Value = handles.thisPlot;
handles.lowplot = max(handles.hgraph.XLim(1),1);
handles.highplot = min(handles.hgraph.XLim(2),BFPobj.maxFrame);
handles.thisRange = [handles.lowplot,handles.highplot];
handles.hlowplot.String = num2str(handles.lowplot);
handles.hhighplot.String = num2str(handles.highplot);
tmplines = findobj(handles.hgraph,'type','line');
if ~isempty(tmplines)
plotZeroLine(); % mark line of zero force
end
makeStiffAnot(); % display RBC stiffness info
handles.hgraph.ButtonDownFcn = {@getcursor_callback};
end
%% Call to start bead and pipette tracking accross pre-selected intervals
% runs tracking procedure
function runtrack_callback(~,~)
BFPobj.Track(handles.hgraph); % run tracking
handles.hgraph.ButtonDownFcn = {@getcursor_callback}; % reset callback
set([handles.hrunforce,handles.hgraphplot,handles.hlowplot,handles.hhighplot,...
handles.hgraphbead,handles.hgraphpip, handles.hgraphitem,handles.hreport],'Enable','on');
handles.toPlot = 2;
handles.thisPlot = 2;
handles.lowplot = max(BFPobj.minFrame,1);
handles.highplot = BFPobj.maxFrame;
handles.thisRange = [handles.lowplot,handles.highplot];
handles.hlowplot.String = num2str(handles.lowplot);
handles.hhighplot.String = num2str(handles.highplot);
handles.hgraphitem.Value = handles.thisPlot;
end
%% Update BFPClass object containing tracking and force settings
% procedure to create BFPClass object, which performs all the
% calculations; if object exist, it is overwritten with settings
% currently selected in the GUI
function update_callback(~,~)
BFPobj = BFPClass(vidObj.Name, vidObj ,handles.intervallist);
BFPobj.getBeadParameters(handles.beadradius,handles.beadbuffer,handles.beadsensitivity,handles.beadgradient,handles.beadmetricthresh,handles.P2M);
BFPobj.getPipParameters(handles.pipmetricthresh, handles.contrasthresh, handles.pipbuffer, handles.dilate);
set([handles.hruntrack,handles.hgenfilm],'Enable','on');
end
%% Add an interval to the set of tracking intervals
% adds currently defined interval to the list of intervals
% runs mady integrity checks, communicates issues to the user, performs
% corrections and even runs small functions to get user input;
% Note that this is quite complex and cornerstone function. It manages
% the interval addition in a way the final interval list retains
% integrity and the tracking results are then clear and meaningful.
function addinterval_callback(~,~)
% make input interval copy, to track corrective changes
origint = [];
origint = strucopy(origint,handles.interval);
extracalibration = false;
% check if initial frame of the interval is frame of origin of the
% pipette pattern. This test is important to clarify, if the
% pipette patter selected matches the pattern of the interval
if ( handles.interval.frames(1) ~= handles.tmppatframe && handles.interval.frames(1) ~= handles.updpatframe)
initrackdlg = warndlg({'Selected pipette pattern does not originate at the first frame of the proposed interval';...
'Program will attempt to search the pattern in the frame, to verify its contrast compatibility and autoset the initial coordinates for search in this interval.'},...
'Remote pipette pattern','replace');
waitfor(initrackdlg); % wait for user to read the message
[ position, ~ ] = TrackPipette( vidObj, handles.interval.pattern, [-1 -1], [handles.interval.frames(1) handles.interval.frames(1)] );
% draw recognized pattern on the interval first frame
hold(handles.haxes,'on');
setframe(handles.interval.frames(1));
patrect = rectangle('Parent', handles.haxes, 'Position', ...
[position(2), position(1), size(handles.interval.pattern,2), size(handles.interval.pattern,1)],...
'EdgeColor','r','LineWidth', 2 );
hold(handles.haxes,'off');
% ask user to accept or discard
choice = questdlg({strjoin({'Program attempted to localize the selected pattern in the first frame of the interval.',...
'The pattern is highlighted in red. If the position is correct, please accept the suggestion.',...
'If pattern is gravely misplaced, the program was unable to localize it.',...
'This is almost certainly caused by contrast incopatibility between the pattern You selected and its appearance in this interval.',...
'Either try to select a compatible patter or remove the interval from tracking'})},...
'Pipette initial coordinate autodetection', 'Accept', 'Cancel', 'Accept');
% explanation: The selected pipette pattern originates in
% a frame outside the interval being added. The algorithm tries
% to localize the pattern in the initial frame of the interval.
% To assure the resulting force is compatible, we need to use
% the same anchor and the same reference frame. If the pipette
% pattern is already in another interval, the program will keep
% its 'referece' and 'anchor'. If it is added from the pattern
% list, program will copy the 'anchor' and the frame of origin
% as a default 'reference'.
switch choice
case 'Accept'
handles.interval.patcoor = [position(2), position(1)]; % save the recorded position in the frame
handles.hpatternint.String = strcat('[',num2str(round(handles.interval.patcoor(1))),',',num2str(round(handles.interval.patcoor(2))),...
';',num2str(handles.interval.frames(1)),']');
handles.tmppatframe; % original frame of the pattern is kept, hidden;
handles.updpatframe = handles.interval.frames(1); % but information about update is kept;
case {'Cancel',''} % catch here also cancellation
handles.interval.patcoor = [];
handles.interval.pattern = [];
handles.hpatternint.String = '[.,.;.]';
patrect.delete; % remove rectangle
return; % cancel adding interval
end
patrect.delete;
end
% verify if bead initial coordinate originates in the initial frame
% - unlike pattern, for bead, this is obligatory
if ( handles.interval.frames(1) ~= handles.tmpbeadframe )
warndlg({strjoin({'Initial frame of the interval does not match the frame of origin of initial bead coordinates.',...
'The initial bead coordinate must be specified for the interval initial frame.'});...
'Please make the necessary corrections and try again.'},...
'Bead frame mismatch', 'replace');
return;
end;
% verify all the necessary fields exist and are filled
for f=1:numel(intfields);
if (~isfield(handles.interval,intfields{f}) || isempty(handles.interval.(intfields{f})))
warndlg(strcat('The field',{' '}, intfields{f}, ' is missing or empty. Provide all the required information.'),...
'Field missing','replace');
return;
end
end
% verify the values of the interval and refframes
if ~(isinvideo(handles.interval.frames(1)) && isinvideo(handles.interval.frames(2)) && isinvideo(handles.interval.reference))
warndlg(strjoin({strcat('One or more of the specified frames, first frame (', num2str(handles.interval.frames(1)),'), ',...
'or last frame (', num2str(handles.interval.frames(2)),'), of the interval,'),...
strcat('or the reference frame (', num2str(handles.interval.reference), ')'),...
strcat('have incorrect value, or value out of bounds of the video: [1,',num2str(vidObj.Frames),'].'),...
'Please review the values and try to add the interval again.'}));
return;
end
% verify intervals do not overlap
if numel(handles.intervallist) > 0;
review = false; % flag for abort and review, if intervals partially overlap
modified = false;
for i=1:numel(handles.intervallist)
if (handles.interval.frames(1) < handles.intervallist(i).frames(2) &&...
handles.interval.frames(2) > handles.intervallist(i).frames(1))
old = [handles.interval.frames(1), handles.interval.frames(2)]; % save original timespan
if handles.interval.frames(1) >= handles.intervallist(i).frames(1);
handles.interval.frames(1) = max(handles.interval.frames(1),handles.intervallist(i).frames(2));
modified = ~modified; % toggle
end
if handles.interval.frames(2) <= handles.intervallist(i).frames(2)
handles.interval.frames(2) = min(handles.interval.frames(2),handles.intervallist(i).frames(1));
modified = ~modified;
end
handles.hstartint.String = num2str(handles.interval.frames(1));
handles.hendint.String = num2str(handles.interval.frames(2));
if modified; % only one limit was changed, i.e. intervals are not mutual subsets
warndlg({strcat('Added interval [',num2str(old(1)),',',num2str(old(2)),...
'] overlaps with another existing interval, [', num2str(handles.intervallist(i).frames(1)),',',...
num2str(handles.intervallist(i).frames(2)),'].');...
strcat('Please note intervals should be exclusive. New interval was modified to [',...
num2str(handles.interval.frames(1)),',',num2str(handles.interval.frames(2)),']. Please review and submit again.')},...
'Overlapping intervals','replace');
review = true;
else
warndlg({strcat('Added interval [',num2str(old(1)),',',num2str(old(2)),...
'] is subset or superset of another existing interval, [', num2str(handles.intervallist(i).frames(1)),',',...
num2str(handles.intervallist(i).frames(2)),']. Please review.')},...
'Duplcite intervals', 'replace');
handles.interval.frames = old; % reset original values
handles.hstartint.String = old(1);
handles.hendint.String = old(2);
return; % if interval is subset of another interval, abort immediatelly
end
end
end
if review; return; end; % after overlaps are sorted out, let user review
end
% verify reference frame is part of the interval being added; in
% case reference is external, issue contrast change risk warning;
% the pipette must be local for this interval
if ( handles.interval.reference >= handles.interval.frames(1) && handles.interval.reference <= handles.interval.frames(2) ) && ...
( handles.tmppatframe >= handles.interval.frames(1) && handles.tmppatframe <= handles.interval.frames(2) );
passed = true; % eligible reference frame; reference and pattern originate in the interval
elseif ( handles.interval.reference >= handles.interval.frames(1) && handles.interval.reference <= handles.interval.frames(2) )
hw = warndlg({'The reference frame originates in this interval, but the pipette pattern originates in another interval.';...
'It is allowed, but make sure the tracked pipette pattern has a compatible contrast and can be recognized in this interval.'},...
'External frame of pipette pattern','replace');
passed = false; % so far uneligible, run further tests
uiwait(hw);
elseif ( handles.tmppatframe >= handles.interval.frames(1) && handles.tmppatframe <= handles.interval.frames(2) )
hw = warndlg({'The pipette pattern originates in this interval, but the reference frame does not belong to the added interval.';...
'It is allowed, but make sure the frame of reference has a compatible contrast and uses the same pipette pattern as in this interval.'},...
'External frame of reference','replace');
passed = false; % so far uneligible, run further tests
uiwait(hw);
else
hw = warndlg({'The reference frame does not belong to the added interval and neither does pipette pattern originate in the interval.';...
'It is allowed, but make sure the frame of reference has a compatible contrast and uses the same pipette pattern as in this interval.';...
'The same goes for the pipette pattern, make sure it has a compatible contrast and can be recognized in this interval.'},...
'External reference frame','replace');
passed = false; % so far uneligible, run further tests
uiwait(hw);
end;
% compare the reference and anchor used with the data at the
% intervals already added. If one pattern with two different
% settings was forced into the list, the result will be a horrible
% mess I do not want to fix. This is not GTA to give people insane
% stunt bonus.
if ~passed
[ rframe, ranchor,~ ] = ... % validate; as validation is with confirmation, suppress warning message (the last passed variable)
validatepattern(handles.interval.pattern,handles.interval.reference,handles.interval.patsubcoor,true);
if (rframe == handles.interval.reference) && all(ranchor == handles.interval.patsubcoor); % let pass
else
if (rframe ~= handles.interval.reference)
strref = strjoin({'The reference frame (i.e. zero-strain frame) You chose,',...
num2str(handles.interval.reference),',is different from the reference frame',...
'previously chosen for the identical pipette pattern,', num2str(rframe),'.'});
else
strref = '';
end
if any(ranchor ~= handles.interval.patsubcoor)
stranch = strjoin({'The anchor point You chose,'...
strcat('[',num2str(round(handles.interval.patsubcoor(1))),',',num2str(round(handles.interval.patsubcoor(2))),']'),...
',is different from the anchor point previously chosen for the identical pipette pattern,',...
strcat('[',num2str(round(ranchor(1))),',',num2str(round(ranchor(2))),'].')});
else
stranch = '';
end
choice = questdlg(strjoin({strref,char(10), stranch,char(10),...
'This may be an unnecesasry warning, but please make sure,',...
'that the pattern, reference frame and anchor are compatible across the intervals,',...
'in order to get compatible force readings across the intervals.'}),...
'Incompatibility between intervals','Keep current','Review','Use previous','Keep current');
switch choice
case 'Keep current'; % let pass
case 'Use previous'; % reset to previously used values
handles.interval.reference = rframe;
handles.interval.patsubcoor = ranchor;
case {'Review',''} % make corrections, catch also cancel btn
return;
end
end
end
% finally, if reference frame be not part of any analysed interval,
% add the calibration frame as a single-frame interval, before
% proceeding with the major addition of user defined interval
[ ~,~, absent ] = (validatepattern(handles.interval.pattern,handles.interval.reference,handles.interval.patsubcoor,true));
if absent && ... % if the pattern is absent in other intervals and also in the currently added
(handles.interval.reference < handles.interval.frames(1) || handles.interval.reference > handles.interval.frames(2));
oldframe = vidObj.CurrentFrame; % save the currently set frame
handles.calibint = [];
handles.calibint = strucopy(handles.calibint,handles.interval); % pattern, patsubcoor, reference are preserved
handles.calibint.frames = [handles.interval.reference,handles.interval.reference];
handles.calibint.beadcoor = [];
handles.calibint.contrast = [];
hcalibfig = buildCalibFig(); % call function to construct single-frame calibration figure
waitfor(hcalibfig);
setframe(oldframe); % reset the original frame
if ~isempty(handles.calibint)
if ~isempty(handles.calibint.beadcoor) && ~isempty(handles.calibint.contrast)
handles.intervallist = strucopy(handles.intervallist,handles.calibint);
extracalibration = true;
% warn('Calibration single-frame interval was successfully added.');
else
warndlg(strjoin({'Selected reference (zero strain) frame,', num2str(handles.interval.reference),...
', does not belong to any present interval, neither to interval being added.',...
'The attempt to add calibration frame failed, probably due improper bead selection.',...
'The adding will now abort.'}),...
'Unaccessible reference point', 'replace');
return;
end
else
warndlg(strjoin({'Selected reference (zero strain) frame,', num2str(handles.interval.reference),...
', does not belong to any present interval, neither to interval being added. ',...
'The attempt to add calibration frame failed, probably due pipette uncompliance.',...
'The adding will now abort.'}),...
'Unaccessible reference point', 'replace');
return;
end
end
% if all checks are passed, add interval to the list and report to
% the table of intervals;
handles.intervallist = strucopy(handles.intervallist,handles.interval);
makeTab(); % call external function to generate the table from the intervallist
% generate addition report
% fields { 'pattern', 'patcoor', 'beadcoor', 'patsubcoor', 'contrast','reference','frames' }
strep = 'Following semi-automatic changes were made:\n\n';
none = true;
for f=1:numel(intfields);
repline = [];
if ~isequal(handles.interval.(intfields{f}),origint.(intfields{f}))
none = false;
switch intfields{f}
case 'pattern'
repline = '\n Different pattern was used.';
case {'patcoor', 'beadcoor', 'patsubcoor', 'frames'}
switch intfields{f}
case 'patcoor'
name = 'Pattern coordinate';
case 'beadcoor'
name = 'Bead coordinate';
case 'patsubcoor'
name = 'Anchor point';
case 'frames'
name = 'Frame interval';
end
repline = strjoin({'\n', name, 'was changed from the old value [',...
num2str(round(origint.(intfields{f})(1))),',',num2str(round(origint.(intfields{f})(2))),']',...
'to the new value of [',...
num2str(round(handles.interval.(intfields{f})(1))),',',num2str(round(handles.interval.(intfields{f})(2))),']'});
case 'contrast'
repline = strjoin({'\n','Contrast was switched from',origint.(intfields{f}),...
'to',handles.interval.(intfields{f})});
case 'reference'
repline = strjoin({'\n','The frame of reference was changed from',...
num2str(round(origint.(intfields{f}))),'to',num2str(round(handles.interval.(intfields{f})))});
end
end
if ~isempty(repline); strep = sprintf(strcat(strep,repline)); end;
end
if extracalibration;
strep = sprintf(strjoin({strep,'\n','Single-frame calibration interval was added.'}));
none = false;
end
if none; strep='Interval was added without any further modifications.'; end;
msgbox(strep,'Interval addition report','modal');
% clear interval to be reused; clear UI data; clear temp. variables
% reset tracking interval to fault-free combination
handles.interval = struct('frames',[round(vidObj.CurrentFrame),round(vidObj.Frames)]); % rounding might not be necessary
set(handles.hstartint, 'String', num2str(handles.interval.frames(1)),...
'Callback',{@setintrange_callback,handles.interval.frames(1),1}); % reset range and callback parameters
set(handles.hendint, 'String', num2str(handles.interval.frames(2)),...
'Callback',{@setintrange_callback,handles.interval.frames(2),2});
handles.hrefframe.String = [];
handles.hpatternint.String = '[.,.;.]';
handles.hbeadint.String = '[.,.;.]';
handles.hpatsubcoor.String = '[.,.]';
handles.updpatframe = 0;
handles.tmppatframe = [];
% disable the buttons
handles.hgetpattern.Enable = 'off';
handles.hselectpat.Enable = 'off';
handles.hshowpattern.Enable = 'off';
handles.hgetpatsubcoor.Enable = 'off';
handles.haddinterval.Enable = 'off';
handles.hgetrefframe.Enable = 'off';
% enable Update button
handles.hupdate.Enable = 'on';
end
%% Marks an interval in the interval list for removal
% selects table lines (intervals from intervallist) to be removed
function rmtabledint_callback(hT, data)
row = data.Indices(1);
col = data.Indices(2);
if data.EditData % selected for removal
handles.remove(end+1) = row; % get selected row for removal
hT.Data{row,col} = true;
else
[is,ind] = find(handles.remove==row);
if is; handles.remove(ind) = []; end; % remove the index
hT.Data{row,col} = false;
end
if numel(handles.remove) > 0; handles.heraseint.Enable = 'on';
else handles.heraseint.Enable = 'off'; end
end
%% Call to erase selected intervals from the list
% removes all selected entries from the interval list and the table
function eraseint_callback(~,~)
if numel(handles.remove) > 0;
handles.remove = sort(handles.remove,'descend'); % the elements are deleted from the end
for ind=1:numel(handles.remove) % remove intervals from the list
handles.intervallist(handles.remove(ind)) = [];
end
handles.remove = []; % erase remove list
handles.heraseint.Enable = 'off'; % switch off the button
makeTab(); % remake table of intervals
end
end
% allows to select the pattern anchor
function getpatsubcoor_callback(~,~)
if ~isfield(handles.interval,'pattern') || isempty(handles.interval.pattern);
warndlg('Choose a pipette pattern first. Then select the anchor.','No pipette pattern selected','replace');
return;
end;
choice = questdlg(strjoin({'The anchor point defines a precise coordinate point on the pipette to',...
'calculate red blood cell extension. The anchor is determined when pattern is initially',...
'selected. If the same pattern (with the same time frame of reference distance) is used',...
'in several intervals, it is necessary to keep the same anchor, in order to have comparable',...
'calculated force across the intervals. Changing the anchor for one of the intervals is possible,',...
'but keep in mind the results from different intervals may not be mutually compatible.'}),...
'Anchor change', 'Proceed', 'Cancel', 'Cancel');
if (strcmp(choice, 'Cancel') || strcmp(choice,'')) % catch cancel btn
return;
end
inPattern = handles.interval.pattern; % input pattern
hpatfig = figure; % create new figure with a button, displaying the pattern
hpataxes = axes('Parent',hpatfig, 'Units','normalized','Position',[0,0.2,1,0.8]);
imagesc(handles.interval.pattern, 'Parent',hpataxes);
colormap(gray);
axis(hpataxes,'image');
haccept = uicontrol('Parent',hpatfig, 'Style', 'pushbutton', 'String', 'Accept',...
'Units','normalized','Enable','off','Position',[0.2,0,0.2,0.15],'Callback','uiresume(gcbf)');
BCfunction = makeConstrainToRectFcn('impoint',get(hpataxes,'XLim'),get(hpataxes,'YLim'));
beadpoint = impoint(hpataxes,'PositionConstraintFcn', BCfunction);
try
haccept.Enable = 'on'; % enable 'accept' btn
uiwait(gcf); % wait for acceptance (only one button)
subcoor = (beadpoint.getPosition); % read the value
if isequal(inPattern,handles.interval.pattern) % check if the pattern was not changed in the meantime
handles.interval.patsubcoor = subcoor; % save new selected value
handles.hpatsubcoor.String = strcat('[',num2str(round(subcoor(1))),','...
,num2str(round(subcoor(2))),']'); % update the string
else
warn('The pattern was changed during anchor selection process.',...
'No changes were made.');
end
catch
warn('interrupt','Original value of anchor was kept, any possible input discarded.');
end
beadpoint.delete; % delete the impoint object
if isgraphics(hpatfig); close(hpatfig); end; % close the figure (if not closed)
end
%% Set the video frame to the first frame of current interval
% set current frame to the first frame of the interval; useful when
% selecting initial bead position
function gotointframe_callback(~,~)
if isempty(handles.interval) || ~isfield(handles.interval,'frames') ||...
isempty(handles.interval.frames) || handles.interval.frames(1) == 0;
setframe(vidObj.CurrentFrame);
handles.hstartint.String = num2str(vidObj.CurrentFrame);
handles.interval.frames(1) = vidObj.CurrentFrame;
else
setframe(handles.interval.frames(1));
end
end
%% Get the selected bead in the list as the bead for current interval
% saves the currently open bead coor for this interval to track
function getintbead_callback(~,~)
% check if there is a bead in the list to add
if numel(handles.beadlist)==0 || isempty(handles.beadlist(handles.hbeadinilist.Value));
warn('No bead has been added to the list');
return;
end;
val = handles.hbeadinilist.Value;
if handles.interval.frames(1) ~= handles.beadlist(val).frame
choice = questdlg(strjoin({'The frame of origin of the selected bead',...
'does not match the initial frame of the interval. You can update the initial frame,',...
'cancel and choose another bead from the list, or select the bead directly on the screen.'}),...
'Frame mismatch','Update','Cancel','Select','Update');
switch choice
case 'Update'
handles.hstartint.String = num2str(handles.beadlist(val).frame);
handles.interval.frames(1) = handles.beadlist(val).frame;
case {'Cancel',''} % catch cancel (X) button
return;
case 'Select'
getpoint_callback(handles.hselectbead,0,'interval');
return;
end
end
handles.bead = handles.beadlist(val);
handles.interval.contrast = handles.beadlist(val).contrast;
handles.interval.beadcoor = handles.beadlist(val).coor;
handles.tmpbeadframe = handles.beadlist(val).frame;
handles.hbeadint.String = strcat('[',num2str(round(handles.interval.beadcoor(1))),',',num2str(round(handles.interval.beadcoor(2))),...
';',num2str(handles.tmpbeadframe),';',handles.interval.contrast,']');
handles.hgetpattern.Enable = 'on';
handles.hselectpat.Enable = 'on';
end
%% Get selected pipette pattern from the list for the current interval
% saves the currently open pattern for this interval to track
function getintpat_callback(~,~)
% check if there's a pattern in the list to add
if numel(handles.patternlist)==0 || isempty(handles.patternlist(handles.hpatternlist.Value))
warn('No pipette pattern in the list');
return;
end
val = handles.hpatternlist.Value;
handles.pattern = handles.patternlist(val); % set last added pattern
handles.tmppatframe = handles.patternlist(val).frame;
handles.updpatframe = 0; % if pattern was matched to interval in the meantime, reset
handles.interval.pattern = handles.patternlist(val).cdata;
handles.interval.patcoor = handles.patternlist(val).coor;
[handles.interval.reference, handles.interval.patsubcoor,~] = ...
validatepattern(handles.patternlist(val).cdata, handles.tmppatframe, handles.patternlist(val).anchor);
handles.hrefframe.String = num2str(handles.interval.reference);
handles.hpatsubcoor.String = strcat('[',num2str(round(handles.interval.patsubcoor(1))),','...
,num2str(round(handles.interval.patsubcoor(2))),']');
handles.hpatternint.String = strcat('[',num2str(round(handles.interval.patcoor(1))),',',num2str(round(handles.interval.patcoor(2))),...
';',num2str(handles.tmppatframe),']');
handles.hgetpatsubcoor.Enable = 'on';
handles.hshowpattern.Enable = 'on';
handles.haddinterval.Enable = 'on';
handles.hgetrefframe.Enable = 'on';
end
%% Display selected pipette pattern
% displays the pattern selected for the given interval
function [hpatfig,hpataxes] = showintpattern_callback(~,~)
if ~isfield(handles.interval,'pattern') || isempty(handles.interval.pattern);
warn('Nothing to show. Select a pipette pattern first');
return;
end;
hpatfig = figure; % open new figure
hpataxes = axes('Parent',hpatfig);
imagesc(handles.interval.pattern, 'Parent',hpataxes); % display image (imagesc scales also intensity range)
colormap(gray); % grayscale image
axis(hpataxes,'image');
if isfield(handles.interval,'patsubcoor')
viscircles(hpataxes,handles.interval.patsubcoor,1,'EdgeColor','b');
end;
end
%% Attempts to correctly obtain the reference distance frame
% get reference frame for the currently selected pattern
function getrefframe_callback(~,~)
if (~isfield(handles.interval,'pattern') || ...
isempty(handles.interval.pattern));
warn('Please select the pipette pattern tip first.','Reference frame is pattern-specific');
return;
end;
[handles.interval.reference, handles.interval.patsubcoor,~] = ... % validate the pattern
validatepattern(handles.interval.pattern, handles.interval.reference, handles.interval.patsubcoor);
handles.hrefframe.String = num2str(handles.interval.reference);
handles.hpatsubcoor.String = strcat('[',num2str(round(handles.interval.patsubcoor(1))),','...
,num2str(round(handles.interval.patsubcoor(2))),']');
end
%% Read input reference dist frame from edit field
% set reference frame, where the RBC is not strained
function setrefframe_callback(source,~,oldval)
val = round(str2double(source.String));
if (isnan(val) || val < 1 || val > vidObj.Frames)
warndlg({'Input must be a positive number within the video limits.';'Input is rounded.'},...
'Incorrect input','replace');
source.String = num2str(oldval);
handles.interval.reference = []; % set to non-input
return;
end
handles.interval.reference = val;
set(handles.hrefframe,'String', num2str(val), 'Callback', {@setrefframe_callback,val});
end
%% Frame range of the interval selection
% set the range of frames to track
function setintrange_callback(source,~,oldval,num)
in = str2double(source.String);
% check if input is a valid number
if (isnan(in) || in < 1 || in > vidObj.Frames)
warndlg({'Input must be a positive number within the video limits.';'Input is rounded.'},...
'Incorrect input', 'replace');
source.String = num2str(oldval);
handles.interval.frames(num) = oldval;
return;
end
low = round(str2double(handles.hstartint.String));
high = round(str2double(handles.hendint.String));
% check if interval is at least one frame
if (low > high)
warndlg('The input range is empty. Original value will be reset.',...
'Incorrect input','replace');
source.String = num2str(oldval);
handles.interval.frames(num) = oldval;
return;
end
handles.interval.frames = [low,high];
handles.hgetbead.Enable = 'on';
% all tests passed, update callback arguments for UIs
handles.hstartint.Callback = {@setintrange_callback,low,1};
handles.hendint.Callback = {@setintrange_callback,high,2};
end
%% Measuring geometric properties of the probe
% measure radius of the pipette, contact, scale by drawing a line
function measureLength_callback(source,~,type)
if handles.selecting;
warn('select');
return;
else handles.selecting = true;
end;
microns = 5;
if strcmp(type,'scale');
micronstr = inputdlg(strjoin({'This function allows You to calibrate pixel-to-micron ratio',...
'directly on the video image. You can draw a line across an object of known dimensions',...
'(e.g. scalebar, bead, etc.). Please note that this information is directly available',...
'from Your microscopy setup as a precise number; measuring it this way will yield poorer',...
'precision. If You wishto proceed, input the length of the line in microns:'}),...
'Video scale calibration',1,{num2str(microns)});
if isempty(micronstr);
handles.selecting = false;
return;
end
microns = str2double(micronstr);
if isnan(microns) || microns <= 0
warn('The input must be a positive number of type double.');
handles.selecting = false;
return;
end
elseif handles.verbose;
msgOptions.Interpreter = 'Latex';
msgOptions.WindowStyle = 'modal';
msgbox(strjoin({'Select the {\bfseries diameter} of the', type,...
'. The measured length will be converted into radius.'}),...
'Radius measurement', msgOptions);
end
BCfunction = makeConstrainToRectFcn('imline',get(handles.haxes,'XLim'),get(handles.haxes,'YLim'));
line = imline(handles.haxes,'PositionConstraintFcn',BCfunction);
source.String = 'Confirm';
source.Callback = 'uiresume(gcbf)';
uiwait(gcf);
try
LineEnds = line.getPosition;
line.delete;
length_ = norm( LineEnds(1,:)-LineEnds(2,:) );
if strcmp(type,'pipette')
handles.PIPradius = 0.5*length_*handles.P2M;
handles.hPIPrad.String = num2str(round(handles.PIPradius,2));
elseif strcmp(type,'contact')
handles.CAradius = 0.5*length_*handles.P2M;
handles.hCArad.String = num2str(round(handles.CAradius,2));
elseif strcmp(type,'scale')
handles.P2M = microns/length_;
handles.hP2M.String = num2str(round(handles.P2M,2));
end
catch
warn('interrupt',...
strjoin({'The',type,'length detection failed, no changes were made.'}));
end
source.Callback = {@measureLength_callback,type}; % reset callback
if strcmp(type,'scale');
str='<HTML><center>Pixel to<br>micron:</HTML>';
else
Type = type;
Type(1) = upper(type(1));
str = strcat('<HTML><center>',Type,'<br>radius:</HTML>'); % reset string
end;
source.String = str;
handles.selecting = false;
end
%% Measuring the RBC radius
% detects the RBC and measures its radius, using the TrackBead method;
% TODO: make sure pixel to micron ratio is considered. If the RBC is
% too large (or small) in the video, it wouldn't be detected
function measureRBC_callback(source,~)
if handles.selecting; % make sure only one selection runs at a time
warn('select');
return;
else handles.selecting = true; % set handles.selecting flag
end;
BCfunction = makeConstrainToRectFcn('impoint',get(handles.haxes,'XLim'),get(handles.haxes,'YLim'));
RBCpoint = impoint(handles.haxes,'PositionConstraintFcn',BCfunction);
source.String = 'Confirm';
source.Callback = 'uiresume(gcbf)';
uiwait(gcf);
try % this type of selection is very error-prone, if user doesn't follow the instructions
RBCinicoor = (RBCpoint.getPosition);
RBCframe = round(vidObj.CurrentFrame);
RBCcontrast = questdlg('Does the red blood cell appear bright or dark?',...
'RBC contrast','bright','dark','bright');
if all(~strcmp(RBCcontrast,{'bright','dark'})) % (X) case
RBCpoint.delete; % delete point
source.Callback = {@measureRBC_callback}; % restore callback
source.String = '<HTML><center>RBC<br>radius:</HTML>';
handles.selecting = false;
warn('RBC measurement interrupted by user');
return;
end
radrange = [2,3.5]/handles.P2M; % 2-3 microns radius in pixels
[RBCcoor,RBCradius_,RBCmet,~] = TrackBead(vidObj,RBCcontrast,RBCinicoor,[RBCframe,RBCframe],...
'radius',radrange, 'sensitivity',0.95,'edge',0.1);
if ( RBCradius_==0 ) % nothing detected
warn(strjoin({'The RBC detection failed, no valid result returned.',...
'Make sure the cell is well visible, with clear edge (if possible)',...
'in the frame of selection, and try again.'}));
else
if RBCmet < handles.beadmetricthresh
warn(strjoin({'The RBC was detected, but the strength of the detection',...
'is rather low, with',num2str(round(RBCmet,2)),'below the threshold of',...
num2str(round(handles.beadmetricthresh,2)),'Review, if the detection appears correct.'}));
end;
hRBCshow = viscircles(handles.haxes,[RBCcoor(2),RBCcoor(1)],RBCradius_);
found = questdlg('Was the RBC detected correctly?','Confirm RBC detection','Accept','Cancel','Accept');
if strcmp(found, 'Accept') % cancel and (X) are dropped
handles.RBCradius = RBCradius_*handles.P2M;
handles.hRBCrad.String = num2str(round(handles.RBCradius,2));
end
end
catch
warn('interrupt');
end
RBCpoint.delete;
source.Callback = {@measureRBC_callback}; % restore callback
source.String = '<HTML><center>RBC<br>radius:</HTML>';
pause(2); % wait, so the user can see the RBC outline
if exist('hRBCshow','var'); hRBCshow.delete; end; % delete the RBC outline (if created)
handles.selecting = false; % remove handles.selecting flag
end
%% Read and set input experimental values
% set experimental parameters; validate input
function setexpdata_callback(source,~,oldval)
input = str2double(source.String);
if isnan(input) || input < 0
source.String = num2str(oldval);
warndlg('Input must be a positive number of type double','Incorrect input', 'replace');
return;
end
handles.pressure = str2double(handles.hpressure.String);
handles.RBCradius = str2double(handles.hRBCrad.String);
handles.PIPradius = str2double(handles.hPIPrad.String);
handles.CAradius = str2double(handles.hCArad.String);
handles.P2M = str2double(handles.hP2M.String);
source.Callback = {@setexpdata_callback,input};
end
%% Pipette erode/dilate parameters
% sets number of pixels to erode of dilate the pipette pattern to retry
% detection and improve metrics. Note this is very constly and works
% only for lower correlations (efficiently), there is can make a 10 %
% difference
function dilate_callback(~,~)
erode = round(str2double(handles.herode.String)); % intergers required (full pixels)
dilate = round(str2double(handles.hdilate.String));
if (isnan(erode) || isnan(dilate) || erode < 0 || dilate < 0 ) % abort if input is incorrect
warndlg('Input must be a non-negative number of type double', 'Incorrect input', 'replace');
handles.herode.String = num2str(handles.dilate(1));
handles.hdilate.String = num2str(handles.dilate(2));
return;
end
handles.dilate(1) = erode;
handles.dilate(2) = dilate;
end
%% Pipette buffer
% set pipette tracking grace period
function pipbuffer_callback(source,~)
val = str2double(source.String);
if isnan(val) || val < 0
warndlg({'The grace period must be a positive number.';'Non-integer input is rounded.'},...
'Incorrect input', 'replace');
source.String = num2str(handles.pipbuffer);
return;
end
handles.pipbuffer = round(val);
source.String = num2str(handles.pipbuffer);
end
%% Setting thresholds for tracking
% set contrast threshold value
function pipcontrast_callback(source,~)
handles.contrasthresh = source.Value;
handles.hcontrasthreshtxt.String = {'Contrast';strjoin({'thresh:', num2str(round(handles.contrasthresh,2))})};
end
% set correlation threshold for the pipette pattern detection
function pipmetric_callback(source,~)
handles.pipmetricthresh = source.Value;
handles.hcorrthreshtxt.String = {'Correlation';strjoin({'thresh:', num2str(round(handles.pipmetricthresh,2))})};
end
% set detection metric threshold, values are usually between (0,2)
function beadmetric_callback(source,~)
handles.beadmetricthresh = source.Value;
handles.hmetrictxt.String = {'Metric'; strjoin({'thresh:', num2str(round(handles.beadmetricthresh,2))})};
end
% set circle detection gradient threshold
function beadgrad_callback(source,~)
handles.beadgradient = source.Value;
handles.hgradtxt.String = {'Gradient: '; num2str(round(handles.beadgradient,2))};
end
% set circle detection sensitivity
function beadsensitivity_callback(source,~)
handles.beadsensitivity = source.Value;
handles.hsensitivitytxt.String = {'Sensitivity: '; num2str(round(handles.beadsensitivity,2))};
end
% set bead tracking grace period
function setbuffer_callback(source,~)
val = str2double(source.String);
if isnan(val) || val < 0
warndlg({'The grace period must be a posivite number.';'Non-integer input is rounded.'},...
'Incorrect input', 'replace');
source.String = num2str(handles.beadbuffer);
return;
end
handles.beadbuffer = round(val);
source.String = num2str(handles.beadbuffer);
end
%% Runs bead detection and deduces bead radius range for tracking
% get approximate range for MB radius interactively, in pixels
function getradrange_callback(source,~,tag)
% set selection lock
if handles.selecting
warn('select');
return;
else
handles.selecting = true;
end
% detect the bead
[beadinfo,pass,rad] = getBead(source,tag,handles.haxes,'cbk',@getradrange_callback);
source.String = '<HTML><center>Radius range</HTML>'; % restore button string
if isempty(beadinfo) || ~pass % check if selection was success
handles.selecting = false;
return;
end;
handles.hminrad.String = num2str(max(0.5*rad,1)); % set new radii >= 1
handles.hmaxrad.String = num2str(max(1.5*rad,1));
setrad_callback(0,0); % takes care of rounding and validation
handles.selecting = false; % remove selection lock
end
%% Set bead detection radius range
% set limit on radius for the bead tracking; verify correct input
function setrad_callback(~,~)
vmin = round(str2double(handles.hminrad.String)); % round values for further input
vmax = round(str2double(handles.hmaxrad.String));
if (isnan(vmin) || isnan(vmax) || vmin < 0 || vmax < 0 ) % abort if input is incorrect
warndlg('Input must be a non-negative number of type double --- is rounded to an interger', 'Incorrect input', 'replace');
handles.hminrad.String = num2str(handles.beadradius(1));
handles.hmaxrad.String = num2str(handles.beadradius(2));
return;
end
handles.beadradius(1) = vmin;
handles.beadradius(2) = vmax;
handles.hminrad.String = num2str(vmin); % infindcircles takes intergers...
handles.hmaxrad.String = num2str(vmax); % ... display intergers to hint user
if (handles.beadradius(1) > handles.beadradius(2)) % warn if input gives empty range
warndlg('Lower bead radius limit is larger than the upper limit',...
'Empty radius range', 'replace');
end
end
%% Bead list functions
% add the selected bead coordinate to list
function addbead_callback(~,~)
if isempty(handles.lastlistbead);
warn('No bead to add.');
return;
end;
handles.beadlist = strucopy(handles.beadlist,handles.lastlistbead); % push back bead to the beadlist
ind = numel(handles.beadlist);
handles.hbeadinilist.String(ind) = {strcat('[',num2str(round(handles.lastlistbead.coor(1))),',',num2str(round(handles.lastlistbead.coor(2))),';',...
num2str(handles.lastlistbead.frame),';',handles.lastlistbead.contrast,']')};
handles.hbeadinilist.Value = ind;
end
% drop-down menu to choose one of the bead initial coors
function pickbead_callback(source,~)
val = source.Value;
handles.bead = handles.beadlist(val);
end
% remove selected bead coordinate from the list
function rmbead_callback(~,~)
val = handles.hbeadinilist.Value;
num = numel(handles.beadlist);
if (num == 0)
return;
elseif (num == 1)
handles.hbeadinilist.String(1) = {'no data'};
handles.hbeadinilist.Value = 1;
handles.beadlist(num) = [];
elseif (val == num)
handles.hbeadinilist.String(val) = [];
handles.beadlist(val) = [];
handles.hbeadinilist.Value = val-1;
else
handles.hbeadinilist.String(val) = [];
handles.beadlist(val) = [];
end
end
%% Pattern list functions
% button adds current pattern to the pattern list
function addpattern_callback(~,~)
if isempty(handles.lastlistpat);
warn('No pattern to add.');
return;
end;
handles.patternlist = strucopy(handles.patternlist,handles.lastlistpat); % adds new pattern to the end of the list
ind = numel(handles.patternlist);
handles.hpatternlist.String(ind) = {strcat('[',num2str(round(handles.lastlistpat.coor(1))),',',num2str(round(handles.lastlistpat.coor(2))),';',...
num2str(handles.lastlistpat.frame),']')};
handles.hpatternlist.Value = ind;
end
% button removes current pattern from the pattern list
function rmpattern_callback(~,~)
val = handles.hpatternlist.Value;
num = numel(handles.patternlist);
if (num == 0) % case of nothing to remove
return;
elseif (num == 1) % removing the last one; replace name with 'nodata'
handles.hpatternlist.String(1) = {'no data'};
handles.hpatternlist.Value = 1;
handles.patternlist(val) = [];
elseif (val == num) % removing last entry in the list; shift left
handles.hpatternlist.String(val) = [];
handles.patternlist(val) = [];
handles.hpatternlist.Value = val-1;
else % general remove; shifts by itself
handles.hpatternlist.String(val) = [];
handles.patternlist(val) = [];
end
end
% drop-down menu to choose one of the patterns
function pickpattern_callback(source,~)
val = source.Value;
setpattern(handles.patternlist(val));
end
%% Detect bead in the frame as initial position for tracking
% select the centre of the bead as a seed for tracking
function getpoint_callback(source,~,srctag)
% check if there's no other selection under way
if handles.selecting
warn('select');
return;
else
handles.selecting = true;
end
[handles.bead,pass,~] = getBead(source,srctag,handles.haxes); % call method to detect bead
% if detection fails (reject or error), old 'handles.bead' value is returned
% if old value is empty, stop here, do not change anything
if isempty(handles.bead) || ~pass;
handles.selecting = false;
return;
end;
% only if meaningful handles.bead selection was returned, continue
switch srctag
case 'list' % call source is list
handles.lastlistbead = handles.bead;
handles.hbeadcoortxt.String = strcat('[',num2str(round(handles.bead.coor(1))),',',num2str(round(handles.bead.coor(2))),...
';',num2str(handles.bead.frame),']');
case 'interval' % call source is direct interval bead detection
if (handles.interval.frames(1) ~= handles.bead.frame)
warn(strjoin({'Initial frame of current tracking interval was changed',...
'to the frame of bead selection. If You wish to preserve Your interval,',...
'reselect the bead in the appropriate frame.'}));
handles.interval.frames(1) = handles.bead.frame;
handles.hstartint.String = num2str(round(handles.interval.frames(1)));
end;
if (handles.interval.frames(2) == 0 || handles.interval.frames(2) < handles.interval.frames(1))
warn(strjoin({'Final frame of current tracking interval was invalid',...
'and was changed. The final frame must not precede the initial.'}));
handles.interval.frames(2) = handles.interval.frames(1);
handles.hendint.String = num2str(round(handles.interval.frames(2)));
end;
handles.interval.beadcoor = handles.bead.coor;
handles.interval.contrast = handles.bead.contrast;
handles.hbeadint.String = strcat('[',num2str(round(handles.interval.beadcoor(1))),',',num2str(round(handles.interval.beadcoor(2))),...
';',num2str(handles.interval.frames(1)),']');
handles.tmpbeadframe = handles.interval.frames(1);
handles.hgetpattern.Enable = 'on';
handles.hselectpat.Enable = 'on';
end
handles.selecting = false; % release detection lock
end
%% Gets pattern for the pipette tip
% allows user to select rectangular ROI as a pattern file
function getrect_callback(source,~,srctag)
% check if no other selection process is running
if handles.selecting
warn('select');
return;
else
handles.selecting = true;
end
[handles.pattern,pass] = getPattern( source, srctag);
if isempty(handles.pattern) || ~pass;
handles.selecting = false;
return;
end;
switch srctag
case 'list'
handles.lastlistpat = handles.pattern;
handles.hpatcoortxt.String = strcat( '[',num2str(round(handles.pattern.coor(1))),',',num2str(round(handles.pattern.coor(2))),';',...
num2str(handles.pattern.frame),']');
setpattern(handles.pattern);
case 'interval'
handles.interval.pattern = handles.pattern.cdata;
handles.interval.patcoor = handles.pattern.coor;
handles.tmppatframe = handles.pattern.frame;
handles.updpatframe = 0; % if pattern was matched to interval in the meantime, reset
[handles.interval.reference,handles.interval.patsubcoor,~] = ... % validate if the array is not already present
validatepattern(handles.pattern.cdata,handles.pattern.frame,handles.pattern.anchor);
handles.hrefframe.String = num2str(handles.interval.reference);
handles.hpatternint.String = strcat('[',num2str(round(handles.interval.patcoor(1))),',',num2str(round(handles.interval.patcoor(2))),...
';',num2str(handles.tmppatframe),']');
handles.hpatsubcoor.String = strcat('[',num2str(round(handles.interval.patsubcoor(1))),','...
,num2str(round(handles.interval.patsubcoor(2))),']');
end
handles.selecting = false;
end
%% Get path of the video file
% set video path by typing it in
function videopath_callback(source,~)
handles.videopath = source.String;
end
% browse button to chose the file
function [ validFile] = browsevideo_callback(~,~)
[filename, pathname] = uigetfile({'*.avi;*.mp4;*.tiff;*tif','Video Files (*.avi,*.mp4,*.tiff)'},...
'Select a video file',handles.videopath);
if ~isequal(filename, 0) % test validity of selected file; returned 0 if canceled
handles.videopath = strcat(pathname,filename);
handles.hvideopath.String = handles.videopath;
validFile = openvideo_callback; % returns true is properly openned
else
validFile = false; % return not-selected flag
end
end
%% Change video framerate
% user can change video framerat (though might not be a good idea)
function reframe_callback(source,~,type)
if strcmp(type,'btn') % this is a button call requesting change
source.Visible = 'off';
handles.hreframeedt.Visible = 'on';
else % case of 'data' type - value of frmrate passed in
val = str2double(source.String);
if (isnan(val) || val <= 0)
warn('Input must be a positive value of type double.')
source.String = num2str(vidObj.Framerate); % reset old value
return;
end
val = round(val,1);
vidObj.Framerate = val;
source.String = num2str(val);
vidObj.Duration = vidObj.Frames/vidObj.Framerate;
source.Visible = 'off';
handles.hreframebtn.Visible = 'on';
setvidinfo();
end
end
%% Settings for ouput video
% sets frame rate of the output generated video
function outvideo_callback(source,~,failsafe,var)
val = round(str2double(source.String));
if (isnan(val) || val < 1)
warndlg('The input must be a positive number.','Incorrect input','replace');
source.String = num2str(failsafe);
return;
end
if (var == 1)
handles.outframerate = val;
elseif (var == 2)
handles.outsampling = val;
end
source.String = num2str(val); % in case of rounding
end
% generate an external video file of original film overlaid with
% tracking marks
function generatefilm_callback(~,~)
BFPobj.generateTracks('Framerate',handles.outframerate,'Sampling',handles.outsampling);
end
%% Display tracking overlay
% whether to display or not the tracking data results
function disptrack_callback(source,~)
handles.disptrack = source.Value;
end
%% Calculate (if not) and plot the video SD2 contrast
% calculate SD2 contrast and plot the contrast progress of the video,
% it can also be switched to rSD2 contrast, SD2 contrast is analyzed
% and intervals of low contrast marked
function getcontrast_callback(~,~,srctag)
% if video is long, issue notice
if vidObj.Frames > 1000 && handles.verbose && numel(vidObj.Contrast) ~= vidObj.Frames
choice = questdlg(strjoin({'The video consists of more than 1000 frames. Depending on Your system,',...
'the analysis can take up to several minutes. Progress is reported in the Matlab command window.',...
'If successfully completed, the data could be later reused during tracking. Would You like to',...
'continue?'}),'Contrast analysis', 'Continue', 'Cancel', 'Continue');
switch choice
case 'Continue' % continue with the analysis
case {'Cancel',''} % catch also (X)
return; % cancel the analysis
end
end
% if it is analysis call, take the whole domain
if strcmp(srctag,'analysis')
handles.lowplot = 1; % initial frame
handles.highplot = vidObj.Frames; % final frame
if handles.contype ~= 1 % switch to SD2 metric for analysis
warn('Contrast metric type switched to SD2. There is no thresholding for rSD2 type metric');
handles.contype = 1;
handles.hSD2.Value = 1;
handles.hrSD2.Value = 0;
end
end
% returned 'contrast' all video frames, regardeless of subinterval
% see function comment for details
[ contrast, ~ ] = vidObj.getContrast(handles.lowplot, handles.highplot,handles.contype,backdoorObj.contrastRunningVarianceWindow);
if numel(contrast) ~= vidObj.Frames % if process of contrast calculation was cancelled
handles.highplot = min(handles.lowplot+numel(contrast)-1, vidObj.Frames);
end
handles.fitInt = [handles.lowplot, 0; handles.highplot, 0]; % set global fit interval
cla(handles.hgraph); % clear current graph
ax2 = findobj('Tag','deformationaxis'); % delete the right y-axis, which might be...
if exist('ax2','var'); ax2.delete; end; % ... drawn by force plotter; tagged
hold(handles.hgraph,'on');
set(handles.hgraph, 'FontUnits','normalized','FontSize',handles.labelfontsize);
hconplot = plot(handles.hgraph,handles.lowplot:handles.highplot,contrast(handles.lowplot:handles.highplot),'r','HitTest','off');
% hgrayplot = plot(handles.hgraph,handles.lowplot:handles.highplot,gray(handles.lowplot:handles.highplot), 'b', 'HitTest','off');
xlim(handles.hgraph,[handles.lowplot,handles.highplot]); % avoid margins around the graph
handles.thisRange = [handles.lowplot,handles.highplot]; % range of the current plot
handles.thisPlot = 1; % currently plotted contrast flag
handles.toPlot = 1; % contrast to plot next
handles.hgraphitem.Value = handles.toPlot;
handles.hgraphplot.Enable = 'on'; % allow plot button, contrast only
set( handles.hlowplot, 'Enable','on', 'String', num2str(handles.lowplot) );
set( handles.hhighplot, 'Enable','on', 'String', num2str(handles.highplot) );
% legend(handles.hgraph,'contrast','mean gray');
cl = legend(handles.hgraph,'contrast');
cl.Box = 'off';
cl.FontUnits = 'normalized';
title(handles.hgraph,{'Contrast measure';'[standard deviation of each frame]'},'Color','r','FontUnits','normalized','FontSize',handles.labelfontsize);
xlabel(handles.hgraph, 'Time [frames]', 'FontUnits', 'normalized', 'FontSize', handles.labelfontsize);
ylabel(handles.hgraph, 'Contrast [r.u.]', 'FontUnits', 'normalized', 'FontSize', handles.labelfontsize);
% find plateaux and report 'safe' and 'unsafe' intervals
% these parameters can be backdoored;
% sensitivity, threshold, duration and minimal contrast. The results are only
% informative, so user should need to change them. More
% sophisticated analysis can be done using adaptive plateaux
% fitting.
if strcmp(srctag,'analysis') % if the call comes from analysis
% save default values suitable for force plateaux detection
defaults = [ handles.kernelWidth, handles.noiseThresh, handles.minLength ];
handles.kernelWidth = backdoorObj.contrastPlateauDetectionSensitivity;
handles.noiseThresh = backdoorObj.contrastPlateauDetectionThreshold;
handles.minLength = backdoorObj.contrastPlateauDetectionLength;
fit_callback(0,0,'plat',true);
% restore defaults
[handles.kernelWidth] = defaults(1);
[handles.noiseThresh] = defaults(2);
[handles.minLength] = defaults(3);
if handles.verbose;
helpdlg(strjoin({'Contrast analysis has finished. The detected plateaux (in red) are the safest',...
'intervals for tracking. Drop in contrast of more than',num2str((1-backdoorObj.contrastPlateauDetectionLimit)*100),'%',...
'off maximum is designated (if detected) in blue. Those intervals might be unsuitable for tracking.'}),...
'Contrast analysis finished');
end
end
handles.hgraph.ButtonDownFcn = {@getcursor_callback};
end
%% Go to frame
% sets frame input into edit field after pressing a button
function gotoframe_callback(~,~)
% temporary edit field
hsetframe = uicontrol('Parent',handles.husevideo, 'Style','edit', 'Units', 'normalized','FontUnits','normalized',...
'Position', [0.8, 0.75, 0.2, 0.25],'String',num2str(getFrame()),'Callback', {@presetframe});
% avoid errors; treat malformed input
function presetframe(src,~)
val = str2double(src.String);
if isnan(val)
elseif val <= 0
setframe(1);
elseif val >= vidObj.Frames
setframe(vidObj.Frames);
else
setframe(val);
end
delete(hsetframe); % delete the edit field
end
end
%% Video playback functions
% rewinds or fast forwards video; argument sense is usually +/-5 frames
% but the value can be changed in backdoorObj
function fastvideo_callback(~,~,sense)
if sense < 0
sense = backdoorObj.rewindFramerate;
else
sense = backdoorObj.fastforwardFramerate;
end
playvideo_callback(0,0,sense);
end
% starts to play the video
function playvideo_callback(~,~,rate)
handles.playing = true;
handles.hplaybutton.String = 'Stop';
handles.hplaybutton.Callback = {@stopvideo_callback};
while ((vidObj.CurrentFrame + rate <= vidObj.Frames && vidObj.CurrentFrame + rate > 0) && handles.playing)
setframe(vidObj.CurrentFrame+rate);
pause(1);
end
handles.hplaybutton.String = 'Play';
end
% stops the video play
function stopvideo_callback(~,~)
handles.playing = false;
handles.hplaybutton.String = 'Play';
handles.hplaybutton.Callback = {@playvideo_callback,1}; % restores play callback, sets framerate to 1
end
%% Open video defined by path
% open video and set its parameters where necessary; update callbacks
function [newOpenned] = openvideo_callback(~,~)
newOpenned = false;
if exist(handles.videopath,'file') ~= 2; % i.e. path is not a path to a file
warndlg('Path is incorrect or the file doesn''t exist','File inaccessible','replace');
return;
else
try
vidObj = vidWrap(handles.videopath);
handles.frame = struct('cdata', zeros(vidObj.Height,vidObj.Width, 'uint16'), 'colormap', []);
handles.hmoviebar.Enable = 'on';
handles.hmoviebar.Min = 1;
handles.hmoviebar.Max = vidObj.Frames;
handles.hmoviebar.Value = vidObj.CurrentFrame;
handles.hmoviebar.SliderStep = [ 1/vidObj.Frames, 0.1 ];
setframe(1);
setvidinfo();
handles.interval = struct('frames', [1,1],'pattern',[]); % reset interval-in-making
handles.interval.frames = [1,vidObj.Frames]; % set initial interval and callback parameters
set(handles.hstartint, 'String', num2str(handles.interval.frames(1)),...
'Callback',{@setintrange_callback,handles.interval.frames(1),1});
set(handles.hendint, 'String', num2str(handles.interval.frames(2)),...
'Callback',{@setintrange_callback,handles.interval.frames(2),2});
handles.hrefframe.String = [];
handles.hpatsubcoor.String = '[.,.]';
set([handles.hpatternint,handles.hbeadint],'String','[.,.;.]');
set([handles.hgetbead, handles.hselectbead],'Enable','on'); % enable selections
set([handles.hdispframe,handles.hstartint,handles.hendint,handles.hshowframe,handles.hrefframe],'Enable','on');
set([handles.hplaybutton,handles.hrewindbtn,handles.hffwdbutton,handles.hcontrast],'Enable','on');
handles.hradtxt.Enable = 'on';
handles.haddinterval.Enable = 'off';
handles.hvideopath.String = handles.videopath; % if called without GUI (i.e. the path string not updated)
newOpenned = true;
catch
% issue warning of incompatible file
warndlg(strjoin({'The specified file at', handles.videopath, 'exists, but could not be openned.',char(10),...
'It is either malformed or its format is invalid. Please check the file and try again.'}),...
'Invalid video file','replace');
% restore currently active videopath, if not empty
if ~isempty(vidObj)
handles.videopath = vidObj.videopath;
handles.hvideopath.String = handles.videopath;
end
end
end
end
% set position in the video
function videoslider_callback(source,~)
setframe(source.Value);
end
% ==================================================================
%% ======================== HELPER FUNCTIONS ==========================
%% Extract the pipette pattern from the frame
% detect and return pipette pattern based on the selected area;
% try/catch in case user interrupts the selection
function [ patinfo,pass ] = getPattern( source, tag )
patinfo = struct('coor',[],'frame',[],'reference',[], 'cdata', [], 'anchor', []);
BCRfunction = makeConstrainToRectFcn('imrect',handles.haxes.XLim,handles.haxes.YLim);
rectangle = imrect(handles.haxes,'PositionConstraintFcn',BCRfunction); % interactive ROI selection
source.String = 'Confirm'; % update UI to confirm selection
source.Callback = 'uiresume(gcbf)';
uiwait(gcf);
try
dcoor = rectangle.getPosition; % vector of 4 coordinates; doubles
icoor = round(dcoor);
roi = [ max(icoor(2),1), min(icoor(2)+icoor(4),vidObj.Height);... % ROI in the image
max(icoor(1),1), min(icoor(1)+icoor(3),vidObj.Width) ]; % construct coors of ROI
patinfo.cdata = handles.frame.cdata(roi(1,1):roi(1,2),roi(2,1):roi(2,2),:); % copy the selected image
patinfo.coor = [ dcoor(1), dcoor(2) ]; % save rect upper left corner
patinfo.frame = round(vidObj.CurrentFrame); % set reference distance for the pattern
rectangle.delete;
choice = questdlg(strjoin({'Please, select the anchor on the pattern, keep default selection.',...
'For more information, choose ''Info'' button'}),'Anchor selection',...
'Select', 'Default', 'Info', 'Default');
if strcmp(choice,'Info')
choice = questdlg(strjoin({'The anchor represents a coordinate on the pattern, which',...
'will be reported by the tracking method over time. It determines the point on the',...
'pipette, which will be used to calculate the extension of the red blood cell.',...
'You can select the point Yourself, or let the choice up to algorithm.'}),...
'Anchor selection - details', 'Select', 'Default', 'Default');
end
switch choice
case 'Select'
% set depending on the source call
if strcmp(tag,'interval')
handles.interval.pattern = patinfo.cdata;
[hf,hax] = showintpattern_callback();
hf.CloseRequestFcn = {@closeFig};
else
hax = handles.hminiaxes;
imagesc(patinfo.cdata, 'Parent', hax); % display the cut in the special window
axis(hax, 'image','off');
end
BCfunction = makeConstrainToRectFcn('impoint',get(hax,'XLim'),get(hax,'YLim'));
anchorpoint = impoint(hax, 'PositionConstraintFcn', BCfunction);
if isvalid(anchorpoint) % figure not closed by user
source.String = 'Accept';
source.ForegroundColor = 'red';
source.Callback = 'uiresume(gcbf)';
if exist('hf','var')
hf.CloseRequestFcn = 'uiresume(gcbf)';
end
uiwait(gcbf);
try
patinfo.anchor = anchorpoint.getPosition;
catch
warning(strjoin({'An error occured during anchor selection callback,',...
'it was probably interrupted by another function or action.'}));
warndlg({'Selection function failed. The anchor point value (reference) has been set to default';...
'It can still be modified in Interval selection window.'},'Anchor selection failed','replace');
patinfo.anchor = round(0.5*[size(patinfo.cdata,2),size(patinfo.cdata,1)]);
end
anchorpoint.delete;
end
set(source, 'String', 'Select', 'ForegroundColor', 'black', 'Callback', {@getrect_callback,tag});
if exist('hf','var'); hf.delete; end;
case 'Default'
patinfo.anchor = round(0.5*[size(patinfo.cdata,2),size(patinfo.cdata,1)]);
otherwise % case (X)
warn('Pipette pattern selection cancelled by user during anchor choice.');
patinfo = handles.pattern;
pass = false;
source.String = 'Select';
source.Callback = {@getrect_callback, tag};
return;
end
if strcmp(tag,'interval')
handles.hgetpatsubcoor.Enable = 'on';
handles.hshowpattern.Enable = 'on';
handles.haddinterval.Enable = 'on';
handles.hgetrefframe.Enable = 'on';
end
pass = true;
catch
warn('interrupt');
rectangle.delete;
patinfo = handles.pattern;
pass = false;
end
source.String = 'Select';
source.Callback = {@getrect_callback, tag};
function closeFig(~,~) % if figure is closed before imroi obj. created
patinfo.anchor = round(0.5*[size(patinfo.cdata,2),size(patinfo.cdata,1)]);
warndlg({'Selection interrupted. The anchor point value (reference) has been set to default';...
'It can still be modified in Interval selection window.'},'Anchor selection interrupted','replace');
uiresume(gcbf);
end
end
%% Detect the bead near the click-provided coordinate
% detect and return bead information; calls TrackBead method; provides
% also bead detection radius range calibration
function [ beadinfo,pass,rad ] = getBead( source,tag,hax,varargin )
persistent inpar; % persistent parser, created only once
% create parser instance, if not present
if isempty(inpar)
inpar = inputParser();
defaultCallback = @getpoint_callback; % to reset source cbk
defaultFrame = -1; % no input, flag as -1 (any more elegant solution more than welcome)
inpar.addRequired('source'); % calling uicontrol
inpar.addRequired('tag'); % call from list or direct
inpar.addRequired('hax', @(x) isgraphics(x,'axes')); % axes where to get bead
inpar.addParameter('frm', defaultFrame, @isfloat);
inpar.addParameter('cbk', defaultCallback );
end
% parse the inputs (they should allow overloads in Matlab...)
inpar.parse(source,tag,hax,varargin{:});
source = inpar.Results.source;
tag = inpar.Results.tag;
haxis = inpar.Results.hax;
cbk = inpar.Results.cbk;
if inpar.Results.frm==-1; % no input, use current frame
frm = vidObj.CurrentFrame;
else % use passed frame
frm = inpar.Results.frm;
end
% =============================================================
beadinfo = struct('coor',[],'frame',[],'contrast',[]);
% intial section, set boundary, select point, change UI, wait for
% confirmation of the selection
BCfunction = makeConstrainToRectFcn('impoint',get(haxis,'XLim'),get(haxis,'YLim'));
beadpoint = impoint(haxis,'PositionConstraintFcn',BCfunction);
source.String = 'Confirm';
source.Callback = 'uiresume(gcbf)';
uiwait(gcf);
try
beadinfo.coor = beadpoint.getPosition;
beadinfo.frame= round(frm);
choice = questdlg('Select bead contrast. For a bead darker than background, select ''Dark'', and visa versa.',...
'Bead contrast','Bright','Dark','Dark');
switch choice
case 'Bright'
beadinfo.contrast = 'bright';
case 'Dark'
beadinfo.contrast = 'dark';
otherwise % (X) case
beadinfo = handles.bead;
pass=false;
rad = 0;
beadpoint.delete;
source.String = 'Select';
source.Callback = {cbk,tag};
warn('Bead selection cancelled by user during contrast choice.');
return;
end;
beadpoint.delete;
if strcmp(tag,'beadrad')
defaultRad = [5,50]; % wide range to catch-all (nearly)
else
defaultRad = handles.beadradius;
end
[coor,rad,metric,~] = TrackBead(vidObj, beadinfo.contrast, beadinfo.coor,...
[ beadinfo.frame, beadinfo.frame ], 'radius', defaultRad, 'retries', 1 ); % try to detect the bead in the frame
if rad == 0; % stop if nothing is detected
warndlg(strjoin({'No bead was detected in the given vicinity for',beadinfo.contrast,'contrast.',...
'Please repeat Your selection, placing the search point within the desired bead',...
'and choosing the appropriate contrast. Search will now abort.'}),'No bead detected','replace');
pass = false;
source.String = 'Select';
source.Callback = {cbk,tag};
return; % kill the procedure
end
if metric < handles.beadmetricthresh; % warn for weak detection; use glob metric thresh
warn(strjoin({'The bead metric is only',num2str(round(metric,2)),...
'which is below the threshold',num2str(handles.beadmetricthresh),...
'and detection failures can occur.'}));
end;
hcirc = viscircles(haxis,[ coor(2), coor(1) ], rad, 'EdgeColor','r'); % plot the detected bead
choice = questdlg('Was the bead detected correctly?','Confirm selection','Accept','Reject','Accept');
switch choice
case 'Accept' % precise the coordinate
beadinfo.coor = [coor(2), coor(1) ];
pass = true;
case {'Reject', ''} % catch also (X)
beadinfo = handles.bead;
pass = false;
end;
hcirc.delete;
catch
warn('interrupt');
beadpoint.delete;
beadinfo = handles.bead;
pass=false;
rad = 0;
end
source.String = 'Select';
source.Callback = {cbk,tag};
end
%% Select and display pattern from the list
% selects pattern
function [] = setpattern(pattern_in)
handles.pattern = pattern_in;
imagesc(handles.pattern.cdata, 'Parent', handles.hminiaxes); % display the cut in the special window
axis(handles.hminiaxes, 'image','off');
end
%% Small video procedures
% returns current frame number; can be changed to more complex and
% failsafe behaviour (now's just ridiculous, I know)
function [currentFrame] = getFrame()
currentFrame = handles.vidFrameNo;
end
% check if this frame is part of the video
function [ is ] = isinvideo(frame)
is = ( frame > 0 && frame <= vidObj.Frames );
end
%% Set requested frame number as current frame
% sets the GUI to the given frame number; takes case of drawing the
% detection overlay, if requested
function [] = setframe(frameNo)
handles.vidFrameNo = round(frameNo);
handles.frame = vidObj.readFrame(handles.vidFrameNo);
handles.hdispframe.String = strcat(num2str(handles.vidFrameNo),'/', num2str(vidObj.Frames));
handles.hmoviebar.Value = vidObj.CurrentFrame;
imagesc(handles.frame.cdata, 'Parent', handles.haxes);
colormap(gray);
axis(handles.haxes, 'image'); % set limits of the canvas to 'image'
handles.haxes.FontUnits = 'normalized'; % make sure ticks are rescaled with the window
if handles.disptrack
hold(handles.haxes, 'on')
for i=1:numel(BFPobj.intervallist);
if( handles.vidFrameNo >= BFPobj.intervallist(i).frames(1) && ...
handles.vidFrameNo <= BFPobj.intervallist(i).frames(2) )
coorind = handles.vidFrameNo - BFPobj.intervallist(i).frames(1) + 1;
viscircles(handles.haxes,[ BFPobj.pipPositions(i).coor(coorind,2)/handles.P2M,...
BFPobj.pipPositions(i).coor(coorind,1)/handles.P2M ],...
5, 'EdgeColor','b'); % plot pipette
viscircles(handles.haxes,[ BFPobj.beadPositions(i).coor(coorind,2)/handles.P2M,...
BFPobj.beadPositions(i).coor(coorind,1)/handles.P2M ],...
BFPobj.beadPositions(i).rad(coorind)/handles.P2M, 'EdgeColor','r'); % plot bead
break; % do not continue once plotted
end
end
end
end
%% Video information report
% populates the video information pannel
function setvidinfo()
handles.hvidwidth.String = strcat('Width: ',num2str(vidObj.Width),' px');
handles.hvidheight.String = strcat('Height: ',num2str(vidObj.Height),' px');
handles.hvidframes.String = strcat('Frames: ',num2str(vidObj.Frames));
handles.hvidname.String = strcat('Name: ', vidObj.Name);
handles.hvidformat.String = strcat('Format: ', vidObj.Format);
handles.hvidframerate.String = strcat('Framerate: ',num2str(vidObj.Framerate), ' fps');
handles.hvidduration.String = strcat('Duration: ',num2str(vidObj.Duration),' s');
handles.hreframeedt.String = vidObj.Framerate;
if vidObj.istiff;
handles.hreframebtn.Enable = 'on';
handles.hreframebtn.String = 'Reframerate';
handles.hreframebtn.TooltipString = 'Click to change framerate';
else
handles.hreframebtn.Enable = 'off';
handles.hreframebtn.String = 'TIFF only';
end
end
%% Copy strcture
% copy structure into an empty target
function outlist = strucopy(outlist,item)
size = numel(outlist);
names = fieldnames(item);
for i=1:numel(names)
outlist(size+1).(names{i}) = item.(names{i});
end
end
%% Validate new pip pattern against already present patterns
% search if the current pattern is already present in the list and user
% appropriate reference frame and anchor, if it is
% in: pattern: the image array; patframe: the frame of origin of image,
% anchor: selected anchor of the image pattern
function [rframe,ranchor,absent] = validatepattern(pattern,patframe,anchor,nowarn)
if ~exist('nowarn','var'); nowarn = false; end;
rframe = patframe; % return zero, if original reference cannot be found
ranchor = anchor;
absent = true;
if numel(handles.intervallist) > 0
for i=1:numel(handles.intervallist)
if isequaln(pattern, handles.intervallist(i).pattern)
if (rframe == handles.intervallist(i).reference) && all(ranchor == handles.intervallist(i).patsubcoor)
if ~nowarn
warn(strjoin({'The pattern was recognised in another interval,',...
'all settings comply.'}));
end
else
rframe = handles.intervallist(i).reference; % this is the reference distance frame
ranchor = handles.intervallist(i).patsubcoor; % this is the anchor for the given pattern
if ~nowarn
warn(strjoin({'The pattern was found in another present interval',...
'and reference frame and anchor point were updated accordingly.',char(10),...
strcat('Reference frame reset to:',num2str(rframe)),char(10),...
strcat('Anchor point reset to:[',...
num2str(round(ranchor(1))),',',num2str(round(ranchor(2))),']'),char(10),...
'You can change this manually, in the interval selection panel.'}));
end
end
absent = false;
break;
else
absent = true;
% old values are returned, if pattern is not found
end
end
else
% do nothing
end
end
%% Generate table with intervals
% generates table of intervals from the intervallist entries
function makeTab()
tablist = struct2table(handles.intervallist,'AsArray',true); % convert structure to table, so that certain fields can be safely removed from the displayed table
tablist.pattern = []; % remove the pattern images
tablist.contrast = []; % remove contrast string
tablist.reference = []; % remove reference frame
inArray = table2array((tablist)); % convert table to array of doubles, so that the fields are separated into unity width columns
inArray = round(inArray); % round, just to make things look nicer and optimize space
removes = num2cell(false(numel(handles.intervallist),1)); % column of selectable fields to allow removals; cell array
inData = num2cell(inArray); % convert the numeric inputs into cell array (that's what uitable wants)
inData(:,size(inArray,2)+1) = removes; % combine the cell arrays and choke the shrew
htab = uitable('Parent', handles.hlistinterval,'Data', inData, 'ColumnName', colnames, 'ColumnFormat',...
colformat, 'ColumnEditable',[false false false false false false false false true],...
'RowName',[], 'Units','normalized','Position',[0,0,0.9,1],...
'ColumnWidth',{52},'CellEditCallback',@rmtabledint_callback);
end
%% Contrast type radiobutton switch
% radio button group selection change callback
% determines which type of contrast will be plot
function contype_callback(~,data)
if data.NewValue == handles.hSD2;
handles.contype = 1; % plot SD2 contrast metric
disp('Contrast plot set to SD2 metric');
else
handles.contype = 2; % plot running contrast metric
disp('Contrast plot set to rSD2 metric');
end
% if contrast plot is and will be open; replot when changed type
if ~isempty(handles.thisPlot) && handles.thisPlot == 1 && handles.toPlot==1
graphplot_callback(0,0);
end
end
%% Exponential fit of graphed data
% fits data with one-parametric exponentiel
function [ est, FitCurve ] = expfit( int, frc, varargin )
persistent inp;
% create the parser, if doesn't exist
if isempty(inp)
inp = inputParser();
defaultRate = 1;
addRequired(inp, 'int');
addRequired(inp, 'frc');
addParameter(inp, 'framerate', defaultRate, @isnumeric);
end;
% parse the inputs
inp.parse(int, frc, varargin{:});
int = inp.Results.int;
frc = inp.Results.frc;
rate = inp.Results.framerate;
% ===============================
DF = frc(end) - frc(1); % change in force
int = (int - int(1))/rate; % start time at 0
initau = 1; % initial time constant guess
model = @expfun; % model exp function handle
options = optimset('Display','final','FunValCheck','on');
est = fminsearch(model, initau, options);
function [sse, fittedCurve] = expfun(tau)
fittedCurve = frc(1) + DF * ( 1.0 - exp(-int/tau) );
errorVector = fittedCurve - frc;
sse = sum(errorVector.^2);
end
[~, FitCurve] = model(est);
end
%% Generate valid name of file to save data/image/figure
% function to set up path for a file to save
function [ exportfile ] = putFileName(name)
dataList = { '.csv','.txt','.dat' };
graphicList = { '.bmp','.eps','.jpg', '.pdf','.png','.tif','.fig' };
matFile = '.mat';
persistent dir;
if isempty(dir)
if ~isempty(vidObj)
[dir, ~, ~ ] = fileparts(vidObj.videopath); % the same dir as video to start
else
dir = pwd;
end
end
inipath = fullfile(dir,name);
[~,~,ext] = fileparts(inipath);
switch ext
case dataList
[filename, dir] = uiputfile({'*.csv;*.txt;*.dat;','Data files (*.csv,*.txt,*.dat)'},...
'Select a file for export',inipath); % choose path and file for data export
case graphicList
[filename, dir] = uiputfile({'*.bmp;*.eps;*.jpg;*.pdf;*.png;*.tif;*.fig','Data files (*.bmp,*.eps,*.jpg,*.pdf,*.png,*.tif,*.fig)'},...
'Select a file for export',inipath); % choose path and file for graphic export
case matFile
[filename, dir] = uiputfile({'*.mat;','Mat-files (*.mat)'},...
'Select a file for export',inipath); % choose path and file for matlab export
end
if isequal(filename,0) || isequal(dir,0)
exportfile = 0;
dir = pwd;
else
exportfile = fullfile(dir,filename);
end
end
%% Get valid name and path of file to load data
% function to set up path for a file to load
function [importfile] = getFileName(name)
dataList = { '.csv','.txt','.dat' };
graphicList = { '.bmp','.eps','.jpg', '.pdf','.png','.tif','.fig' };
matFile = '.mat';
persistent dir;
if isempty(dir)
if ~isempty(vidObj) % if object was instantiated
[dir, ~, ~ ] = fileparts(vidObj.videopath); % the same dir as video to start
else
dir = pwd;
end
end
inipath = fullfile(dir,name);
[~,~,ext] = fileparts(inipath);
switch ext
case dataList
[filename, dir] = uigetfile({'*.csv;*.txt;*.dat;','Data files (*.csv,*.txt,*.dat)'},...
'Select a file for import',inipath); % choose path and file for data export
case graphicList
[filename, dir] = uigetfile({'*.bmp;*.eps;*.jpg;*.pdf;*.png;*.tif;*.fig','Data files (*.bmp,*.eps,*.jpg,*.pdf,*.png,*.tif,*.fig)'},...
'Select a file for import',inipath); % choose path and file for graphic export
case matFile
[filename, dir] = uigetfile({'*.mat;','Mat-files (*.mat)'},...
'Select a file for import',inipath); % choose path and file for matlab export
end
importfile = fullfile(dir,filename);
end
%% Warning function
% function to display warning dialogue or just command line warning,
% depending on the 'handles.verbose' variable; Some warning messages are preset
% otherwise, the passed string is displayed
function warn( type, append )
% construct the string
switch type
case 'select'
str = 'Another selection process is currently running. Finish the former and try again.';
name = 'Concurrent selection';
case 'interrupt'
str = strjoin({'Selection process was interrupted by another action.',...
'Please try again, without any intermittent action during the process.'});
name = 'Interrupted selection';
otherwise
str = type;
name = 'Specific warning';
end
if exist('append','var');
strParts = {str,append};
str = strjoin(strParts,'\n\n'); % construct two-part warning
end;
% display either the warining dialogue or command line warning
if handles.verbose
hdia = warndlg(str, name, 'replace');
uiwait(hdia);
else
warning(str);
end
end
%% Plot horizontal line of zero load/deformation
% plots red dashed line at y=0 to indicate pushing and pulling
function plotZeroLine()
handles.hzeroline = plot(handles.hgraph,handles.lowplot:handles.highplot,zeros(1,handles.highplot-handles.lowplot+1),...
'--r','LineWidth',2,'HitTest','off');
handles.pushtxt = text(handles.hgraph.XLim(2),0,'Pushing','Parent',handles.hgraph,'FontUnits','normalized',...
'HorizontalAlignment','right', 'VerticalAlignment','top','Color','red',...
'FontSize',handles.labelfontsize,'Margin',5,'HitTest','off');
handles.pulltxt = text(handles.hgraph.XLim(2),0,'Pulling','Parent',handles.hgraph,'FontUnits','normalized',...
'HorizontalAlignment','right', 'VerticalAlignment','bottom','Color','red',...
'FontSize',handles.labelfontsize,'Margin',5,'HitTest','off');
end
%% RBC stiffness annotation
% generates RBC stiffness annotation after the force was run
function makeStiffAnot()
if isempty(BFPobj)||isempty(BFPobj.k)
warn('RBC stiffness information can be generated only after the force has been calculated');
return;
end;
if ~isempty(handles.anot);handles.anot.delete;end;
if handles.overLimit;colour = 'red'; else colour = 'blue';end
strk = {strcat('$$ k = ', num2str(round(handles.stiffness)),' \frac{pN}{\mu m}$$'),...
strcat('$$ \Delta k = \pm' , num2str(round(BFPobj.Dk)),' \frac{pN}{\mu m} $$')};
handles.anot = annotation( handles.hcalc, 'textbox', 'interpreter', 'latex', 'String', strk, ...
'Units', 'normalized', 'Position', [0,0.67,0.5,0.15], 'Margin', 0, ...
'LineStyle','none','FitBoxToText','off','Color',colour,'FontUnits','normalized',...
'FontSize',0.025);
end
%% Export the whole environment
function saveEnvironment(fileName)
% GUI settings
for dat = 1:numel(GUIdata)
outgoing.GUIdata.(GUIdata{dat}) = handles.(GUIdata{dat});
end
% objects
outgoing.GUIobj.backdoorObj = backdoorObj;
outgoing.GUIobj.BFPobj = BFPobj;
outgoing.GUIobj.vidObj = vidObj;
% GUI flags
% strings
for str = 1:numel(GUIflags.Strings)
outgoing.GUIflags.(GUIflags.Strings{str}).String = handles.(GUIflags.Strings{str}).String;
end
% values
for val = 1:numel(GUIflags.Values)
outgoing.GUIflags.(GUIflags.Values{val}).Value = handles.(GUIflags.Values{val}).Value;
end
% enables
for ebl = 1:numel(GUIflags.Enables)
outgoing.GUIflags.(GUIflags.Enables{ebl}).Enable = handles.(GUIflags.Enables{ebl}).Enable;
end
% visibility
for vis = 1:numel(GUIflags.Visibles)
outgoing.GUIflags.(GUIflags.Visibles{vis}).Visible = handles.(GUIflags.Visibles{vis}).Visible;
end
save(fileName,'outgoing');
end
%% Load a full saved session from a file
function loadEnvironment(fileName)
in = load(fileName);
if ~isfield(in,'outgoing'); % if import cancelled
return;
end;
oldanot = handles.anot;
if ~isempty(handles.anot)
handles.anot.delete();
end
oldvideopath = handles.videopath;
oldvidobj = vidObj; % [] if empty
handles.videopath = in.outgoing.GUIdata.videopath;
% open video -- sets up GUI values, which will be later modified
% generates vidObj object, the video wrapper, based on the imported
% video information;
% if the video file doesn't exist or it cannot be successfully
% opened, user can navigate to the proper file, if it fails as
% well, import is interrupted
if exist(handles.videopath,'file') ~= 2 || ~openvideo_callback(); % i.e. path is not a path to a file or openning fails
hwd = warndlg('Path to the video file is incorrect, the file doesn''t exist or is malformed. Please try to navigate to the appropriate file.',...
'File not found','replace');
validFile = browsevideo_callback(); % attempt to browse and open the video
if ~validFile % no valid video provided
handles.videopath = oldvideopath; % restore old videopath
handles.anot = oldanot; % restope old annotation
return; % if import failed, return
end
hwd.delete;
end
% generate matching structure
match = vidObj.matchVideos(in.outgoing.GUIobj.vidObj); % test if videos seem the same (just verify width,height,#frames,format)
% inform if videos match; copy calculated data if they do
if ~match.result
warndlg(strjoin({'The specified video doesn''t match the imported video. The matching procedure',...
'reported the following results:',char(10), 'width:', num2str(match.width), char(10),...
'height:',num2str(match.height), char(10), '#frames:', num2str(match.frames), char(10),...
'format:',num2str(match.format), char(10), ...
'The former video will be reinstated (if any), and import will terminate'}));
vidObj = oldvidobj;
handles.videopath = oldvideopath;
handles.hvideopath.String = oldvideopath;
else
disp('The dimensions, format and frames# matches, importing contrast data.');
vidObj.Contrast = in.outgoing.GUIobj.vidObj.Contrast;
vidObj.GrayLvl = in.outgoing.GUIobj.vidObj.GrayLvl;
vidObj.LocContrast = in.outgoing.GUIobj.vidObj.LocContrast;
vidObj.Duration = in.outgoing.GUIobj.vidObj.Duration;
vidObj.Framerate = in.outgoing.GUIobj.vidObj.Framerate;
end
backdoorObj = BFPGUIbackdoor(@backdoorFunction); % preconstruct object connected to bd-function
BFPobj = []; % preconstruct empty object
% GUI data
for elm = 1:numel(GUIdata)
handles.(GUIdata{elm}) = in.outgoing.GUIdata.(GUIdata{elm});
end
makeTab(); % generates tab of selected intervals
handles.selecting = false; % selection processes are not imported
handles.playing = false; % video loaded in stopped state
% GUI flags
for elm = 1:numel(GUIflags.Enables)
handles.(GUIflags.Enables{elm}).Enable = in.outgoing.GUIflags.(GUIflags.Enables{elm}).Enable;
end
for elm = 1:numel(GUIflags.Strings)
handles.(GUIflags.Strings{elm}).String = in.outgoing.GUIflags.(GUIflags.Strings{elm}).String;
end
for elm = 1:numel(GUIflags.Values)
handles.(GUIflags.Values{elm}).Value = in.outgoing.GUIflags.(GUIflags.Values{elm}).Value;
end
for elm = 1:numel(GUIflags.Visibles)
handles.(GUIflags.Visibles{elm}).Visible = in.outgoing.GUIflags.(GUIflags.Visibles{elm}).Visible;
end
% objects
%vidObj = in.outgoing.GUIobj.vidObj; % object already duplicated
BFPobj = in.outgoing.GUIobj.BFPobj;
backdoorObj = in.outgoing.GUIobj.backdoorObj;
% generate stiffness annotation
if ~isempty(BFPobj) && ~isempty(BFPobj.k); makeStiffAnot(); end;
graphplot_callback(0,0);
end
% ====================================================================
%% ============== SINGLE-FRAME CALIBRATION FUNCTIONS =================
% The single frame calibration is necessary, if the reference distance
% frame (i.e. the frame, where the bead and the RBC just touch, with zero
% load incurred on the bead) is not part of any tracked interval. Then,
% the program attempts to semi-automatically create and add single-frame
% interval. User only needs to verify the pipette pattern was well
% matched and select the appropriate bead (in case more beads are
% present).
%% Create new figure, where user can calibrate the probe
% constructs the calibration figure, uicontrols etc.
function [hcalibfig]= buildCalibFig()
% build the controls
titstr = strjoin({'Calibration single-frame interval - frame',num2str(handles.calibint.reference)});
hcalibfig = figure('Name', titstr,'Units', 'normalized','WindowStyle','modal',...
'OuterPosition', [0.2,0.2,0.6,0.6], 'Visible', 'on', 'Selected', 'on',...
'CloseRequestFcn',{@calibfigCleanup_closereq});
hcalibax = axes('Parent',hcalibfig,'Units','normalized', 'Position', [0,0.3,1,0.6],...
'FontUnits','normalized');
hacceptpip = uicontrol('Parent', hcalibfig,'Style','pushbutton', ...
'Units', 'normalized', 'Position', [0.05,0.1,0.2,0.1],'FontUnits','normalized',...
'TooltipString','Accept pipette detection to finalise calibration',...
'String', '<HTML><center> Accept <br> Pipette </HTML>','Interruptible','on',...
'Callback', {@acceptpip_callback});
hgetbeadbtn = uicontrol('Parent', hcalibfig,'Style','pushbutton', ...
'Units', 'normalized', 'Position', [0.3,0.1,0.2,0.1],'FontUnits','normalized',...
'TooltipString','Define the bead for the distance calibration',...
'String', 'Get Bead','Interruptible','on', ...
'Callback', {@getcalibead_callback,hcalibax,handles.calibint.reference});
hfinishbtn = uicontrol('Parent', hcalibfig,'Style','pushbutton', ...
'Units', 'normalized', 'Position', [0.55,0.1,0.2,0.1],'FontUnits','normalized',...
'TooltipString','Accept calibration, return data to main function, and close this window',...
'String', 'Finish','Interruptible','on', 'Callback', {@acceptcalib_callback});
hcancelbtn = uicontrol('Parent', hcalibfig,'Style','pushbutton', ...
'Units', 'normalized', 'Position', [0.8,0.1,0.15,0.1],'FontUnits','normalized',...
'TooltipString','Close the window, discard changes, and abort interval addition',...
'String', '<HTML><center> Cancel <br> (reject pipette) </HTML> ',...
'Interruptible','on', 'Callback', {@calibfigCleanup_closereq});
set([hacceptpip,hgetbeadbtn,hfinishbtn,hcancelbtn],'Interruptible','on');
handles.calibint.acceptedpip = false; % add one field for this figure
% read the frame
oldframe = vidObj.CurrentFrame;
calibframe = vidObj.readFrame(handles.calibint.reference);
vidObj.readFrame(oldframe); % resets the original frame number in the main figure
imagesc(calibframe.cdata, 'Parent', hcalibax);
colormap(gray);
axis(hcalibax, 'image'); % set limits of the canvas to 'image'
hcalibax.FontUnits = 'normalized';
% detect and display the pattern in this frame
[ position, ~ ] = TrackPipette( vidObj, handles.calibint.pattern, [-1 -1],...
[handles.calibint.frames(1) handles.calibint.frames(2)], 'wideField',true );
handles.calibint.patcoor = [ position(2), position(1) ];
hold(hcalibax,'on');
rectangle('Parent', hcalibax, 'Position', ...
[position(2), position(1), size(handles.interval.pattern,2), size(handles.interval.pattern,1)],...
'EdgeColor','r','LineWidth', 2 );
hold(hcalibax,'off');
end
%% Accept input calibration information
% verifies the data and closes the calibration window
function acceptcalib_callback(~,~)
if ~isempty(handles.calibint.beadcoor) && ~isempty(handles.calibint.contrast) && handles.calibint.acceptedpip;
disp('All necessary data were measured, returning to the main window');
delete(gcf);
handles.calibint = rmfield(handles.calibint,'acceptedpip'); % remove locally used field
else
if ~handles.calibint.acceptedpip
warn(strjoin({'The delineated pipette tip alignment was not verified. Please',...
'either accept the pipette, or calcel the calibration altogether. If program is',...
'unable to detect the pipette pattern, You need to redesign Your tracking.'}));
else
warn(strjoin({'Bead was not properly selected. Please make sure the appropriate bead'...
'is chosen and appears delineated on the image.'}));
end
return;
end
end
%% Select bead for calibration
% allows user to select the appropriate bead
function getcalibead_callback(source,~,hax,frm)
persistent hviscirc;
if isgraphics(hviscirc); hviscirc.Visible = 'off'; end; % hide the circle
tag = 'interval';
[ beadinfo, pass, rad ] = getBead( source, tag, hax, 'frm', frm );
source.Callback = {@getcalibead_callback,hax,frm}; % reset the callback changed in getBead method
source.String = 'Get Bead';
if pass
handles.calibint.beadcoor = beadinfo.coor;
handles.calibint.contrast = beadinfo.contrast;
hviscirc = viscircles(hax,beadinfo.coor,rad,'EdgeColor','r');
else
warn(strjoin({'Bead detection was unsuccessful, please try again.',...
'Previos detection result, if any, was reverted.',...
'If the problems continue, please try to change Your calibration frame.'}));
end
if isgraphics(hviscirc); hviscirc.Visible = 'on'; end; % show the circle
end
%% Confirm detected pipette pattern
% confirm proper pipette pattern detection; note user cannot reposition
% the pipette pattern in the calibration interval. If program is unable
% to detect the pattern, it is unlikely this calibration would be of
% any use for a completely different interval
function acceptpip_callback(source,~)
handles.calibint.acceptedpip = true;
source.String = '<HTML><center> Reject <br> Pipette </HTML>';
source.TooltipString = 'Recall pipette confirmation and abort the calibration';
source.Callback = {@rejectpipette_callback};
end
%% Reject detected pipette pattern
% reject automatically detected pipette and abort the single-frame calibration
function rejectpipette_callback(~,~)
hw = warndlg(strjoin({'The incorrect detection of the pipette pattern in the calibration frame',...
'means, the pattern is not transferable and another reference frame must be chosen, or',...
'the pipette pattern tentatively expanded. The adding will abort and return to the main',...
'window.'}),'Pipette pattern detection rejected','modal');
uiwait(hw);
handles.calibint = []; % discard data
delete(gcf); % close calibration window
end
%% Close calibration window
% callback after close request of calibration figure
function calibfigCleanup_closereq(~,~)
choice = questdlg(strjoin({'You are about to cancel the calibration interval window.',...
'If You continue, currently collected calibration data will be discarded.',...
'The adding of an interval will be aborted and another form of calibration',...
'will have to be set up. Are You sure You want to quit?'}),...
'Quit outstanding calibration','Quit and abort','Continue','Continue');
switch choice
case 'Quit and abort'
handles.calibint = []; % clear calibration interval data
delete(gcf);
case {'Continue',''} % catch also (X)
return;
end
end
% =====================================================================
%% ======== PAY NO ATTENTION TO THE MAN BEHIND THE CURTAIN ============
% Like a wizard behind a curtain, this set of calls allows to unmess
% minor inconveniences, mostly resulting from errors, which leave various
% global variables switched to locked state. It can be called through
% backdoor object, from the command line.
function [status] = backdoorFunction( action )
status = false;
switch action
case 'reselect'
handles.selecting = false;
warning(strjoin({'Selection lock removed. This function is intended only to',...
'help You recover after fatal error. Hope You know well, what You''re doing.'}));
status = true;
case 'deadwaitbar'
set(groot,'ShowHiddenHandles','on');
saleGosse = get(groot,'Children');
for f=numel(saleGosse):-1:1
if strcmp(saleGosse(f).Tag, 'TMWWaitbar')
saleGosse(f).delete;
end
end
warning(strjoin({'Handles of the dead waitbars were killed. If You still see',...
'them, then they be undead. Good luck to You!'}));
status = true;
end
end
end
% last visit on July 23