diff --git a/VERSION b/VERSION index 83981c0e3aabbb291048f2d619c53f3bb686f5c7..a6b4ce84014c146e09687bcb2615f45705efc402 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -175 \ No newline at end of file +176 \ No newline at end of file diff --git a/lib/+artoa/+controller/+file/loadInterim.m b/lib/+artoa/+controller/+file/loadInterim.m index ff28195d4ff814160f95adaaa62afdfadc9672c5..c6ecba3fb7b57ddcadbc3e4c7d8d536e095cac86 100644 --- a/lib/+artoa/+controller/+file/loadInterim.m +++ b/lib/+artoa/+controller/+file/loadInterim.m @@ -25,6 +25,8 @@ artoa.controller.track.trajectoryOutput.close(); %% Load mat file load(fullfile(pathname, filename), '-mat'); +%% Initialize plugins +artoa.plugins.initialize(); %% Initialize tracking parameter artoa.controller.track.parameter.updateGui(); %% Initialize offsets diff --git a/lib/+artoa/+controller/+main/collectTrackingMethods.m b/lib/+artoa/+controller/+main/collectTrackingMethods.m new file mode 100644 index 0000000000000000000000000000000000000000..3523deaa632230fdddd9b8219fbefff7b91fa69c --- /dev/null +++ b/lib/+artoa/+controller/+main/collectTrackingMethods.m @@ -0,0 +1,15 @@ +function [trackingMethods] = collectTrackingMethods() +%INITIALIZE Summary of this function goes here +% Detailed explanation goes here + +global artoaWorkspace; + +%% Get default tracking methods +trackingMethods = artoaWorkspace.defaults.trackingMethods; + +%% Get plugin methods +if (artoa.data.hasMember(artoaWorkspace, {'plugins', 'tracking', 'functionHandles'})) + trackingMethods = [trackingMethods fieldnames(artoaWorkspace.plugins.tracking.functionHandles)]; +end + +end diff --git a/lib/+artoa/+controller/+main/open.m b/lib/+artoa/+controller/+main/open.m index 17801e7f51139f6c8426e89dc9bceed920a397c3..a68584f059512ffb357a267148d5b8ab13f2547a 100644 --- a/lib/+artoa/+controller/+main/open.m +++ b/lib/+artoa/+controller/+main/open.m @@ -62,14 +62,12 @@ callbacks.buttonEmpiricalToNan = @artoa.controller.edit.offsets.buttonEmpiricalT artoaWorkspace.defaults.mainCallbacks = callbacks; %% Start artoa4 - artoa.gui.main( ... callbacks, ... - artoaWorkspace.defaults.trackingMethods, ... + artoa.controller.main.collectTrackingMethods(), ... artoaWorkspace.defaults.interpolationMethods, ... artoaWorkspace.defaults.soundspeedMethods ... ); end - diff --git a/lib/+artoa/+controller/initializeArtoa4.m b/lib/+artoa/+controller/initializeArtoa4.m index 17c1dc6e3651b9d5407ba04ff84cbe692a68375a..4b624487d7779aba2346dd09f2cc515f5f893aba 100644 --- a/lib/+artoa/+controller/initializeArtoa4.m +++ b/lib/+artoa/+controller/initializeArtoa4.m @@ -1,6 +1,6 @@ function [] = initializeArtoa4() %INITIALIZEARTOA4 Initializes variables required for artoa4 and creates the main window if required. -% +% global artoaDataInput artoaWorkspace artoaGui; @@ -36,6 +36,9 @@ artoaWorkspace.defaults.soundspeedMethods = { ... }; artoaWorkspace.defaults.pickPointMarkerSize = 80; +artoaWorkspace.plugins = struct(); +artoaWorkspace.plugins.tracking = struct(); + artoaGui = struct(); artoaGui.figures = struct(); artoaGui.main = struct(); @@ -58,6 +61,9 @@ catch ex error([mfilename ': Reading soundsource file failed with the following message: ' ex.message]); end +%% Initialize all plugins +artoa.plugins.initialize(); + %% Open main gui artoa.controller.main.open(); @@ -70,4 +76,3 @@ if artoa.data.getMember(artoaDataInput.ini, {'view', 'hidedeleteddatapoints'}, f end end - diff --git a/lib/+artoa/+plugins/+tracking/callTrackingMethod.m b/lib/+artoa/+plugins/+tracking/callTrackingMethod.m new file mode 100644 index 0000000000000000000000000000000000000000..49652d630415acee899d74b59778529db5f82af8 --- /dev/null +++ b/lib/+artoa/+plugins/+tracking/callTrackingMethod.m @@ -0,0 +1,22 @@ +function [positions, clockError] = callTrackingMethod(pTrackingMethod, pData, pSoundsourcePositions, pCombinationDetails) +%CALLTRACKINGMETHOD Summary of this function goes here +% Detailed explanation goes here + +global artoaWorkspace; + +positions = []; +clockError = []; + +if ~artoa.data.hasMember(artoaWorkspace, {'plugins', 'tracking', 'functionHandles', pTrackingMethod}) + return; +end + +[positions, clockError] = artoaWorkspace.plugins.tracking.functionHandles.(pTrackingMethod)( ... + pData, ... + pSoundsourcePositions, ... + pCombinationDetails ... + ); + + +end + diff --git a/lib/+artoa/+plugins/findTrackingMethods.m b/lib/+artoa/+plugins/findTrackingMethods.m new file mode 100644 index 0000000000000000000000000000000000000000..066b74c90c3b8aefb5275e3f3735e2237a8e3bcb --- /dev/null +++ b/lib/+artoa/+plugins/findTrackingMethods.m @@ -0,0 +1,35 @@ +function [trackingMethods] = findTrackingMethods() +%INITIALIZE Summary of this function goes here +% Detailed explanation goes here + +%% Find all tracking methods +[filePath, ~, ~] = fileparts(mfilename('fullpath')); +filePath = fullfile(filePath, '..', '..', '..'); + +trackingMethods = dir(fullfile(filePath, 'plugins', 'tracking', '*.m')); + +plugins = {}; +for oTrackingMethods = 1:size(trackingMethods) + if (trackingMethods(oTrackingMethods).isdir) + continue; + end + plugins{end + 1} = fullfile( ... + trackingMethods(oTrackingMethods).folder, ... + trackingMethods(oTrackingMethods).name ... + ); +end + +trackingHandles = function_handle(plugins); + +%% Convert them to struct +trackingMethods = struct(); +if ~iscell(trackingHandles) + trackingMethods.(func2str(trackingHandles)) = trackingHandles; + return; +end + +for i = 1:size(trackingHandles) + trackingMethods.(func2str(trackingHandles{i})) = trackingHandles{i}; +end + +end diff --git a/lib/+artoa/+plugins/initialize.m b/lib/+artoa/+plugins/initialize.m new file mode 100644 index 0000000000000000000000000000000000000000..d092d2e326f666b085d1c468793549072928e66d --- /dev/null +++ b/lib/+artoa/+plugins/initialize.m @@ -0,0 +1,19 @@ +function [] = initialize() +%INITIALIZE Summary of this function goes here +% Detailed explanation goes here + +global artoaWorkspace; + +%% Check if workspace supports plugins + +if ~artoa.data.hasMember(artoaWorkspace, {'plugins', 'tracking'}) + warning('Loaded artoaWorkspace does not support tracking plugins! Aborting ...'); + return; +end + +%% Get tracking methods +artoaWorkspace.plugins.tracking.functionHandles = ... + artoa.plugins.findTrackingMethods(); + + +end diff --git a/lib/+artoa/+trajectory/calculate.m b/lib/+artoa/+trajectory/calculate.m index 8b1044e1b23ff2589d00bfb64d4f8c5a60eaa713..bc7d9213273b38cb4c784bf9c6af3a3675e30740 100644 --- a/lib/+artoa/+trajectory/calculate.m +++ b/lib/+artoa/+trajectory/calculate.m @@ -233,11 +233,11 @@ for oCombination = 1:size(soundsourceCombinations, 1) currentCombination.referencePosition{1} = num2str(trajectory(end, :)); end [segmentPositions, segmentDates, segmentClockError, segmentResiduals] = artoa.trajectory.calculateCombinationSegment( ... - preparedData, ... - currentCombination, ...%trajectory(end, :), ... - floatReferenceTime, ... - pSoundVelocity(oCombination, :), ... - pTrackingParameter.trackingMethodString ... + preparedData, ... + currentCombination, ...%trajectory(end, :), ... + floatReferenceTime, ... + pSoundVelocity(oCombination, :), ... + pTrackingParameter.trackingMethodString ... ); % remove NaNs from segment diff --git a/lib/+artoa/+trajectory/calculateCombinationSegment.m b/lib/+artoa/+trajectory/calculateCombinationSegment.m index 2451093d9e5b32b7436642d1f4a822d9e3a44497..105bd21c27c366420aa1f25a6aa0f94ee946876a 100644 --- a/lib/+artoa/+trajectory/calculateCombinationSegment.m +++ b/lib/+artoa/+trajectory/calculateCombinationSegment.m @@ -62,9 +62,12 @@ referencePosition = cellfun(@str2double, strsplit(pCombinationDetails.referenceP %% Filter toa data intersectedToaDates = pCorrectedData.(soundsourceNames{1}).toaDate; soundsourcePositions = [pCorrectedData.(soundsourceNames{1}).position]; +pluginSoundsourcePositions = struct(); +pluginSoundsourcePositions.(soundsourceNames{1}) = pCorrectedData.(soundsourceNames{1}).position; for i = 2:length(soundsourceNames) [intersectedToaDates] = intersect(intersectedToaDates, pCorrectedData.(soundsourceNames{i}).toaDate); soundsourcePositions = [soundsourcePositions; pCorrectedData.(soundsourceNames{i}).position]; + pluginSoundsourcePositions.(soundsourceNames{i}) = pCorrectedData.(soundsourceNames{i}).position; end %% Remove all dates that are out of bounds @@ -73,12 +76,22 @@ intersectedToaDates = intersectedToaDates( ... & intersectedToaDates <= segmentEnd ... ); +%% Create a table for every soundsource (required by tracking plugin system) +pluginTables = struct(); +for i = 1:length(soundsourceNames) + pluginTables.(soundsourceNames{i}) = table(); +end + +%% Prepare data distanceToSoundsources = {}; %segmentPositions = [pStartposition]; segmentPositions = referencePosition; +segmentToas = {}; +warning('off'); % disable warnings because creating the tables are not consistent for oDates = 1:length(intersectedToaDates) currentDateValue = intersectedToaDates(oDates); distanceToSoundsources{oDates} = []; + segmentToas{oDates} = []; for i = 1:length(soundsourceNames) currentName = soundsourceNames{i}; selection = pCorrectedData.(currentName).toaDate == currentDateValue; @@ -105,12 +118,18 @@ for oDates = 1:length(intersectedToaDates) + windowStartTime ... + leapseconds ... + floatWindowStart; + segmentToas{oDates} = [segmentToas{oDates}; relativeToa]; % calculate distance to the source distanceToSoundsources{oDates} = [ ... distanceToSoundsources{oDates}; ... relativeToa * (pSoundVelocity(i)/1000) ]; + + % store in pluginTables + pluginTables.(currentName).date{oDates} = currentDateValue; + pluginTables.(currentName).toa{oDates} = relativeToa; + pluginTables.(currentName).distance{oDates} = distanceToSoundsources{oDates}(i); % distanceToSoundsources{oDates} = [ ... % distanceToSoundsources{oDates}; ... % (... @@ -123,6 +142,7 @@ for oDates = 1:length(intersectedToaDates) end end +warning('on'); if strcmp(trackingMethod, 'hyperbolic') && (length(soundsourceNames) < 3) trackingMethod = 'circular'; @@ -171,6 +191,16 @@ switch trackingMethod end segmentClockError = [segmentClockError; currentClockError]; end + otherwise + % prepare soundsource infos + [segmentPositions, segmentClockError] = artoa.plugins.tracking.callTrackingMethod( ... + pTrackingMethod, ... + pluginTables, ... + pluginSoundsourcePositions, ... + pCombinationDetails ... + ); + + segmentPositions = [referencePosition; segmentPositions]; % if length(unique(sosonr)) < 3; % check if 3 sources have been specified in track panel % fprintf(1,'Hyperbolic tracking not possible: only 2 sound sources specified, using circular tracking\r'); diff --git a/lib/vendor/FEX-function_handle/function_handle.m b/lib/vendor/FEX-function_handle/function_handle.m new file mode 100644 index 0000000000000000000000000000000000000000..168c3593d46f3d54a3bb3116620180205fba394c --- /dev/null +++ b/lib/vendor/FEX-function_handle/function_handle.m @@ -0,0 +1,147 @@ +function h = function_handle(fcn) +% FUNCTION_HANDLE Construct function handle from string or +% cell array of strings +% +% FUNCTION_HANDLE constructs a <a href="matlab:help([matlabroot '/toolbox/matlab/datatypes/function_handle'])">function_handle</a> for built-in or +% user-defined functions. +% +% When constructing a <a href="matlab:help([matlabroot +% '/toolbox/matlab/datatypes/function_handle'])">function_handle</a> directly with <a href="matlab:help('@')">@</a> or <a href="matlab:help('str2func')">str2func</a>, +% it is only possible to succesfully evaluate the resulting handle when +% the function the handle refers to was on the MATLAB search at the time +% the handle was created. To create valid handles to arbitrarty +% functions possibly not on the search path, use the FUNCTION_HANDLE +% constructor. +% +% F = FUNCTION_HANDLE(fcn) for string or cell string [fcn] creates a +% function_handle or cell array of function handles for each of the +% handles or strings in [fcn]. Any string in [fcn] may include the +% path to the function of interest. An error is issued in case the +% path information is incorrect, of the specified file is not an +% executable MATLAB function. +% +% EXAMPLES: +% +% >> F = function_handle('./path/to/function/myFcn.m') +% F = +% @myFcn +% +% >> A = function_handle(... +% {@cos, '../dir/not/on/MATLAB/path/myFunction.m'}) +% A = +% @cos @myFunction +% +% >> A{1}(pi) +% ans = +% -1 +% +% >> functions(A{1}) +% ans = +% function: 'min' +% type: 'simple' +% file: '' +% +% >> functions(A{2}) +% ans = +% function: 'myFunction' +% type: 'simple' +% file: '/fullpath/dir/not/on/MATLAB/path/myFunction.m' +% +% See also <a href="matlab:help([matlabroot '/toolbox/matlab/datatypes/function_handle'])">function_handle (built-in)</a>, str2func, functions. + + +% Please report bugs and inquiries to: +% +% Name : Rody P.S. Oldenhuis +% E-mail : oldenhuis@gmail.com +% Licence : 2-clause BSD (See License.txt) + + +% Changelog +%{ +2014/March/19 (Rody Oldenhuis) + - NEW: first version +%} + + +% If you find this work useful, please consider a donation: +% https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6G3S5UYM7HJ3N + + %% Initialize + + % Quick exit + if isa(fcn, 'function_handle') + h = fcn; return; end + + if ~iscell(fcn) + fcn = {fcn}; end + + % Extract everything that already is a function handle + h = cell(size(fcn)); + F = cellfun('isclass', fcn, 'function_handle'); + h(F) = fcn(F); + if all(F) + return; end + + % Continue with the ones specified as strings + fcn = fcn(~F); + if ~iscellstr(fcn) + throwAsCaller(MException(... + 'function_handle:invalid_objects',... + 'Invalid types detected in input. Expected types are ''function_handle'' or ''char''.')); + end + hF = cell(size(fcn)); + + %% Get to work + + % Make sure we always end up where we started + prevDir = pwd; + clean__ = onCleanup(@(~)cd(prevDir)); + + for ii = 1:numel(fcn) + + % Valid inputs + if any(exist(fcn{ii})==[2 3 5 6 8]) %#ok<EXIST> + + [pth,name] = fileparts(fcn{ii}); + + % non-builtin + if ~isempty(pth) + if exist(pth,'dir')==7 + cd(pth); + hF{ii} = str2func(['@' name]); + cd(prevDir); + else + throwAsCaller(MException(... + 'function_handle:dir_not_found',... + 'Directory ''%s'' not found.', pth)); + end + + % builtin + elseif exist(fcn{ii},'builtin')==5 + hF{ii} = str2func(fcn{ii}); + + % unrecognized + else + throwAsCaller(MException(... + 'function_handle:fcn_not_found',... + ['Function ''%s'' is not on the MATLAB search path, and does not seem ',... + 'to be a builtin.'], name)); + end + + % Invalid input + else + throwAsCaller(MException(... + 'function_handle:fcn_invalid',... + 'Function or file ''%s'' not found.', fcn{ii})); + end + end + + % Final assignment + h(~F) = hF; + + % Make output more intuitive + if numel(h)==1 + h = h{1}; end + +end diff --git a/plugins/tracking/sampleTrackingMethod.m b/plugins/tracking/sampleTrackingMethod.m new file mode 100644 index 0000000000000000000000000000000000000000..e9c0d03693bc4f75bc9c9885b217cadd94efa775 --- /dev/null +++ b/plugins/tracking/sampleTrackingMethod.m @@ -0,0 +1,34 @@ +function [positions, clockError] = sampleTrackingMethod(pData, pSoundsourcePositions, pCombinationDetails) +%SAMPLETRACKINGMETHOD A sample function to write your own tracking algorithms +% +% Parameters: +% pData (struct): +% Data structured by soundsource. The toa is already corrected +% (e.g. doppler correction etc.) +% pSoundsourcePositions (struct): +% The soundsource positions [deg]. +% pCombinationDetails (table): +% Contains all information of the current soundsource +% combination. +% +% +% Returns: +% positions (double): +% The calculated list of segment positions as a X x 2 column vector. +% clockError (double): +% The estimated clock error. + +%% Get reference position and all soundsource names that are involved +referencePosition = cellfun(@str2double, strsplit(pCombinationDetails.referencePosition{1})); +soundsourceNames = strsplit(pCombinationDetails.soundsources{1}); + +%% Show all data that has been passed to the plugin method +pData +pCombinationDetails +pSoundsourcePositions + +%% Return NaN because this is a sample +positions = NaN(size(pData.(soundsourceNames{1}).date, 1), 2); +clockError = []; + +end \ No newline at end of file