This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
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?
IntrinsicImaging/WidgetIntrinsicAnalysis.m
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1351 lines (1002 sloc)
46.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |