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/WidgetVideoAcquisition.m
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
623 lines (440 sloc)
19.2 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 WidgetVideoAcquisition < handle | |
%WIDGETVIDEOACQUISITION | |
% initialize video stream | |
properties | |
stream | |
adaptor | |
device | |
resolution | |
fps | |
bitdepth | |
cdata | |
snapshot | |
autolim | |
flim | |
clim | |
thread | |
end | |
%% --- user interface properties --- %% | |
properties (Access = private) | |
ui_parent | |
ui_grid | |
ui_panel | |
ui_text_device | |
ui_text_status | |
ui_checkbox_auto | |
ui_pushbutton_grab | |
ui_pushbutton_preview | |
end | |
%% --- preview window properties --- %% | |
properties (Access = private) | |
pw_parent | |
pw_axes | |
pw_image | |
pw_patch | |
pw_mask | |
pw_state | |
end | |
%% --- constan properties --- %% | |
properties (Access = private, Constant = true, Hidden = true) | |
UIWINDOW_SIZE = [1, 1, 256, 99]; | |
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 = [255, 0, 0]; | |
SATURATION_THRESHOLD = 0.995; | |
PUSHBUTTON_SIZE = [1, 1, 90, 26]; | |
CHECKBOX_SIZE = [1, 1, 45, 20]; | |
PREVIEW_SCREEN_RATIO = 0.4 | |
DEVICE_ID = 1; | |
BIT_LABEL = {'8 bit', '12 bit', '16 bit'}; | |
BIT_MAX = [2^8, 2^12, 2^16] - 1; | |
FRAMES_PER_TRIGGER = 10; | |
STATE_STREAM = 0; | |
STATE_SNAPSHOT = 1; | |
end | |
%% --- constructor / destructor methods --- %% | |
methods | |
function obj = WidgetVideoAcquisition(varargin) | |
%WIDGETVIDEOACQUISITION class constructor | |
% use input parser | |
parserObj = inputParser; | |
addParameter(parserObj, 'Parent', [], @isgraphics); | |
addParameter(parserObj, 'Adaptor', [], @ischar); | |
parse(parserObj, varargin{:}); | |
% set parent graphics object | |
if isempty(parserObj.Results.Parent) | |
obj.ui_parent = figure(... | |
'Visible', 'on',... | |
'Tag', 'hVideoAcquisition',... | |
'Name', 'Video Acquisition',... | |
'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 device adaptor | |
obj.adaptor = parserObj.Results.Adaptor; | |
% recognize device adaptor | |
obj.setVideoAdaptor(); | |
% recognize video properties | |
obj.setVideoProperties(); | |
% initialize video stream | |
obj.setStreamObject(); | |
% render user interface | |
obj.renderUserInterface(); | |
% render preview window | |
obj.renderPreviewWindow(); | |
% start video stream | |
start(obj.stream); | |
end | |
function obj = dispose(obj) | |
%DISPOSE class destructor | |
if isa(obj.thread, 'timer') | |
stop(obj.thread); | |
delete(obj.thread); | |
end | |
if isa(obj.stream, 'videoinput') | |
stop(obj.stream); | |
end | |
if isgraphics(obj.pw_parent, 'figure') | |
delete(obj.pw_parent); | |
end | |
if isa(obj.ui_grid, 'uiGridLayout') | |
delete(obj.ui_grid); | |
end | |
if isgraphics(obj.ui_parent, 'figure') | |
delete(obj.ui_parent); | |
end | |
delete(obj); | |
end | |
function obj = renderUserInterface(obj) | |
%RENDERUSERINTERFACE | |
%%% --- create widget panel --- %%% | |
obj.ui_panel = uipanel(... | |
'Parent', obj.ui_parent,... | |
'Title', 'Video Acquisition',... | |
'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', 3,... | |
'HGrid', 4,... | |
'VGap', obj.GRID_VGAP,... | |
'HGap', obj.GRID_HGAP); | |
obj.ui_text_device = uicontrol(... | |
'Parent', obj.ui_panel,... | |
'Style', 'text',... | |
'String', sprintf('%s, %s, (%d x %d)',obj.device, obj.adaptor, obj.resolution),... | |
'BackgroundColor', obj.BACKGROUND_COLOR,... | |
'Units', 'pixels',... | |
'Position', obj.ui_grid.getGrid('VIndex', 1, 'HIndex', 1:2)); | |
obj.ui_grid.align(obj.ui_text_device,... | |
'VIndex', 1,... | |
'HIndex', 1:4,... | |
'Anchor', 'center'); | |
obj.ui_text_status = uicontrol(... | |
'Parent', obj.ui_panel,... | |
'Style', 'text',... | |
'String', sprintf('%s, FPS %.2f, VL [%d %d]',... | |
obj.BIT_LABEL{obj.bitdepth},... | |
obj.fps,... | |
obj.clim),... | |
'BackgroundColor', obj.BACKGROUND_COLOR,... | |
'Units', 'pixels',... | |
'Position', obj.ui_grid.getGrid('VIndex', 2, 'HIndex', 1:2)); | |
obj.ui_grid.align(obj.ui_text_status,... | |
'VIndex', 2,... | |
'HIndex', 1:3,... | |
'Anchor', 'center'); | |
obj.ui_checkbox_auto = uicontrol(... | |
'Parent', obj.ui_panel,... | |
'Style', 'checkbox',... | |
'String', 'auto',... | |
'Value', 1,... | |
'BackgroundColor', obj.BACKGROUND_COLOR,... | |
'Callback', @obj.fcnCallback_checkAutoLimits,... | |
'Units', 'pixels',... | |
'Position', obj.CHECKBOX_SIZE); | |
obj.ui_grid.align(obj.ui_checkbox_auto,... | |
'VIndex', 2,... | |
'HIndex', 4,... | |
'Anchor', 'center'); | |
obj.ui_pushbutton_preview = uicontrol(... | |
'Parent', obj.ui_panel,... | |
'Style', 'pushbutton',... | |
'String', 'Preview',... | |
'Callback',@obj.fcnCallback_preview,... | |
'Units', 'pixels',... | |
'Position', obj.PUSHBUTTON_SIZE); | |
obj.ui_grid.align(obj.ui_pushbutton_preview,... | |
'VIndex', 3,... | |
'HIndex', 1:2,... | |
'Anchor', 'center'); | |
obj.ui_pushbutton_grab = uicontrol(... | |
'Parent', obj.ui_panel,... | |
'Style', 'pushbutton',... | |
'String', 'Grab',... | |
'Callback',@obj.fcnCallback_grab,... | |
'Units', 'pixels',... | |
'Position', obj.PUSHBUTTON_SIZE); | |
obj.ui_grid.align(obj.ui_pushbutton_grab,... | |
'VIndex', 3,... | |
'HIndex', 3:4,... | |
'Anchor', 'center'); | |
end | |
function obj = renderPreviewWindow(obj) | |
%RENDERPREVIEWWINDOW | |
% get screen resolution | |
screenResolution = get(0, 'ScreenSize'); | |
screenResolution = floor(obj.PREVIEW_SCREEN_RATIO * screenResolution(3:4)); | |
aspectRatioCam = obj.resolution(1) ./ obj.resolution(2); | |
previewWidth = screenResolution(1); | |
previewHeight = floor(aspectRatioCam * previewWidth); | |
% render image window handlers | |
obj.pw_parent = figure(... | |
'Visible','on',... | |
'MenuBar','none',... | |
'ToolBar','none',... | |
'Name','video stream',... | |
'NumberTitle','off',... | |
'Resize', 'off',... | |
'Units','pixels',... | |
'Position', [1, 1, previewWidth, previewHeight],... | |
'CloseRequestFcn', @obj.fcnCallback_closeViewerWindow); | |
movegui(obj.pw_parent, 'north'); | |
obj.pw_axes = axes(... | |
'Parent', obj.pw_parent,... | |
'Visible','off',... | |
'Units', 'pixels',... | |
'Position', [1, 1, previewWidth, previewHeight]); | |
obj.pw_image = image(... | |
obj.cdata,... | |
'Parent', obj.pw_axes,... | |
'CDataMapping', 'scaled'); | |
set(obj.pw_axes,... | |
'XTick', [],... | |
'YTick', [],... | |
'CLim', obj.clim); | |
colormap(obj.pw_axes, 'gray'); | |
obj.pw_mask = false(size(obj.cdata)); | |
hold(obj.pw_axes, 'on'); | |
obj.pw_patch = image(... | |
repmat(shiftdim(obj.PATCH_COLOR, -1), obj.resolution(1), obj.resolution(2)),... | |
'Parent', obj.pw_axes,... | |
'CDataMapping', 'scaled',... | |
'AlphaData', obj.pw_mask); | |
hold(obj.pw_axes,'off'); | |
% activate preview state | |
obj.pw_state = obj.STATE_STREAM; | |
end | |
end | |
%% --- user interface callbacks --- %% | |
methods | |
function obj = fcnCallback_closeUserInterface(obj, ~, ~) | |
%FCNCALLBACK_CLOSE close class destructor | |
obj.dispose(); | |
end | |
function obj = fcnCallback_closeViewerWindow(obj, ~, ~) | |
%FCNCALLBACK_CLOSEVIEWERWINDOW toggle visibility property | |
set(obj.pw_parent, 'Visible', 'off'); | |
end | |
function obj = fcnCallback_checkAutoLimits(obj, ~, ~) | |
%FCNCALLBACK_CHECKAUTOLIMITS | |
% update autolimit | |
obj.autolim = get(obj.ui_checkbox_auto, 'Value'); | |
% update snapshot limits | |
if (obj.pw_state == obj.STATE_SNAPSHOT) | |
if obj.autolim == 1 | |
set(obj.pw_axes, 'CLim', obj.clim); | |
else | |
set(obj.pw_axes, 'CLim', [0, obj.BIT_MAX(obj.bitdepth)]); | |
end | |
end | |
end | |
function obj = fcnCallback_preview(obj, ~, ~) | |
%FCNCALLBACK_preview activates preview stream on camera | |
obj.preview(); | |
end | |
function obj = fcnCallback_grab(obj, ~, ~) | |
%FCNCALLBACK_grab gets current frame | |
obj.grab(); | |
end | |
end | |
%% --- functional methods --- %% | |
methods | |
function obj = setVideoAdaptor(obj) | |
%SETVIDEOADAPTOR determine video adapter | |
hwinfo = imaqhwinfo(); | |
adaptors = hwinfo.InstalledAdaptors; | |
if ~any(strcmp(obj.adaptor, adaptors)) | |
switch computer | |
case 'PCWIN64' | |
obj.adaptor = 'winvideo'; | |
case 'GLNXA64' | |
obj.adaptor = 'linuxvideo'; | |
case 'MACI64' | |
obj.adaptor = 'macvideo'; | |
end | |
end | |
end | |
function obj = setVideoProperties(obj) | |
%SETVIDEOPROPERTIES calculates FPS and resolution | |
% shoot 50 frames to test camera | |
deviceInfo = imaqhwinfo(obj.adaptor, obj.DEVICE_ID); | |
obj.device = deviceInfo.DeviceName; | |
vidobj = videoinput(obj.adaptor, obj.DEVICE_ID); | |
set(vidobj,... | |
'ReturnedColorSpace', 'grayscale',... | |
'LoggingMode', 'memory',... | |
'FramesPerTrigger', 50,... | |
'TriggerFrameDelay', 5); | |
start(vidobj); | |
wait(vidobj, Inf); | |
stop(vidobj); | |
[frames, timeStamp] = getdata(vidobj); | |
frames = squeeze(frames); | |
% set fps | |
obj.fps = 1 / mean(diff(timeStamp)); | |
% set stream resolution | |
obj.resolution = vidobj.VideoResolution; | |
obj.resolution = fliplr(obj.resolution); | |
% set frame limits | |
obj.autolim = 1; | |
obj.flim = [min(frames(:)), max(frames(:))]; | |
% remove stream obj | |
delete(vidobj); | |
% set preview limits | |
obj.setPreviewLimits(); | |
% initialize cdata | |
obj.cdata = frames(:,:,end); | |
end | |
function obj = setStreamObject(obj) | |
%SETSTREAMOBJECT initialize video stream object | |
% initialize stream object | |
obj.stream = videoinput(obj.adaptor, obj.DEVICE_ID); | |
set(obj.stream,... | |
'ReturnedColorSpace', 'grayscale',... | |
'LoggingMode', 'memory',... | |
'FramesAcquiredFcn', @obj.fcnCamera_stream,... | |
'FramesAcquiredFcnCount', obj.FRAMES_PER_TRIGGER,... | |
'FramesPerTrigger', obj.FRAMES_PER_TRIGGER,... | |
'TriggerRepeat', Inf); | |
triggerconfig(obj.stream, 'immediate'); | |
% initialize callback thread | |
obj.thread = timer(... | |
'ExecutionMode', 'singleShot',... | |
'Name', 'ThreadIntrinsicAnalysis',... | |
'TimerFcn', @obj.fcnThread_setPreviewProperties,... | |
'TasksToExecute', 1); | |
end | |
function obj = setPreviewLimits(obj) | |
%SETPREVIEWLIMITS | |
if obj.flim(2) <= obj.BIT_MAX(1) | |
obj.bitdepth = 1; | |
elseif (obj.flim(2) > obj.BIT_MAX(1)) && (obj.flim(2) <= obj.BIT_MAX(2)) | |
obj.bitdepth = 2; | |
else | |
obj.bitdepth = 3; | |
end | |
% default color limit is determined by bitdepth | |
obj.clim = [0, obj.BIT_MAX(obj.bitdepth)]; | |
if obj.autolim == 1 | |
obj.clim = obj.flim; | |
end | |
end | |
function obj = grab(obj) | |
%GRAB exaecute a snapshot from camera and stop preview | |
set(obj.pw_parent, 'Visible', 'on'); | |
set(obj.pw_parent, 'Name', 'Grabbed Frame'); | |
obj.pw_state = obj.STATE_SNAPSHOT; | |
obj.snapshot = get(obj.pw_image, 'CData'); | |
end | |
function obj = preview(obj) | |
%PREVIEW | |
if strcmp('off', get(obj.stream, 'Running')) | |
% start video stream | |
start(obj.stream); | |
end | |
set(obj.pw_parent, 'Visible', 'on'); | |
set(obj.pw_parent, 'Name', 'Video Stream'); | |
obj.pw_state = obj.STATE_STREAM; | |
end | |
function obj = close(obj) | |
set(obj.pw_parent, 'Visible', 'off'); | |
end | |
end | |
%% --- camera callbacks --- %% | |
methods | |
function obj = fcnCamera_stream(obj, hSrc, ~) | |
% read available frames | |
framesCount = get(hSrc, 'FramesAvailable'); | |
[frames, timeStamp] = getdata(hSrc, framesCount); | |
frames = squeeze(frames); | |
% calculate current FPS | |
obj.fps = 1 / mean(diff(timeStamp)); | |
% calculate frame intensity limit | |
obj.flim = [min(frames(:)), max(frames(:))]; | |
% assign current cdata | |
obj.cdata = frames(:,:,end); | |
% update properties on separate thread | |
start(obj.thread); | |
end | |
function obj = fcnThread_setPreviewProperties(obj, ~, ~) | |
%FCNTHREAD_SETPREVIEWPROPERTIES | |
% check if current preview is stream or snapshot | |
if obj.pw_state == obj.STATE_STREAM | |
% set preview intensity limit | |
obj.setPreviewLimits(); | |
% saturation mask | |
obj.pw_mask = (obj.cdata >= obj.SATURATION_THRESHOLD * obj.BIT_MAX(obj.bitdepth)); | |
% update cdata | |
set(obj.pw_image, 'CData', obj.cdata); | |
set(obj.pw_axes, 'CLim', obj.clim); | |
elseif obj.pw_state == obj.STATE_SNAPSHOT | |
% clear saturation mask | |
obj.pw_mask = false(obj.resolution); | |
% update cdata | |
set(obj.pw_image, 'CData', obj.snapshot); | |
end | |
% update saturation mask | |
set(obj.pw_patch, 'AlphaData', obj.pw_mask); | |
% update properties status | |
set(obj.ui_text_status,... | |
'String', sprintf('%s, FPS %.2f, VL [%d %d]',... | |
obj.BIT_LABEL{obj.bitdepth},... | |
obj.fps,... | |
obj.flim)); | |
obj.ui_grid.align(obj.ui_text_status,... | |
'VIndex', 2,... | |
'HIndex', 1:3,... | |
'Anchor', 'center'); | |
end | |
end | |
%% --- PUBLIC METHODS --- %% | |
methods | |
function obj = enable(obj, varstate) | |
%ENABLE | |
if strcmp('on', varstate) || strcmp('off', varstate) | |
set(obj.ui_pushbutton_preview, 'Enable', varstate); | |
set(obj.ui_pushbutton_grab, 'Enable', varstate); | |
set(obj.ui_checkbox_auto, 'Enable', varstate); | |
end | |
end | |
end | |
end | |