Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
classdef WidgetIntrinsicAnalysis < handle
%WIDGETINTRINSICANALYSIS analyse streaming image data
properties
stream
adaptor
fps
bitdepth
resolution
clim
period
window
offset
filter
threshold
binning
frame
end
%% --- Export properties --- %%
properties (Access = private)
export_path
export_name
export_flag
export_writerVideo
export_writerMetaData
export_fileVideo
export_fileMetaData
export_fileBackground
export_fileSignal
export_fileIntrinsic
export_fileReference
export_fileROI
end
%% --- Analysis properties --- %%
properties (Access = public)
buffer_size
buffer_frames
buffer_stamps
buffer_baseline
buffer_signal
buffer_mask
index_trial
index_baselineFirst
index_baselineLast
index_signalFirst
index_signalLast
thread_analysis
thread_export
thread_present
end
%% --- visualization properties --- %%
properties (Access = private)
vw_parent
vw_axes
vw_image
vw_patch
vw_ellipse
vw_roi
vw_mask
bg_parent
bg_axes
bg_bars
bg_plot
end
%% --- UI properties --- %%
properties (Access = private)
ui_parent
ui_panel
ui_grid
ui_text_export
ui_checkbox_export
ui_pushbutton_export
ui_popup_binning
ui_edit_period
ui_edit_windowBefore
ui_edit_windowAfter
ui_edit_offsetBefore
ui_edit_offsetAfter
ui_text_threshold
ui_slider_threshold
ui_pushbutton_start
ui_pushbutton_stop
end
%% --- constant properties --- %%
properties (Constant = true, Access = private, Hidden = true)
UIWINDOW_SIZE = [1, 1, 256, 267];
GRID_VGAP = [12, 2, 5];
GRID_HGAP = [5, 2, 5];
BACKGROUND_COLOR = [1, 1, 1];
FOREGROUND_COLOR = [0.5, 0.5, 0.5];
PATCH_COLOR = [0,255,0];
ALPHA_VALUE = 0.5;
PUSHBUTTON_SIZE = [1, 1, 90, 26];
EDITBOX_SIZE = [1, 1, 60, 20];
CHECKBOX_SIZE = [1, 1, 90, 20];
SLIDER_SIZE = [1, 1, 240, 20];
BIN_SIZE = 100;
DEVICE_ID = 1;
BIT_MAX = [2^8, 2^12, 2^16] - 1;
GAUSSFILTER_SIZE = 32;
GAUSSFILTER_SIGMA = 5;
KEY_ENTER = 13;
BINNING_TYPE = {'x1', 'x2', 'x4'};
BINNING_VALUE = [1,2,4];
end
%% --- events --- %%
events (NotifyAccess = protected)
event_start
event_stop
event_trigger
end
%% --- constructor / destructor --- %%
methods
function obj = WidgetIntrinsicAnalysis(varargin)
%WIDGETINTRINSICANALYSIS class constructor
parserObj = inputParser;
addParameter(parserObj, 'Parent', [], @isgraphics);
parse(parserObj, varargin{:});
% set parent graphics objects
if isempty(parserObj.Results.Parent)
obj.ui_parent = figure(...
'Visible', 'on',...
'Tag', 'hIntrinsicAnalysis',...
'Name', 'Intrinsic Analysis',...
'MenuBar', 'none',...
'ToolBar', 'none',...
'NumberTitle', 'off',...
'Color', obj.BACKGROUND_COLOR,...
'Resize', 'off',...
'Units', 'pixels',...
'Position', obj.UIWINDOW_SIZE,...
'CloseRequestFcn', @obj.fcnCallback_closeUserInterface);
movegui(obj.ui_parent, 'northwest');
else
obj.ui_parent = parserObj.Results.Parent;
end
% set defaults values
obj.defaults();
% render user interface
obj.renderUserInterface();
end
function obj = dispose(obj)
%DISPOSE class destructor
% remove threads
if isa(obj.thread_analysis, 'timer')
delete(obj.thread_analysis);
end
if isa(obj.thread_export, 'timer')
delete(obj.thread_export);
end
% remove user interface grid
if isa(obj.ui_grid, 'uiGridLayout')
delete(obj.ui_grid);
end
% destroy bar graph figure
if isgraphics(obj.bg_parent, 'figure')
delete(obj.bg_parent);
end
% destroy viewer figure
if isgraphics(obj.vw_parent, 'figure')
delete(obj.vw_parent);
end
% check if parent is figure or was inherit
if isgraphics(obj.ui_parent, 'figure')
delete(obj.ui_parent);
end
% dispose class object
delete(obj);
end
function obj = defaults(obj)
%DEFAULTS
% allocate properties
obj.period = 8;
obj.window = [2, 2];
obj.offset = [0.5, 0.5];
obj.filter = fspecial('gaussian', [obj.GAUSSFILTER_SIZE, obj.GAUSSFILTER_SIZE], obj.GAUSSFILTER_SIGMA);
obj.export_name = sprintf('%s_IntrinsicExport', datestr(now,'yyyymmdd-HHMMSS'));
obj.export_path = pwd;
obj.export_flag = 0;
obj.threshold = 0.50;
% set threads
obj.thread_analysis = timer(...
'ExecutionMode', 'singleShot',...
'Name', 'ThreadIntrinsicAnalysis',...
'TimerFcn', @obj.fcnThread_analysis,...
'TasksToExecute', 1);
obj.thread_export = timer(...
'ExecutionMode', 'singleShot',...
'Name', 'ThreadVideoExport',...
'TimerFcn', @obj.fcnThread_export,...
'TasksToExecute', 1);
obj.thread_present = timer(...
'ExecutionMode', 'singleShot',...
'Name', 'ThreadAnalysisPresent',...
'TimerFcn', @obj.fcnThread_present,...
'TasksToExecute', 1);
end
function obj = renderUserInterface(obj)
%RENDERUSERINTERFACE
%%% --- create widget panel --- %%%
obj.ui_panel = uipanel(...
'Parent', obj.ui_parent,...
'Title', 'Analysis',...
'TitlePosition', 'lefttop',...
'BorderType', 'line',...
'HighlightColor', obj.FOREGROUND_COLOR,...
'ForegroundColor', obj.FOREGROUND_COLOR,...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'normalized',...
'Position', [0, 0, 1, 1],...
'Units', 'pixels');
%%% --- create grid object --- %%%
obj.ui_grid = uiGridLayout(...
'Parent', obj.ui_panel,...
'VGrid', 9,...
'HGrid', 4,...
'VGap', obj.GRID_VGAP,...
'HGap', obj.GRID_HGAP);
%%% --- export properties --- %%%
exportPath = fullfile(obj.export_path, obj.export_name);
obj.ui_text_export = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', ['...',exportPath(end-45:end)],...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 1, 'HIndex', 1:4));
obj.ui_grid.align(obj.ui_text_export,...
'VIndex', 1,...
'HIndex', 1:4,...
'Anchor', 'center');
obj.ui_pushbutton_export = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'pushbutton',...
'String', 'set export',...
'Enable', 'on',...
'Callback', @obj.fcnCallback_setExport,...
'Units', 'pixels',...
'Position', obj.PUSHBUTTON_SIZE);
obj.ui_grid.align(obj.ui_pushbutton_export,...
'VIndex', 2,...
'HIndex', 1:2,...
'Anchor', 'center');
obj.ui_checkbox_export = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'checkbox',...
'String', 'keep video',...
'Value', 0,...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Callback', @obj.fcnCallback_check,...
'Units', 'pixels',...
'Position', obj.CHECKBOX_SIZE);
obj.ui_grid.align(obj.ui_checkbox_export,...
'VIndex', 2,...
'HIndex', 3:4,...
'Anchor', 'center');
%%% --- software binning --- %%%
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'binning',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 3, 'HIndex', 3));
obj.ui_grid.align(ui_text,...
'VIndex', 3,...
'HIndex', 3,...
'Anchor', 'east');
obj.ui_popup_binning = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'popup',...
'String', obj.BINNING_TYPE,...
'Value', 1,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_popup_binning,...
'VIndex', 3,...
'HIndex', 4,...
'Anchor', 'east');
%%% --- stimuli properties --- %%%
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'period [sec]',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 3, 'HIndex', 1));
obj.ui_grid.align(ui_text,...
'VIndex', 3,...
'HIndex', 1,...
'Anchor', 'east');
obj.ui_edit_period = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%.2f', obj.period),...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_period,...
'VIndex', 3,...
'HIndex', 2,...
'Anchor', 'west');
%%% --- buffer properties --- %%%
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'before',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 4, 'HIndex', 3));
obj.ui_grid.align(ui_text,...
'VIndex', 4,...
'HIndex', 3,...
'Anchor', 'south');
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'after',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 4, 'HIndex', 4));
obj.ui_grid.align(ui_text,...
'VIndex', 4,...
'HIndex', 4,...
'Anchor', 'south');
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'window size [sec]',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 5, 'HIndex', 1:2));
obj.ui_grid.align(ui_text,...
'VIndex', 5,...
'HIndex', 1:2,...
'Anchor', 'east');
obj.ui_edit_windowBefore = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%.2f', obj.window(1)),...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_windowBefore,...
'VIndex', 5,...
'HIndex', 3,...
'Anchor', 'center');
obj.ui_edit_windowAfter = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%.2f', obj.window(2)),...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_windowAfter,...
'VIndex', 5,...
'HIndex', 4,...
'Anchor', 'center');
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'stimuli offset [sec]',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 6, 'HIndex', 1:2));
obj.ui_grid.align(ui_text,...
'VIndex', 6,...
'HIndex', 1:2,...
'Anchor', 'east');
obj.ui_edit_offsetBefore = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%.2f', obj.offset(1)),...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_offsetBefore,...
'VIndex', 6,...
'HIndex', 3,...
'Anchor', 'center');
obj.ui_edit_offsetAfter = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%.2f', obj.offset(2)),...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_offsetAfter,...
'VIndex', 6,...
'HIndex', 4,...
'Anchor', 'center');
%%% --- threshold slider --- %%%
obj.ui_text_threshold = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', sprintf('mask threshold %.2f',obj.threshold),...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 7, 'HIndex', 1:4));
obj.ui_grid.align(obj.ui_text_threshold,...
'VIndex', 7,...
'HIndex', 1:4,...
'Anchor', 'south');
obj.ui_slider_threshold = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'slider',...
'Min', 0,...
'Max', 1,...
'Value', 0.5,...
'SliderStep', [1/obj.BIN_SIZE, 1/(obj.BIN_SIZE/5)],...
'Callback', @obj.fcnCallback_slider,...
'Units', 'pixels',...
'Position', obj.SLIDER_SIZE);
obj.ui_grid.align(obj.ui_slider_threshold,...
'VIndex', 8,...
'HIndex', 1:4,...
'Anchor', 'center');
%%% --- push buttons --- %%%
obj.ui_pushbutton_start = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'pushbutton',...
'String', 'Start',...
'Enable', 'on',...
'Callback', @obj.fcnCallback_start,...
'Units', 'pixels',...
'Position', obj.PUSHBUTTON_SIZE);
obj.ui_grid.align(obj.ui_pushbutton_start,...
'VIndex', 9,...
'HIndex', 1:2,...
'Anchor', 'center');
obj.ui_pushbutton_stop = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'pushbutton',...
'String', 'Stop',...
'Enable', 'off',...
'Callback', @obj.fcnCallback_stop,...
'Units', 'pixels',...
'Position', obj.PUSHBUTTON_SIZE);
obj.ui_grid.align(obj.ui_pushbutton_stop,...
'VIndex', 9,...
'HIndex', 3:4,...
'Anchor', 'center');
end
function obj = renderVisualWindow(obj)
%RENDERVISUALWINDOW
% get screen resolution
screenResolution = get(0, 'ScreenSize');
screenResolution = floor(0.4 * screenResolution(3:4));
aspectRatioCam = obj.resolution(1) ./ obj.resolution(2);
previewWidth = screenResolution(1);
previewHeight = floor(aspectRatioCam * previewWidth);
obj.clim = [min(obj.frame(:)), max(obj.frame(:))];
% apply initial binning
obj.binning = obj.BINNING_VALUE(get(obj.ui_popup_binning, 'Value'));
if obj.binning > 1
obj.frame = imbinning(obj.frame, obj.binning, obj.binning);
obj.resolution = size(obj.frame);
end
% render image window handlers
obj.vw_parent = figure(...
'Visible','on',...
'MenuBar','none',...
'ToolBar','none',...
'Name','Reference Frame',...
'NumberTitle','off',...
'Resize', 'off',...
'Units','pixels',...
'Position', [1, 1, previewWidth, previewHeight],...
'WindowKeyPressFcn', @obj.fcnCallback_acceptROI,...
'CloseRequestFcn', @obj.fcnCallback_closeViewerWindow);
movegui(obj.vw_parent, 'north');
obj.vw_axes = axes(...
'Parent', obj.vw_parent,...
'Visible','off',...
'Units', 'pixels',...
'Position', [1, 1, previewWidth, previewHeight]);
obj.vw_image = image(...
obj.frame,...
'Parent', obj.vw_axes,...
'CDataMapping', 'scaled');
set(obj.vw_axes,...
'XTick', [],...
'YTick', [],...
'CLim', obj.clim);
colormap(obj.vw_axes, 'gray');
obj.vw_mask = false(obj.resolution);
hold(obj.vw_axes, 'on');
obj.vw_patch = image(...
repmat(shiftdim(obj.PATCH_COLOR, -1), obj.resolution(1), obj.resolution(2)),...
'Parent', obj.vw_axes,...
'CDataMapping', 'scaled',...
'AlphaData', obj.vw_mask);
hold(obj.vw_axes,'off');
% user defined ROI
obj.vw_ellipse = imellipse(obj.vw_axes);
end
function obj = renderBarGraph(obj)
%RENDERBARGRAPH interactive signal difference graph
viewerPosition = get(obj.vw_parent, 'Position');
graphHeight = floor(viewerPosition(4) / 2);
graphWidth = floor(viewerPosition(3) / 2);
% create a figure output
obj.bg_parent = figure(...
'Visible', 'on',...
'MenuBar', 'none',...
'ToolBar', 'none',...
'Name', 'Difference Histogram',...
'NumberTitle', 'off',...
'Color', obj.BACKGROUND_COLOR,...
'Resize', 'on',...
'Units', 'pixels',...
'Position', [1, 1, graphWidth, graphHeight],...
'CloseRequestFcn', @obj.fcnCallback_closeBarGraph);
movegui(obj.bg_parent, 'southeast');
obj.bg_axes = axes('Parent', obj.bg_parent);
obj.buffer_mask = double(obj.frame) ./ obj.BIT_MAX(obj.bitdepth);
data = obj.buffer_mask(obj.vw_roi);
obj.vw_mask = obj.buffer_mask;
[y,x] = histcounts( data(:), obj.BIN_SIZE);
x = x(1:end-1)+diff(x)./2;
step = max(x)*0.01;
obj.bg_bars = bar(x, y, 0.6, 'EdgeColor',[.4,.4,.4], 'FaceColor',[.75,.75,.75]);
set(obj.bg_axes,'XLim',[min(x)-step, max(x)+step]);
set(obj.ui_slider_threshold, 'Value', obj.threshold);
hold(obj.bg_axes, 'on');
obj.bg_plot = plot(obj.bg_axes, [obj.threshold, obj.threshold], get(obj.bg_axes,'YLim'), 'r-.','LineWidth', 1.2);
hold(obj.bg_axes, 'off');
xlabel(obj.bg_axes,'baseline / signal','FontSize',12);
ylabel(obj.bg_axes,'frequency','FontSize', 12);
end
function obj = enable(obj, varstate)
%ENABLE toggle on/off enable property
if strcmp('on', varstate) || strcmp('off', varstate)
set(obj.ui_edit_period, 'Enable', varstate);
set(obj.ui_edit_windowBefore, 'Enable', varstate);
set(obj.ui_edit_windowAfter, 'Enable', varstate);
set(obj.ui_edit_offsetBefore, 'Enable', varstate);
set(obj.ui_edit_offsetAfter, 'Enable', varstate);
set(obj.ui_checkbox_export, 'Enable', varstate);
set(obj.ui_popup_binning, 'Enable', varstate);
set(obj.ui_pushbutton_export, 'Enable', varstate);
end
end
end
%% --- user callbacks --- %%
methods
function obj = fcnCallback_closeUserInterface(obj, ~, ~)
%FCNCALLBACK_CLOSEUSERINTERFACE request destructor
obj.dispose();
end
function obj = fcnCallback_closeViewerWindow(obj, ~, ~)
%FCNCALLBACK_CLOSEVIEWERWINDOW toggle viewer property
set(obj.vw_parent, 'Visible', 'off');
end
function obj = fcnCallback_closeBarGraph(obj, ~, ~)
%FCNCALLBACK_CLOSEBARGRAPH toggle viewer property
set(obj.bg_parent, 'Visible', 'off');
end
function obj = fcnCallback_parse(obj, hSrc, ~)
%FCNCALLBACK_PARSE parse edit box
% parse edin box
varchar = get(hSrc, 'String');
varnum = str2double(regexp(varchar, '[0-9]+\.?[0-9]*', 'match'));
varnum(isempty(varnum)) = 0;
% assign to property
switch hSrc
case obj.ui_edit_period
% check for min period available
minValue = sum(obj.window) + sum(obj.offset);
if varnum < minValue
varnum = minValue;
end
set(obj.ui_edit_period, 'String', sprintf('%.2f', varnum));
obj.period = varnum;
case obj.ui_edit_windowBefore
% check if window fits in period
maxValue = obj.period - obj.window(2) - sum(obj.offset);
if varnum > maxValue
varnum = maxValue;
end
set(obj.ui_edit_windowBefore, 'String', sprintf('%.2f', varnum));
obj.window(1) = varnum;
case obj.ui_edit_windowAfter
% check if window fits in period
maxValue = obj.period - obj.window(1) - sum(obj.offset);
if varnum > maxValue
varnum = maxValue;
end
set(obj.ui_edit_windowAfter, 'String', sprintf('%.2f', varnum));
obj.window(2) = varnum;
case obj.ui_edit_offsetBefore
% check if window fits in period
maxValue = obj.period - obj.offset(2) - sum(obj.window);
if varnum > maxValue
varnum = maxValue;
end
set(obj.ui_edit_offsetBefore, 'String', sprintf('%.2f', varnum));
obj.offset(1) = varnum;
case obj.ui_edit_offsetAfter
% check if window fits in period
maxValue = obj.period - obj.offset(1) - sum(obj.window);
if varnum > maxValue
varnum = maxValue;
end
set(obj.ui_edit_offsetAfter, 'String', sprintf('%.2f', varnum));
obj.offset(2) = varnum;
end
end
function obj = fcnCallback_check(obj, ~, ~)
%FCNCALLBACK_CHECKBOX toggle use state
obj.export_flag = get(obj.ui_checkbox_export, 'Value');
end
function obj = fcnCallback_slider(obj, ~, ~)
%FCNCALLBACK_SLIDER
obj.threshold = get(obj.ui_slider_threshold, 'Value');
set(obj.ui_text_threshold,...
'String', sprintf('mask threshold %.2f',obj.threshold));
obj.ui_grid.align(obj.ui_text_threshold,...
'VIndex', 7,...
'HIndex', 1:4,...
'Anchor', 'south');
if isgraphics(obj.bg_plot, 'line')
set(obj.bg_plot, 'XData', [obj.threshold, obj.threshold]);
obj.adjustMask();
end
end
function obj = fcnCallback_setExport(obj, ~, ~)
%FCNCALLBACK_SETEXPORT set user defined export
% open dialog box for saving files
obj.export_name = sprintf('%s_IntrinsicExport', datestr(now,'yyyymmdd-HHMMSS'));
[fileName, pathName] = uiputfile('*.*', 'Save experiment as', obj.export_name);
% set new file name
if (fileName ~= 0)
obj.export_name = fileName;
end
% set new path
if (pathName ~= 0)
obj.export_path = pathName;
end
% update export message
exportPath = fullfile(obj.export_path, obj.export_name);
if length(exportPath) > 45
exportPath = ['...', exportPath(end-45:end)];
end
set(obj.ui_text_export, 'String', exportPath);
obj.ui_grid.align(obj.ui_text_export,...
'VIndex', 1,...
'HIndex', 1:4,...
'Anchor', 'south');
end
function obj = fcnCallback_acceptROI(obj, ~, ~)
%FCNCALLBACK_ACCEPTROI
key_char = get(obj.vw_parent, 'CurrentCharacter');
if uint8(key_char) == obj.KEY_ENTER
obj.vw_roi = createMask(obj.vw_ellipse, obj.vw_image);
delete(obj.vw_ellipse);
obj.start();
end
end
function obj = fcnCallback_start(obj, ~, ~)
%FCNCALLBACK_START initialize run
% emit start event
notify(obj, 'event_start');
end
function obj = fcnCallback_stop(obj, ~, ~)
%FCNCALLBACK_STOP stop run
% emit stop event
notify(obj, 'event_stop');
obj.stop();
end
end
%% --- functional methods --- %%
methods
function obj = initialize(obj)
%START initialize and start experiment
% toggle UI components
obj.enable('off');
set(obj.ui_pushbutton_start, 'enable', 'off');
set(obj.ui_pushbutton_stop, 'enable', 'on');
% initialize Run
obj.initializeBuffers();
% initialize Export
obj.initializeExport();
% initialize Stream
obj.initializeStream();
% render visual window
obj.renderVisualWindow();
end
function obj = start(obj)
%START experiment
% render bar graph
obj.renderBarGraph();
% adjust mask
obj.adjustMask();
% start video stream
if isa(obj.stream, 'videoinput')
start(obj.stream);
end
end
function obj = stop(obj)
%STOP user requested experiment stop
% toggle UI components
obj.enable('on');
set(obj.ui_pushbutton_start, 'enable', 'on');
set(obj.ui_pushbutton_stop, 'enable', 'off');
% stop video object
if isa(obj.stream, 'videoinput')
stop(obj.stream);
end
% terminate export
obj.terminateExport();
% reset properties
obj.frame = [];
obj.export_name = sprintf('%s_IntrinsicExport', datestr(now,'yyyymmdd-HHMMSS'));
% update export message
exportPath = fullfile(obj.export_path, obj.export_name);
if length(exportPath) > 45
exportPath = ['...', exportPath(end-45:end)];
end
set(obj.ui_text_export, 'String', exportPath);
obj.ui_grid.align(obj.ui_text_export,...
'VIndex', 1,...
'HIndex', 1:4,...
'Anchor', 'south');
% close windows
delete(obj.vw_parent);
delete(obj.bg_parent);
end
function obj = initializeExport(obj)
%INITIALIZEEXPORT prepare output files
% create export folder
exportFolder = fullfile(obj.export_path, obj.export_name);
if exist(exportFolder, 'dir') == 0
mkdir(exportFolder);
else
% export name alread exist, update
obj.export_name = sprintf('%s_%s',...
datestr(now,'yyyymmdd-HHMMSS'),...
obj.export_name);
% new export folder
exportFolder = fullfile(obj.export_path, obj.export_name);
mkdir(exportFolder);
end
% set filenames
obj.export_fileBackground = [exportFolder, filesep,...
obj.export_name,...
'_background.tif'];
obj.export_fileSignal = [exportFolder, filesep,...
obj.export_name,...
'_signal.tif'];
obj.export_fileReference = [exportFolder, filesep,...
obj.export_name,...
'_reference.tif'];
obj.export_fileROI = [exportFolder, filesep,...
obj.export_name,...
'_roi.tif'];
obj.export_fileMetaData = [exportFolder, filesep,...
obj.export_name,...
'_metadata.txt'];
obj.export_fileIntrinsic = [exportFolder, filesep,...
obj.export_name,...
'_intrinsic.png'];
% export meta data
obj.export_writerMetaData = fopen(obj.export_fileMetaData, 'w');
% log current settings
fprintf(obj.export_writerMetaData, '# experiment\t%s\n', obj.export_name);
fprintf(obj.export_writerMetaData, '# stimuli_period\t%.2f\n', obj.period);
fprintf(obj.export_writerMetaData, '# analysis_window\t%.2f, %.2f\n', obj.window);
fprintf(obj.export_writerMetaData, '# analysis_offset\t%.2f, %.2f\n', obj.offset);
fprintf(obj.export_writerMetaData, '# fps\t%.4f\n', obj.fps);
fprintf(obj.export_writerMetaData, '# frames_per_trial\t%d\n', obj.buffer_size);
fprintf(obj.export_writerMetaData, '# background averages\t%s_background.tif\n', obj.export_name);
fprintf(obj.export_writerMetaData, '# signal averages\t%s_signal.tif\n', obj.export_name);
fprintf(obj.export_writerMetaData, '# reference frame\t%s_reference.tif\n', obj.export_name);
fprintf(obj.export_writerMetaData, '# ROI\t%s_roi.tif\n', obj.export_name);
fprintf(obj.export_writerMetaData, '# intrinsic result\t%s_intrinsic.tif\n', obj.export_name);
% set video writer if specified
if obj.export_flag == 1
% log video export file
fprintf(obj.export_writerMetaData, '# video stream\t%s_videostream.mj2\n', obj.export_name);
% open video writer object
obj.export_fileVideo = [exportFolder, filesep,...
obj.export_name,...
'_videostream.avi'];
obj.export_writerVideo = VideoWriter(obj.export_fileVideo, 'Grayscale AVI');
set(obj.export_writerVideo, 'FrameRate', ceil(obj.fps));
open(obj.export_writerVideo);
end
end
function obj = terminateExport(obj)
%TERMINATEEXPORT close all files
% save reference frame
imwrite(obj.frame, obj.export_fileReference);
% save reference mask
imwrite(uint8(255.*obj.vw_roi), obj.export_fileROI);
% scale frame
frameRaw = double(obj.frame);
frameMax = max(frameRaw(:));
frameMin = min(frameRaw(:));
frameScaled = (frameRaw - frameMin) ./ (frameMax - frameMin);
% adjust mask
obj.adjustMask();
mask = obj.vw_mask > 0;
alpha = (frameScaled + obj.ALPHA_VALUE .* obj.vw_mask) ./ 2;
% convert output frame to 8bit
frameOut = uint8(255 .* frameScaled);
resultFrame = repmat(frameOut, 1, 1, 3);
% apply alpha mask
for c = 1 : 3
tmp = resultFrame(:,:,c);
tmp(mask) = obj.PATCH_COLOR(c) .* alpha(mask);
resultFrame(:,:,c) = tmp;
end
imwrite(resultFrame, obj.export_fileIntrinsic);
% close metadata file
fclose(obj.export_writerMetaData);
% check if video writer was active
if (obj.export_flag == 1)
% wait for export thread to finish
while strcmp(obj.thread_export, 'on')
pause(0.1);
end
% close video writer
close(obj.export_writerVideo);
end
end
function obj = initializeBuffers(obj)
%INITIALIZEBUFFERS set buffers and stream properties
% define indexes
obj.buffer_size = ceil(obj.period * obj.fps);
obj.index_trial = 0;
obj.index_baselineFirst = floor((obj.period - obj.window(1) - obj.offset(1)) * obj.fps);
obj.index_baselineLast = floor((obj.period - obj.offset(1)) * obj.fps);
obj.index_signalFirst = ceil(obj.offset(2) * obj.fps);
obj.index_signalLast = ceil((obj.offset(2) + obj.window(2)) * obj.fps);
end
function obj = initializeStream(obj)
% set video stream
obj.stream = videoinput(obj.adaptor, obj.DEVICE_ID);
% set camera properties
set(obj.stream,...
'ReturnedColorSpace', 'grayscale',...
'LoggingMode', 'memory',...
'FramesAcquiredFcn', @obj.fcnCamera_acquire,...
'FramesAcquiredFcnCount', obj.buffer_size,...
'FramesPerTrigger', obj.buffer_size,...
'StartFcn', @obj.fcnCamera_start,...
'StopFcn', @obj.fcnCamera_stop,...
'TriggerRepeat', Inf);
triggerconfig(obj.stream, 'immediate');
end
function obj = adjustMask(obj)
% adjust mask
obj.vw_mask(obj.vw_mask < obj.threshold) = 0;
obj.vw_mask(~obj.vw_roi) = 0;
set(obj.vw_patch, 'AlphaData', obj.vw_mask .* obj.ALPHA_VALUE);
end
end
%% --- camera callbacks --- %%
methods
function obj = fcnCamera_start(obj, ~, ~)
%FCNCAMERA_START starts camera acquisition
eventLog = get(obj.stream, 'EventLog');
fprintf('Start rec :: %s\n', ...
datestr(eventLog(1).Data.AbsTime,...
'dd-mmm-yyyy HH:MM:SS.FFF'));
end
function obj = fcnCamera_acquire(obj, hSrc, ~)
%FCNCAMERA_ACQUIRE
% trigger external stimuli
notify(obj, 'event_trigger');
% update trial index
obj.index_trial = obj.index_trial + 1;
% read all available frames from buffer
framesCount = get(hSrc, 'FramesAvailable');
[frames, obj.buffer_stamps] = getdata(hSrc, framesCount);
obj.buffer_frames = squeeze(frames);
% start analysis thread
start(obj.thread_analysis);
end
function obj = fcnCamera_stop(obj, ~, ~)
%FCNCAMERA_STOP starts camera acquisition
eventLog = get(obj.stream, 'EventLog');
fprintf('Stop rec :: %s\n', ...
datestr(eventLog(end).Data.AbsTime,...
'dd-mmm-yyyy HH:MM:SS.FFF'));
end
end
%% --- Threads Callbacks --- %%
methods
function obj = fcnThread_analysis(obj, ~, ~)
%FCNTHREAD_evalAVERAGE calculate average difference
% calculate average frames
obj.buffer_baseline = mean(obj.buffer_frames(:,:,obj.index_baselineFirst : obj.index_baselineLast), 3);
obj.buffer_signal = mean(obj.buffer_frames(:, :, obj.index_signalFirst : obj.index_signalLast), 3);
% apply binning
if obj.binning > 1
obj.buffer_baseline = imbinning(obj.buffer_baseline, obj.binning, obj.binning);
obj.buffer_signal = imbinning(obj.buffer_signal, obj.binning, obj.binning);
end
% calculate weights
weightHistory = (obj.index_trial - 1)/obj.index_trial;
weightCurrent = 1 / obj.index_trial;
% update current difference
diffMask = double(obj.buffer_signal) ./ double(obj.buffer_baseline);
diffMask = imfilter(diffMask, obj.filter, 'replicate');
diffMask = max(diffMask(:)) - diffMask; % invert mask
% accumulate mask difference
if obj.index_trial == 1
obj.buffer_mask = diffMask;
else
obj.buffer_mask = weightHistory .* obj.buffer_mask + ...
weightCurrent .* diffMask;
end
% start visualising thread
start(obj.thread_present);
% start export thread
start(obj.thread_export);
end
function obj = fcnThread_export(obj, ~, ~)
%FCNTHREAD_EXPORT records video stream
% retrieve event Log
eventLog = get(obj.stream, 'EventLog');
% user message
fprintf('Trial %03d :: %s\n', ...
obj.index_trial,...
datestr(eventLog(obj.index_trial + 1).Data.AbsTime,...
'dd-mmm-yyyy HH:MM:SS.FFF'));
% write to meta data
fprintf(obj.export_writerMetaData, ...
'# trial%03d\t%s\n', ...
obj.index_trial,...
datestr(eventLog(obj.index_trial + 1).Data.AbsTime,...
'dd-mmm-yyyy HH:MM:SS.FFF'));
% rescale image for export
if obj.bitdepth == 1
exportBackground = uint8(floor(obj.buffer_baseline));
exportSignal = uint8(floor(obj.buffer_signal));
else
exportBackground = uint16(floor(obj.buffer_baseline));
exportSignal = uint16(floor(obj.buffer_signal));
end
% check trial
if obj.index_trial == 1
% write current trial
imwrite(exportBackground, obj.export_fileBackground);
imwrite(exportSignal, obj.export_fileSignal);
else
% write current trial
imwrite(exportBackground, obj.export_fileBackground, 'WriteMode','append');
imwrite(exportSignal, obj.export_fileSignal, 'WriteMode','append');
end
% export video stream
if obj.export_flag == 1
framesCount = size(obj.buffer_frames, 3);
for k = 1 : framesCount
% 16 bit format
lowerBits = uint8(bitand(obj.buffer_frames(:,:,k), 255));
higherBits = uint8(bitand(bitshift(obj.buffer_frames(:,:,k),-8), 255));
writeVideo(obj.export_writerVideo, lowerBits);
writeVideo(obj.export_writerVideo, higherBits);
fprintf(obj.export_writerMetaData, 'trial%d\tframe%d\t%.2f\n',...
obj.index_trial,...
k,...
obj.buffer_stamps(k));
end
fprintf(obj.export_writerMetaData, 'trial%d\ttrigger\t%.2f\n',...
obj.index_trial,...
obj.buffer_stamps(k));
end
end
function obj = fcnThread_present(obj, ~, ~)
%FCNTHREAD_PRESENT
% scale difference frame
maskMin = min(obj.buffer_mask(obj.vw_roi));
maskMax = max(obj.buffer_mask(obj.vw_roi));
obj.vw_mask = (obj.buffer_mask - maskMin) ./ (maskMax - maskMin);
% update current bar graph
[y, x] = histcounts(obj.vw_mask(obj.vw_roi), obj.BIN_SIZE);
x = x(1:end-1) + diff(x)./2;
set(obj.bg_bars, 'XData', x);
set(obj.bg_bars, 'YData', y);
step = 0.01 * max(x);
set(obj.bg_axes, 'XLim',[min(x) - step, max(x) + step]);
set(obj.bg_plot, 'XData', [obj.threshold, obj.threshold]);
set(obj.bg_plot, 'YData', [0, max(y)]);
% adjust mask presentation
obj.adjustMask();
end
end
end
function [img] = imbinning(img, hbin, wbin)
%IMBINNING software image binning
[h, w]=size(img);
% horizontal binning
img = sum(reshape(img, hbin, []) , 1);
img = reshape(img , h/hbin, []).';
% vertical binning
img = sum(reshape(img, wbin, []) ,1);
img = reshape(img, w/wbin, []).';
% renormalize sum
img = img / (hbin * wbin);
end