https://github.com/Klimmasch/AEC
Tip revision: 96e9ae2336937469a8f1602c178ea5e0cb8564b6 authored by Lukas Klimmasch on 13 August 2021, 14:16:04 UTC
Merge branch 'alternateRearing' of https://github.com/Klimmasch/AEC into alternateRearing
Merge branch 'alternateRearing' of https://github.com/Klimmasch/AEC into alternateRearing
Tip revision: 96e9ae2
Model.m
classdef Model < handle
properties
scModel; % SparseCoding cell array containing respective classes
rlModel; % ReinforcementLearning class
focalLength; % focal length [px]
baseline; % interocular distance
objDistMin; % object distance to eyes [m]
objDistMax;
muscleInitMin; % minimal initial muscle innervation
muscleInitMax; % maximal initial muscle innervation
interval; % period of eye stimulus change at training
testInterval; % period of eye stimulus change at testing
desiredAngleMin; % min/max desired vergence angle
desiredAngleMax;
fixDistMin;
fixDistMax;
vergAngleMin;
vergAngleMax;
vergAngleFixMin;
vergAngleFixMax;
textureFile; % config file containing texture stimulus list
nTrainStim; % number of trainig stimuli loaded into the renderer
nTestStim; % number of stimuli for testing
trainTime; % number of training (intended) iterations
trainedUntil; % how long did the training actually proceed?
simulatedTime; % how long did the model take to be learned (min)
testAt; % at which iteration steps online testing is performed
inputParams; % non-default parameter vector used to generate model with configVar
initMethod; % string identifying the initialization of muscle commands
lapSig; % for drawing laplacian distributed disparities
strabAngle; % fixed offset for the right eye to simulate strabism
objSize; % hight and width of the stimulus plane
maxYaw; % maximal yaw angle of object plane
maxTilt; % maximal tilt angle of object plane
maxRoll; % maximal roll angle of object plane
aniseikonia; % different magnification values for the images
sparseCodingType; % type of sparse coding
lambdaMuscleFB; % factor of muscle activity feedback to RL feature vector
lambdaRec; % reconstruction error factor
lambdaMet; % factor of metCosts for reward function
metCostRange; % in case of metabolic costs decay, this provides the range
metCostDec; % auxiliary factor for the decay
lambdaMet_hist; % tracks the lambdaMet in case of metCost decay
reward_mean; % tracks the exponentially weighted average of rewards
reward_variance; % and the variance for normalizing the reward (if desired)
% Model data history
recerr_hist; % reconstruction error [coarse scale, fine scale]
metCost_hist; % metabolic costs
reward_hist; % reward (usefull when normalization comes into play)
% disp_hist; % disparity
vergerr_hist; % vergence error
% verge_actual; % actual vergence angle
% verge_desired; % desired vergence angle
% Z; % object depth
% fixZ; % fixation depth
td_hist; % temporal difference (td) error
feature_hist; % feature vector
cmd_hist; % vergence commands
relCmd_hist; % relativ changes in motor commands
weight_hist; % L1/L2, i.e. sum abs, sum pow2 weights of actor and critic
actorLR_hist; % actor learning rate
criticLR_hist; % critic learning rate
variance_hist; % exploratory variance of actor
savePath; % where all the data are stored
notes; % is there any special things about this model to note?
% Model results at testing procedure
responseResults;
testResult;
testResult2;
testResult3;
testResult4;
testResult5;
testResult6;
testResult7;
vergenceAngleApproach;
metCostsApproach;
musclePlaneResponse;
testHist; % history of testing performance over traintime
% Image processing
patchSize;
pxFieldOfView
dsRatio;
columnInd;
stride;
patchesLeft;
patchesRight;
overlap;
cutout;
% Providing relevant methods
degrees; % contains a tabular that maps muscle activity to angles
degreesIncRes; % contains a part of degrees with increased resolution
metCostsIncRes; % contains a part of metCosts with increased resolution
degDiff; % difference between entries in degreesIncRes
metCosts; % contains a tabular that maps muscle activity to metabolic costs
mfunctionMR; % contains a function approx. of the mapping from activity to angle
% for the medial rectus with activation of lateral rectus equals 0
mfunctionLR; % analogous for the lateral rectus
dmf; % delta in muscle force (for mfunctions) dmf2
dAngleMR; % delta in angle (for mfunctions) dmf
dAngleLR; % delta in muscle force (for mfunctionLR) dmf3
angleMin; % minimal and maximal angle that can be reached by one-dimensional muscle commands
angleMax;
scaleFacMR;
scaleFacLR;
imgRawLeft; % images that are updated by refreshImages
imgRawRight;
imgGrayLeft;
imgGrayRight;
filterLeft; % convolution filter for the left and right images
filterRight;
filterLeftProb; % probabilities for the application of the according filter
filterRightProb;
normFeatVect; % keep(0) or normalize(1) feature fector by z-transform
currMean; % approximations for online signal normalization
currM2;
desiredStdZT; % desired standard deviation of each z-transformed variable
whitening; % should the images be whitened
end
methods
function obj = Model(PARAM)
%% if isempty(PARAM{1}{1}) -> indicator for copy constructor
if (~isempty(PARAM{1}{1}))
obj.textureFile = PARAM{1}{1};
texture = load(sprintf('config/%s', obj.textureFile{2}));
obj.nTrainStim = length(texture.texture);
texture = load(sprintf('config/%s', obj.textureFile{1}));
obj.nTestStim = length(texture.texture);
obj.trainTime = PARAM{1}{2};
obj.testAt = PARAM{1}{3};
if (~(isempty(obj.testAt)) && (obj.testAt(1) ~= 0))
obj.testAt = horzcat(0, obj.testAt);
end
obj.testInterval = PARAM{1}{28};
obj.inputParams = PARAM{1}{25};
obj.sparseCodingType = PARAM{1}{4};
obj.focalLength = PARAM{1}{5};
obj.baseline = PARAM{1}{6};
obj.objDistMin = PARAM{1}{7};
obj.objDistMax = PARAM{1}{8};
obj.muscleInitMin = PARAM{1}{9};
obj.muscleInitMax = PARAM{1}{10};
obj.interval = PARAM{1}{11};
obj.lambdaMuscleFB = PARAM{1}{12};
obj.lambdaRec = PARAM{1}{13};
obj.metCostRange = PARAM{1}{14};
obj.lambdaMet = obj.metCostRange(1);
obj.metCostDec = PARAM{1}{23};
obj.fixDistMin = PARAM{1}{19};
obj.fixDistMax = PARAM{1}{20};
obj.initMethod = PARAM{1}{24};
obj.lapSig = PARAM{1}{34};
obj.strabAngle = PARAM{1}{35};
obj.objSize = PARAM{1}{36};
obj.maxYaw = PARAM{1}{37};
obj.maxTilt = PARAM{1}{38};
obj.maxRoll = PARAM{1}{39};
obj.aniseikonia = PARAM{1}{40};
obj.filterLeft = PARAM{1}{29};
obj.filterRight = PARAM{1}{31};
obj.filterLeftProb = PARAM{1}{30};
obj.filterRightProb = PARAM{1}{32};
obj.whitening = PARAM{1}{33};
% single eye
obj.desiredAngleMin = atand(obj.baseline / (2 * obj.objDistMax));
obj.desiredAngleMax = atand(obj.baseline / (2 * obj.objDistMin));
obj.vergAngleMin = 2 * atand(obj.baseline / (2 * obj.fixDistMax));
obj.vergAngleMax = 2 * atand(obj.baseline / (2 * obj.fixDistMin));
% obj.vergAngleFixMin = 2 * atand(obj.baseline / (2 * 2));
obj.vergAngleFixMin = 2 * atand(obj.baseline / (2 * obj.objDistMax));
obj.vergAngleFixMax = 2 * atand(obj.baseline / (2 * obj.objDistMin));
%%% Create RL models
% Discrete or continuous policy
if (PARAM{3}{11} == 1)
obj.rlModel = ReinforcementLearningCont(PARAM{3});
else
obj.rlModel = ReinforcementLearning(PARAM{3});
end
% obj.recerr_hist = zeros(obj.trainTime, length(PARAM{2}{1})); % recerr_hist = t x #SC_scales
obj.recerr_hist = zeros(obj.trainTime / obj.interval, length(PARAM{2}{1})); % recerr_hist = t x #SC_scales
% obj.disp_hist = zeros(obj.trainTime, 1);
obj.vergerr_hist = zeros(obj.trainTime, 1);
obj.reward_hist = zeros(obj.trainTime, 1);
% obj.verge_actual = zeros(obj.trainTime, 1);
% obj.verge_desired = zeros(obj.trainTime, 1);
% obj.Z = zeros(obj.trainTime, 1);
% obj.fixZ = zeros(obj.trainTime, 1);
obj.td_hist = zeros(obj.trainTime, 1);
% obj.feature_hist = zeros(obj.trainTime, PARAM{3}{9}(1)); % PARAM{3}{9}(1) : inputDim
obj.cmd_hist = zeros(obj.trainTime, 2);
obj.relCmd_hist = zeros(obj.trainTime, PARAM{3}{9}(3)); % relCmd_hist = t x output_dim
% obj.weight_hist = zeros(obj.trainTime, 4);
obj.weight_hist = zeros(obj.trainTime, 6); % for also traking change in weights
obj.metCost_hist = zeros(obj.trainTime, 1);
obj.lambdaMet_hist = zeros(obj.trainTime, 1);
obj.variance_hist = zeros(obj.trainTime, 1);
% normalization of [recErrSignal, metCostSignal, rewardSignal]
% obj.currMean = zeros(1, 3);
% obj.currM2 = zeros(1, 3);
% obj.desiredStdZT = PARAM{1}{26};
% normalization of nth entry in feature vector
obj.normFeatVect = PARAM{1}{26};
obj.currMean = zeros(1, PARAM{3}{9}(1));
obj.currM2 = ones(1, PARAM{3}{9}(1)); % formerly zeros
obj.desiredStdZT = PARAM{1}{27};
% test results
obj.responseResults = struct();
obj.testResult = [];
obj.testResult2 = [];
obj.testResult3 = [];
obj.testResult4 = [];
obj.testResult5 = [];
obj.testResult6 = [];
obj.testResult7 = [];
obj.vergenceAngleApproach = [];
obj.metCostsApproach = [];
obj.musclePlaneResponse = [];
% rmse(vergerr), mean(abs(vergErr)), std(abs(vergErr)), rmse(deltaMetCost), mean(abs(deltaMetCost)), std(abs(deltaMetCost))
obj.testHist = zeros(length(obj.testAt), 6);
% insert modelAt0 entry
% TODO: update for bias as well
if obj.normFeatVect == 0
obj.testHist(1, :) = [1.1593, 1.3440, 1.5243, 1.0736, 1.1527, 0.9517];
else
obj.testHist(1, :) = [1.1557, 1.3355, 1.5078, 1.0812, 1.1689, 0.9552];
end
obj.simulatedTime = 0;
obj.trainedUntil = 0;
obj.notes = '';
%%% Generate image processing constants
obj.patchSize = PARAM{1}{15};
obj.pxFieldOfView = PARAM{1}{16};
obj.dsRatio = PARAM{1}{17};
obj.stride = PARAM{1}{18};
obj.overlap = PARAM{1}{21};
obj.cutout = PARAM{1}{22};
% Prepare index matrix for image patches
obj.prepareColumnInd();
% cut out central region
if (obj.cutout == 1)
obj.prepareCutout();
end
%%% Create SC models
obj.scModel = {};
PARAM{2}{end + 1} = [cellfun('length', obj.columnInd)]; % append image batch size`s 2nd dimensions
if (obj.sparseCodingType == 0)
for i = 1 : length(PARAM{2}{1})
% pick respective parameters from PARAM cell array for ith SC model constructor
obj.scModel{end + 1} = SparseCoding2(cellfun(@(x) x(i), PARAM{2}, 'UniformOutput', true));
% obj.scModel{end + 1} = SparseCoding2(cellfun(@(x) x(i), PARAM{2}, 'UniformOutput', false));
end
else
%TODO: update depricated SparseCodingHomeo class
error('SparseCodingHomeo class is DEPRICATED and therefore currently not supported!');
% obj.scModel_Large = SparseCodingHomeo(PARAM{2}{1}); %coarse scale
% obj.scModel_Small = SparseCodingHomeo(PARAM{2}{2}); %fine scale
end
% Intermediate patch matricies
obj.patchesLeft = cell(1, length(obj.scModel));
obj.patchesRight = cell(1, length(obj.scModel));
for i = 1 : length(obj.scModel)
obj.patchesLeft{i} = zeros(obj.patchSize ^ 2, length(obj.columnInd{i}));
obj.patchesRight{i} = zeros(obj.patchSize ^ 2, length(obj.columnInd{i}));
end
%%% Preparing Functions
% getMF functions
% interpolation tables, each for a _single_ eye
obj.degrees = load('Degrees.mat'); % f(medial_rectus_activiation, medial_rectus_activiation) = vergence_angle table 'results_deg'
% obj.degrees = load('DegreesFlatter.mat');
% obj.degrees = load('DegreesFlatInverted.mat');
% obj.degrees = load('DegreesFlatRotated.mat');
obj.metCosts = load('MetabolicCosts.mat'); % f(medial_rectus_activiation, medial_rectus_activiation) = metabolic_cost table 'results'
% factor by which the resolution of the tabular should be increased
% resFac, size of tabular: 5,33 | 6,65 | 10,1025 | 13,8193 | x,(2^x)+1
% resFactor = 13; % results in comparable amount of entries as the mfunctions
resFactor = 10;
% between 0 and 0.2/0.1 mus. act. the resolution is increased
usedRows = 3;
usedCols = 2;
obj.degreesIncRes = interp2(obj.degrees.results_deg(1 : usedRows, 1 : usedCols), resFactor);
% obj.degreesIncRes = interp2(obj.degrees.results_deg(end - usedRows + 1 : end, end - usedCols + 1 : end), resFactor);
obj.degDiff = abs(max(max(diff(obj.degreesIncRes)))); % distance between the entries
% obj.degDiff = max(max(diff(obj.degreesIncRes)));
obj.scaleFacMR = ((usedRows - 1) / 10) / size(obj.degreesIncRes, 1); % table scaling factors for backwards
obj.scaleFacLR = ((usedCols - 1) / 10) / size(obj.degreesIncRes, 2); % calculation of table_index -> muscle inervation
% increased resolution of metCosts table
obj.metCostsIncRes = interp2(obj.metCosts.results(1 : usedRows, 1 : usedCols), resFactor, 'spline');
% obj.metCostsIncRes = interp2(obj.metCosts.results(end - usedRows + 1 : end, end - usedCols + 1 : end), resFactor);
% muscle function := mf(vergence_angle) = muscle force [single muscle]
resolution = 100001;
approx = spline(1 : 11, obj.degrees.results_deg(:, 1));
xValPos = ppval(approx, 1 : 0.0001 : 11)';
yValPos = linspace(0, 1, resolution)';
% xValNeg = flipud(ppval(approx, 1 : 0.0001 : 11)' * -1);
% yValNeg = linspace(-1, 0, resolution)';
% mfunction = [xValNeg(1 : end - 1), yValNeg(1 : end - 1); xValPos, yValPos];
obj.mfunctionMR = [xValPos, yValPos];
obj.mfunctionMR(:, 1) = obj.mfunctionMR(:, 1) * 2; % angle for two eyes
obj.dAngleMR = abs(diff(obj.mfunctionMR(1 : 2, 1))); % delta in angle
obj.dmf = diff(obj.mfunctionMR(1 : 2, 2)); % delta in muscle force
approx = spline(1 : 11, obj.degrees.results_deg(1, :));
xValPos = ppval(approx, 1 : 0.0001 : 11)';
yValPos = linspace(0, 1, resolution)';
obj.mfunctionLR = [xValPos, yValPos];
obj.mfunctionLR(:, 1) = obj.mfunctionLR(:, 1) * 2; % angle for two eyes
obj.dAngleLR = abs(diff(obj.mfunctionLR(1 : 2, 1))); % delta in angle
obj.angleMin = min(obj.mfunctionLR(obj.mfunctionLR(:, 1) > 0));
obj.angleMax = obj.mfunctionMR(end, 1);
% refreshImages
obj.imgRawLeft = uint8(zeros(240, 320, 3));
obj.imgRawRight = uint8(zeros(240, 320, 3));
obj.imgGrayLeft = uint8(zeros(240, 320, 3));
obj.imgGrayRight = uint8(zeros(240, 320, 3));
end
end
%%% Copy constructor
% Creates a (deep) copy of a handle object
function new = copy(this)
% Instantiate new object of the same class
dummyParams = {{''}, ...
{}, ...
{}};
new = feval(class(this), dummyParams);
% Copy all non-hidden properties
p = properties(this);
% Copy hidden properties which don't show up in the result
% of properties(this)
% p = fieldnames(struct(this));
for i = 1 : length(p)
new.(p{i}) = this.(p{i});
end
end
%%% Generates index matrix for image patches
% Filled image batch, i.e. all patches for the respective scale are used
function prepareColumnInd(this)
% index matrix
this.columnInd = {};
% pixel index of left upper corner of last patch
ilp = this.pxFieldOfView - this.patchSize + 1;
% for #scales
for i = 1 : length(this.dsRatio)
k = 1;
% number of patches per row/column
nprc = length(1 : this.stride(i) : ilp(i));
% each scale has nprc * nprc patches
this.columnInd{end + 1} = zeros(1, nprc ^ 2);
% calculate indices of patches with respect to stride
for j = 1 : this.stride(i) : ilp(i)
this.columnInd{i}((k - 1) * nprc + 1 : k * nprc) = (j - 1) * ilp(i) + 1 : this.stride(i) : j * ilp(i);
k = k + 1;
end
end
end
%%% Cuts out smaller scales from larger scales' fields of view
function prepareCutout(this)
% most inner layer does not need a cutout
% therefore n-1 cuts per n layers
for i = 1 : (length(this.dsRatio) - 1)
% Calculate cutout area, upsample to orig and downsample to current (substract more if needed)
foveaSmallerAdjusted = ceil((this.pxFieldOfView(i + 1) - 2 * this.overlap(i)) * (this.dsRatio(i + 1) / this.dsRatio(i)));
% Calculate offset of coarse scale [px]
offsetCoarse = (this.pxFieldOfView(i) - foveaSmallerAdjusted) / 2;
% # stride applications =^ offset width
offsetWidth = ceil(((offsetCoarse - this.patchSize) / this.stride(i)) + 1);
% number of patches per row/column
nprc = floor((this.pxFieldOfView(i) - this.patchSize) / this.stride(i) + 1);
% remaining patches vector [indices]
remainder = [];
% full rows + first left border part
startPart = 1 : (offsetWidth * (nprc + 1));
% remainder(1 : length(startPart)) = startPart;
remainder = [remainder, startPart];
% right border + next row's left border patches
for j = 1 : (nprc - 2 * offsetWidth - 1)
borderPart = (nprc * (offsetWidth + j) - offsetWidth + 1) : (nprc * (offsetWidth + j) + offsetWidth);
% remainder(length(startPart) + length(borderPart) * (j - 1) + 1 : length(startPart) + length(borderPart) * j) = borderPart;
remainder = [remainder, borderPart];
end
% right border + remaining full rows
endPart = (nprc * (offsetWidth + j + 1) - offsetWidth + 1) : nprc ^ 2;
% remainder(length(startPart) + length(borderPart) * j + 1 : end) = endPart;
remainder = [remainder, endPart];
% Cutting columnInd to eradicate the inner patches in the outer layer
this.columnInd{i} = this.columnInd{i}(:, remainder);
end
end
%%% Patch generation
% param scScale: SC scale index elem {coarse := 1, 2, ..., fine = #}
% param eyePos: eye position index elem {1 := left, 2 := right}
function preprocessImage(this, scScale, eyePos)
if (eyePos == 1)
img = this.imgGrayLeft;
else
img = this.imgGrayRight;
end
% whitening with a constant filter
if this.whitening
% N = size(img, 1);
[n, m] = size(img);
% [fx, fy] = meshgrid(-N/2 : N/2-1, -N/2 : N/2-1);
[fx, fy] = meshgrid(-n/2 : n/2-1, -m/2 : m/2-1);
rho = sqrt(fx.*fx + fy.*fy);
f_0 = 0.4*n;
filt = rho.*exp(-(rho/f_0).^4);
If = fft2(img);
img = real(ifft2(If.*fftshift(filt')));
% img = imagew;
end
% down scale image
for k = 1 : log2(this.dsRatio(scScale))
img = impyramid(img, 'reduce');
end
% convert to double
img = double(img);
% cut fovea in the center
[h, w, ~] = size(img);
img = img(fix(h / 2 + 1 - this.pxFieldOfView(scScale) / 2) : fix(h / 2 + this.pxFieldOfView(scScale) / 2), ...
fix(w / 2 + 1 - this.pxFieldOfView(scScale) / 2) : fix(w / 2 + this.pxFieldOfView(scScale) / 2));
% cut patches and store them as col vectors
patches = im2col(img, [this.patchSize, this.patchSize], 'sliding'); % slide window of 1 px
% take patches by application of respective strides
patches = patches(:, this.columnInd{scScale});
% pre-processing steps (0 mean, unit norm)
patches = patches - repmat(mean(patches), [size(patches, 1), 1]); % 0 mean
normp = sqrt(sum(patches .^ 2)); % patches norm
% normalize patches to norm 1
normp(normp == 0) = eps; % regularizer
patches = patches ./ repmat(normp, [size(patches, 1), 1]); % normalized patches
if (eyePos == 1)
this.patchesLeft{scScale} = patches;
else
this.patchesRight{scScale} = patches;
end
end
%% update and display patches
function updatePatches(this, simulator, stimulus, objDist, disp, nRows, nCols)
angleDes = 2 * atand(this.baseline / (2 * objDist))
angle = angleDes - disp %TODO: check not plus?
this.refreshImagesNew(simulator, stimulus, angle/2, objDist, 3, [0, 0, 0]);
for s = 1:length(this.scModel)
this.preprocessImage(s, 1)
this.preprocessImage(s, 2)
end
this.displayPatches(nRows, nCols);
end
%% Display the current patches
function displayPatches(this, nRows, nCols)
n = size(this.patchesLeft{1}, 2);
p = this.patchSize;
if nargin < 3
nRows = 3; nCols = 3;
elseif nargin < 4
nCols = floor(n/nRows);
end
if nRows*nCols > n
sprintf('Reducing the number of columns.')
nCols = floor(n/nRows);
end
% pleft = this.patchesLeft{scale};
% pright = this.patchesRight{scale};
% leftp = col2im(pleft,[p,p],[p,p*n],'distinct');
% rightp = col2im(pright,[p,p],[p,p*n],'distinct');
% left and right concatenieren und untereinander darstellen
figure;
hold on;
% for i = 1 : nRows*nCols
% subplot(nRows,nCols,i);
% pBin = horzcat(pleft(:,i), pright(:,i));
% binIm = col2im(pBin, [p,p], [p*2,p], 'distinct');
% % pBin = horzcat([pleft(:,i); ones(8,1)], [pright(:,i); ones(8,1)]);
% % binIm = col2im(pBin, [p,p], [p*2,p+1], 'distinct');
% imshow(mat2gray(binIm),'initialMagnification','fit');
% end
% this version is more like the basis displaying
for s = 1 : length(this.scModel)
subplot(1,2,s);
pBin = vertcat(this.patchesLeft{s}, this.patchesRight{s});
[di,~] = size(pBin);
fun1 = @(blc_struct) padarray(padarray(reshape(permute(padarray(reshape(blc_struct.data, sqrt(di / 2), ...
sqrt(di / 2), 2), [1, 1], 'pre'), [1, 3, 2]), (sqrt(di / 2) + 1) * 2, sqrt(di / 2) + 1), [1, 1], ...
'post') - 1, [1 1], 'pre') + 1;
A = pBin(:,1:end-1); % remove last patch to display them in a grid
[~,num] = size(A);
% B = reshape(A,di*r,c);
B = reshape(A, di*nRows, num/nRows);
B = B/max(max(abs(B))) + 0.5;
C = padarray(padarray(blockproc(B,[di,1],fun1)-1,[1 1],'post')+1,[2,2]);
imshow(mat2gray(C));
% imshow(mat2gray(col2im(pBin,[8,16],[8*8,16*6],'distinct')'));
% imshow(mat2gray(col2im(pBin,[8,16],[8*8,16*10],'distinct')));
end
end
%%% Generate Feature Vector and Reward
function [totalFeature, totalReward, errorArray] = generateFR(this, imageBatch)
errorArray = zeros(1, length(this.scModel));
rewardArray = zeros(1, length(this.scModel));
featureArray = cell(1, 2);
for i = 1 : length(this.scModel)
tmpImages = imageBatch{i};
imPind = find(sum(tmpImages .^ 2)); %find non-zero patches (columns)
if (isempty(imPind))
% try
% feature_L = zeros(this.scModel_Large.Basis_num, 1);
% reward_L = this.rlModel.J; %TODO: not supported for continuous rlModel
% catch
% end
error('All values in the extracted patches are zero. Check if the image rendering is alright!');
end
this.scModel{i}.sparseEncode(tmpImages);
errorArray(i) = sum(sum(this.scModel{i}.currentError .^ 2)) / sum(sum(tmpImages .^ 2));
% note that division by norm of patches can be omitted if patches are normalized
rewardArray(i) = -errorArray(i);
% feature vector, i.e. average activation, Eq. 3.1
featureArray{i} = mean(this.scModel{i}.currentCoef(:, imPind) .^ 2, 2);
end
% Compute total reconstruction error and reward
% totalError = sum(errorArray); % total reconstruction error
totalReward = sum(rewardArray); % sum rewards
totalFeature = vertcat(featureArray{:}); % join feature vectors
end
%%% Online signal normalization to 0 mean and unit variance by Welford (1962)
% also known as z-transformation
%
% currMean := current mean approximation
% currM2 := current sum of squares of differences from the current mean
% desiredStdZT := desired std. deviation
%
% param n: index of sample
% param dataSample: new datapoint
% param signalType: {1, 2, 3} := recErrSignal, metCostSignal, rewardSignal; for reward normalization
% {1, 2, ..., feature_n} := nth entry in feature vector; for feature vector normalization
% param updateFlag: {0, 1} := whether current means and M2s shall be updated
%
% return: normalized data sample point
function normDataSample = onlineNormalize(this, n, dataSample, signalType, updateFlag)
if (n < 2)
normDataSample = dataSample * this.desiredStdZT;
return;
end
if (updateFlag == 1)
delta = dataSample - this.currMean(signalType);
this.currMean(signalType) = this.currMean(signalType) + delta / n;
this.currM2(signalType) = this.currM2(signalType) + delta * (dataSample - this.currMean(signalType));
end
% n - 1 to get unbiased variance
normDataSample = ((dataSample - this.currMean(signalType)) / sqrt(this.currM2(signalType) / (n - 1))) * this.desiredStdZT;
% catch case if signal == 0 until now
if (isnan(normDataSample))
normDataSample = 0;
end
end
%%% Calculates muscle force for medial rectus
% maps {vergenceAngle} -> {muscleForce} for one muscle
function mf = getMF(this, vergAngle)
% look up index of vergAngle
if (vergAngle >= this.degrees.results_deg(1, 1) * 2)
indVergAngle = find(this.mfunctionMR(:, 1) <= vergAngle + this.dAngleMR & this.mfunctionMR(:, 1) >= vergAngle - this.dAngleMR);
mf = this.mfunction(indVergAngle, 2);
mf = mf(ceil(length(mf) / 2));
else
mf = 0;
end
end
%%% Calculates muscle force for two muscles
% the activation of one muscle is always 0.
%
% mf: [lateralRectusActivation; medialRectusActivation]
% angleInit: desired init angle for given vergence error [deg]
function [mf, angleInit] = getMF2(this, objDist, desVergErr)
% correct vergence angle for given object distance
angleCorrect = 2 * atand(this.baseline / (2 * objDist));
% desired init angle for given vergence error [deg]
angleInit = angleCorrect - desVergErr;
% look up index of angleInit
if (angleInit >= this.mfunctionMR(1, 1))
if angleInit > this.mfunctionMR(end,1)
mf = [0; this.mfunction(end,1)];
else
indAngleInit = find(this.mfunctionMR(:, 1) <= angleInit + this.dAngleMR & this.mfunctionMR(:, 1) >= angleInit - this.dAngleMR);
mf = this.mfunctionMR(indAngleInit, 2);
mf = [0; mf(ceil(length(mf) / 2))]; % take middle entry
end
else
% if objDist not fixateable with medial rectus, use lateral rectus
if angleInit < this.mfunctionLR(end, 1)
mf = [this.mfunction(end,1), 0];
else
indAngleInit = find(this.mfunctionLR(:, 1) <= angleInit + this.dAngleLR & this.mfunctionLR(:, 1) >= angleInit - this.dAngleLR);
mf = this.mfunctionLR(indAngleInit, 2);
mf = [mf(ceil(length(mf) / 2)); 0]; % take middle entry
end
end
end
%%% Calculates muscle force for two muscles
% drawn from all permitted mf(l, m) ^= f(objDist, desVergErr), where l, m >= 0
% == get muscle force equally distributed over object distance.
function [command, angleInit] = getMFedood(this, objDist, desVergErr)
angleCorrect = 2 * atand(this.baseline / (2 * objDist));
angleInit = angleCorrect - desVergErr;
% angleInit is the angle for both eyes, but degreesIncRes only
% contains angles for one eye
[xi, yi] = find(this.degreesIncRes <= (angleInit / 2) + this.degDiff & this.degreesIncRes >= (angleInit / 2) - this.degDiff);
i = randi(length(xi));
% transform indizes to muscle activities
mfMR = xi(i) * this.scaleFacMR;
mfLR = yi(i) * this.scaleFacLR;
% mfMR = mfMR + 0.6; % for the rotated angles tabular
% mfLR = mfLR + 0.7;
command = [mfLR; mfMR];
end
%%% Calculates muscle force for two muscles
% drawn from all permitted mf(l, m) ^= f(objDist, desVergErr), where l, m >= 0
% == get muscle force equally distributed over object distance
% BUT
function [command, angleInit] = getMFedoodD(this, objDist, desVergErr, Distance)
angleCorrect = 2 * atand(this.baseline / (2 * objDist));
angleInit = angleCorrect - desVergErr;
if Distance > 0.1
Distance = 0.1
warning('your Distance was too big ;) I set it to 0.1 ;-*');
end
% angleInit is the angle for both eyes, but degreesIncRes only
% contains angles for one eye
[xi, yi] = find(this.degreesIncRes <= (angleInit / 2) + this.degDiff & this.degreesIncRes >= (angleInit / 2) - this.degDiff);
i = find((yi .* this.scaleFacLR) >= Distance);
% transform indizes to muscle activities
mfMR = xi(i(1)) * this.scaleFacMR;
mfLR = yi(i(1)) * this.scaleFacLR;
command = [mfLR; mfMR];
end
%%% Maps {objDist, desVergErr} -> {medialRectusActivations, lateralRectusActivations},
% i.e. calculates all muscle activity combinations corresponding to specified {objDist, desVergErr}
function [mfLR, mfMR] = getAnglePoints(this, objDist, desVergErr)
angleCorrect = 2 * atand(this.baseline / (2 * objDist));
angleInit = angleCorrect - desVergErr;
% angleInit is the angle for both eyes, but degreesIncRes only
% contains angles for one eye
[xi, yi] = find(this.degreesIncRes <= (angleInit / 2) + this.degDiff & this.degreesIncRes >= (angleInit / 2) - this.degDiff);
% transform indizes to muscle activities
mfMR = xi .* this.scaleFacMR;
mfLR = yi .* this.scaleFacLR;
end
%%% Maps {medialRectusActivations, lateralRectusActivations} -> vergence angle (single eye)
% i.e. maps muscle activity to angle (one eye)
function angle = getAngle(this, command)
cmd = (command * 10) + 1; % scale commands to table entries
angle = interp2(this.degrees.results_deg, cmd(1), cmd(2), 'spline'); % interpolate in table by cubic splines
end
%%% Maps {medialRectusActivations, lateralRectusActivations} -> VergErr
% DEPRICATED method: slightly faster variant, also a bit less precise,
% only works for the medial rectus
function angle = getAngle2(this, command)
angleIndex = find(this.mfunctionMR(:, 2) <= command(2) + this.dmf & this.mfunctionMR(:, 2) >= command(2) - this.dmf);
angle = this.mfunctionMR(angleIndex, 1);
angle = angle(ceil(length(angle) / 2));
end
%%% Calculates/interpolates metabolic costs resulting from given command
function tmpMetCost = getMetCost(this, command)
cmd = (command * 10) + 1; % scale commands to table entries
tmpMetCost = interp2(this.metCosts.results, cmd(1), cmd(2), 'spline'); % interpolate in table by cubic splines
end
%%% Calculates max. permitted verg_angle {objDist} -> {maxVergErr}
function vergErrMax = getVergErrMax(this, objDist)
% correct vergence angle for given object distance
angleCorrect = 2 * atand(this.baseline / (2 * objDist));
vergErrMax = angleCorrect - this.angleMin;
end
%% Depricated: use refreshImagesNew instead.
%%% Updates images during simulation by generating two new images for both eyes
% param simulator: a renderer instance
% param texture: file path of texture input
% param eyeAngle: angle of single eye (rotation from offspring)
% param objDist: distance of stimulus
% param scaleImSize: scaling factor of stimulus plane [m]
function refreshImages(this, simulator, texture, eyeAngle, objDist, scaleImSize)
simulator.add_texture(1, texture);
simulator.set_params(1, eyeAngle, objDist, 0, scaleImSize); % scaling of obj plane size
result1 = simulator.generate_left();
result2 = simulator.generate_right();
this.imgRawLeft = permute(reshape(result1, ...
[size(this.imgRawLeft, 3), ...
size(this.imgRawLeft, 2), ...
size(this.imgRawLeft, 1)]), ...
[3, 2, 1]);
this.imgRawRight = permute(reshape(result2, ...
[size(this.imgRawRight, 3), ...
size(this.imgRawRight, 2), ...
size(this.imgRawRight, 1)]), ...
[3, 2, 1]);
% convert images to gray scale
this.imgGrayLeft = 0.2989 * this.imgRawLeft(:, :, 1) + 0.5870 * this.imgRawLeft(:, :, 2) + 0.1140 * this.imgRawLeft(:, :, 3);
this.imgGrayRight = 0.2989 * this.imgRawRight(:, :, 1) + 0.5870 * this.imgRawRight(:, :, 2) + 0.1140 * this.imgRawRight(:, :, 3);
end
%%% Generates two new images for both eyes for experimental renderer
% param simulator: a renderer instance
% param textureNumber: index of stimulus in memory buffer
% param eyeAngle: angle of single eye (rotation from offspring)
% param objDist: distance of stimulus
% param scaleImSize: scaling factor of stimulus plane [m]
% param rotatePlane: [tilt, yaw, roll] angles for the object plane
function refreshImagesNew(this, simulator, textureNumber, eyeAngle, objDist, scaleImSize, rotatePlane)
if isempty(this.strabAngle)
simulator.set_params(textureNumber, eyeAngle, objDist, 0, scaleImSize, rotatePlane(1), rotatePlane(2), rotatePlane(3));
else
simulator.set_params(textureNumber, eyeAngle, objDist, this.strabAngle, scaleImSize, rotatePlane(1), rotatePlane(2), rotatePlane(3));
end
result1 = simulator.generate_left();
result2 = simulator.generate_right();
this.imgRawLeft = permute(reshape(result1, ...
[size(this.imgRawLeft, 3), ...
size(this.imgRawLeft, 2), ...
size(this.imgRawLeft, 1)]), ...
[3, 2, 1]);
this.imgRawRight = permute(reshape(result2, ...
[size(this.imgRawRight, 3), ...
size(this.imgRawRight, 2), ...
size(this.imgRawRight, 1)]), ...
[3, 2, 1]);
% convert images to gray scale
this.imgGrayLeft = 0.2989 * this.imgRawLeft(:, :, 1) + 0.5870 * this.imgRawLeft(:, :, 2) + 0.1140 * this.imgRawLeft(:, :, 3);
this.imgGrayRight = 0.2989 * this.imgRawRight(:, :, 1) + 0.5870 * this.imgRawRight(:, :, 2) + 0.1140 * this.imgRawRight(:, :, 3);
end
%%% Calculate binocularity indizes, plot them and save the figure as specified
function plotBinocularity(this, scale, savePath)
leftBasis = this.scModel{scale}.basis(1:end/2, :);
rightBasis = this.scModel{scale}.basis(end/2+1:end, :);
binocularity = (sqrt(sum(leftBasis.^2)) - sqrt(sum(rightBasis.^2))) ./ (sqrt(sum(leftBasis.^2)) + sqrt(sum(rightBasis.^2)));
binocularity = -binocularity;
binocularity = squeeze(binocularity(1, :));
bins = [-1, -0.85, -0.5, -0.15, 0.15, 0.5, 0.85, 1];
% bins = [-1 : 2/7 : 1];
[N, ~] = histcounts(binocularity, bins);
% [N, ~] = histcounts(binocularity, 21);
h = figure;
bar((N./sum(N))*100, 1);
grid on;
xlabel('Binocularity');
ylabel('Percentage of Bases [%]');
% xlim([0.5 7.5]);
ylim([0 100]);
title(sprintf('Scale %d', scale));
saveas(h, sprintf('%s/binocularity_sc%d.png', savePath, scale));
% saveas(h, [savePath, '/binocularity.png']);
end
%%% Plot all gathered performance data and save graphs
% param level: # of plot elem range [1, 8]
% 1 # vergence error
% 2 # reconstruction error
% 3 # verg angle over fix distance (outdated)
% 4 # muscle graphs
% 5 # RL weights
% 6 # reward composition
% 7 # testing performance over train time
% 8 # binocularity plot
% 9 # fitting of basis functions
function allPlotSave(this, level)
% only take the last value before the image/texture is changed
ind = this.interval : this.interval : this.trainTime;
windowSize3 = ceil(this.trainTime * 0.01);
metCost_hist_sma = filter(ones(1, windowSize3) / windowSize3, 1, this.metCost_hist(ind));
windowSize = 1000;
%% Vergence error
if (~isempty(find(level == 1)))
if (this.trainTime < windowSize * this.interval)
windowSize = round(this.trainTime / this.interval / 5);
end
%% Simple Moving Average Vergence Error
vergerr = filter(ones(1, windowSize) / windowSize, 1, abs(this.vergerr_hist(ind)));
figure;
hold on;
grid on;
plot((windowSize + 1) * this.interval : this.interval : size(this.vergerr_hist), vergerr(windowSize + 1 : end), ...
'LineWidth', 1.3);
xlabel(sprintf('Iteration # (interval=%d)', this.interval), 'FontSize', 12);
ylabel('SMA(|verg_{err}|) [deg]', 'FontSize', 12);
title('Moving Average of Vergence Error');
plotpath = sprintf('%s/vergErrSMA', this.savePath);
saveas(gcf, plotpath, 'png');
%% Root Mean Squared Vergence Error
if (this.trainTime < windowSize * this.interval)
windowSize = round(this.trainTime / this.interval / 5);
end
vergerr = this.vergerr_hist(ind);
rmse = zeros(length(1 : windowSize : length(vergerr) - mod(length(vergerr), 2)), 1); % cut if odd length
k = 1 : windowSize : length(vergerr) - mod(length(vergerr), 2);
for i = 1 : length(rmse)
try
rmse(i) = sqrt(mean(vergerr(k(i) : k(i) + windowSize - 1) .^ 2));
catch
% if windowsize > values in vergerr
rmse(i) = 0;
end
end
try
figure;
hold on;
grid on;
plot(windowSize * this.interval : windowSize * this.interval : (length(vergerr) - mod(length(vergerr), 2)) * this.interval, ...
rmse, 'LineWidth', 1.3);
axis([-inf, inf, 0, inf]);
xlabel(sprintf('Iteration # (windowSize=%d)', windowSize * this.interval), 'FontSize', 12);
ylabel('RMSE(verg_{err}) [deg]', 'FontSize', 12);
title('RMSE of Vergence Error');
plotpath = sprintf('%s/vergErrRMSE', this.savePath);
saveas(gcf, plotpath, 'png');
catch
% if windowsize > values in vergerr
warning('windowSize >= vergerr. vergErrRMSE graph will not be generated.');
end
end
%% Reconstruction Error
if (~isempty(find(level == 2)))
try
figure;
hold on;
grid on;
handleArray = zeros(1, length(this.scModel));
for i = 1 : length(this.scModel)
tmpError = filter(ones(1, windowSize) / windowSize, 1, this.recerr_hist(:, i));
handleArray(i) = plot((windowSize + 1) : size(this.recerr_hist, 1), ...
tmpError(windowSize + 1 : end), ...
'color', [rand, rand, rand], 'LineWidth', 1.3);
end
xlabel(sprintf('Episode # (interval=%d)', this.interval), 'FontSize', 12);
ylabel('Reconstruction Error [AU]', 'FontSize', 12);
captions = cell(1, length(handleArray));
for i = 1 : length(handleArray)
captions{i} = strcat('scale', sprintf(' %d', i));
end
l = legend(handleArray, captions);
plotpath = sprintf('%s/recErr', this.savePath);
saveas(gcf, plotpath, 'png');
catch
% if windowsize > values in recerr_hist
warning('windowSize >= recerr_hist. recErr graph will not be generated.');
end
end
% %% Vergence angle / fixation distance
% if (~isempty(find(level == 3)))
% obsWin = 99; %249; % #last iterations to plot
% figure;
% hold on;
% grid on;
% if (length(this.verge_desired) >= obsWin)
% % plot(this.verge_desired(end - obsWin : end), 'color', [0, 0.7255, 0.1765], 'LineWidth', 1.8);
% % plot(this.verge_actual(end - obsWin : end), 'color', [0, 0.6863, 1.0000], 'LineWidth', 1.3);
% plot(this.Z(this.trainedUntil - obsWin : this.trainedUntil), 'color', [0, 0.7255, 0.1765], 'LineWidth', 1.8);
% plot(this.fixZ(this.trainedUntil - obsWin : this.trainedUntil), 'color', [0, 0.6863, 1.0000], 'LineWidth', 1.3);
% else
% % plot(this.verge_desired, 'color', [0, 0.7255, 0.1765], 'LineWidth', 1.8);
% % plot(this.verge_actual, 'color', [0, 0.6863, 1.0000], 'LineWidth', 1.3);
% plot(this.Z, 'color', [0, 0.7255, 0.1765], 'LineWidth', 1.8);
% plot(this.fixZ, 'color', [0, 0.6863, 1.0000], 'LineWidth', 1.3);
% end
% xlabel(sprintf('Iteration # (interval=%d)', this.interval), 'FontSize', 12);
% % ylabel('Angle [deg]', 'FontSize', 12);
% ylabel('Object Distance [m]', 'FontSize', 12);
% ylim([this.objDistMin - 1, this.objDistMax + 1]);
% legend('desired', 'actual');
% title(sprintf('Vergence at last %d steps of training', obsWin + 1));
% % plotpath = sprintf('%s/vergenceAngle', this.savePath);
% plotpath = sprintf('%s/fixationDistTraining', this.savePath);
% saveas(gcf, plotpath, 'png');
% end
%% Muscel graphs
if (~isempty(find(level == 4)))
if (this.rlModel.continuous == 1)
ind2 = 1 : 25 : this.trainTime;
windowSize = ceil(this.trainTime * 0.005);
windowSize2 = ceil(this.trainTime * 0.01);
if (this.trainTime < windowSize * this.interval)
windowSize = round(this.trainTime / this.interval / 5);
end
if windowSize < 2
windowSize = 2;
end
if windowSize2 < 2
windowSize2 = 2;
end
cmd_hist_sma = filter(ones(1, windowSize) / windowSize, 1, this.cmd_hist(ind2, 1));
relCmd_hist_sma = filter(ones(1, windowSize2) / windowSize2, 1, this.relCmd_hist(ind2, 1));
% Two eye muscles
if (this.rlModel.CActor.output_dim == 2)
xVal = [1 : length(cmd_hist_sma)];
cmd_hist_sma = [cmd_hist_sma, filter(ones(1, windowSize) / windowSize, 1, this.cmd_hist(ind2, 2))];
relCmd_hist_sma = [relCmd_hist_sma, filter(ones(1, windowSize2) / windowSize2, 1, this.relCmd_hist(ind2, 2))];
figHandle = figure('OuterPosition', [100, 100, 768, 1024]); % static figure resolution/size
% Lateral Rectus
% Total muscle commands
subplot(3, 2, 1);
hold on;
grid on;
tmpSTD = movingstd(cmd_hist_sma(:, 1), windowSize, 'backward');
[hl1, hp1] = boundedline(xVal, ...
cmd_hist_sma(:, 1), ...
tmpSTD, ...
'alpha');
hl1.Color = [rand, rand, rand];
hp1.FaceColor = hl1.Color;
try
axis([windowSize * 2, length(cmd_hist_sma(:, 1)), ...
min(cmd_hist_sma(windowSize * 2 : end, 1) - tmpSTD(windowSize * 2 : end)) * 0.9, ...
max(cmd_hist_sma(windowSize * 2 : end, 1) + tmpSTD(windowSize * 2 : end)) * 1.1]);
end
xlabel('Iteration #', 'FontSize', 8);
ylabel(sprintf('Total Muscle\nCommands [%%]'), 'FontSize', 12);
title('Lateral Rectus', 'fontweight','normal');
% Delta muscle commands
xVal = [1 : length(relCmd_hist_sma)];
subplot(3, 2, 3);
hold on;
grid on;
tmpSTD = movingstd(relCmd_hist_sma(:, 1), windowSize2, 'backward');
[hl2, hp2] = boundedline(xVal, ...
relCmd_hist_sma(:, 1), ...
tmpSTD, ...
'alpha');
hl2.Color = [rand, rand, rand];
hp2.FaceColor = hl2.Color;
try
axis([windowSize2 * 2, length(relCmd_hist_sma(:, 1)), ...
min(relCmd_hist_sma(windowSize2 * 2 : end, 1) - tmpSTD(windowSize2 * 2 : end)) * 1.1, ...
max(relCmd_hist_sma(windowSize2 * 2 : end, 1) + tmpSTD(windowSize2 * 2 : end)) * 1.1]);
end
xlabel('Iteration #', 'FontSize', 8);
ylabel(strcat('\Delta', sprintf('Muscle\nCommands [%%]')), 'FontSize', 12);
% Medial Rectus
% Total muscle commands
xVal = [1 : length(cmd_hist_sma)];
subplot(3, 2, 2);
hold on;
grid on;
tmpSTD = movingstd(cmd_hist_sma(:, 2), windowSize, 'backward');
[hl3, hp3] = boundedline(xVal, ...
cmd_hist_sma(:, 2), ...
tmpSTD, ...
'alpha');
hl3.Color = [rand, rand, rand];
hp3.FaceColor = hl3.Color;
try
axis([windowSize * 2, length(cmd_hist_sma(:, 2)), ...
min(cmd_hist_sma(windowSize * 2 : end, 2) - tmpSTD(windowSize * 2 : end)) * 0.9, ...
max(cmd_hist_sma(windowSize * 2 : end, 2) + tmpSTD(windowSize * 2 : end)) * 1.1]);
end
xlabel('Iteration #', 'FontSize', 8);
% ylabel('Value', 'FontSize', 12);
% set(gca,'yaxislocation','right');
title('Medial rectus', 'fontweight','normal');
% Delta muscle commands
xVal = [1 : length(relCmd_hist_sma)];
subplot(3, 2, 4);
hold on;
grid on;
tmpSTD = movingstd(relCmd_hist_sma(:, 2), windowSize2, 'backward');
[hl4, hp4] = boundedline(xVal, ...
relCmd_hist_sma(:, 2), ...
tmpSTD, ...
'alpha');
hl4.Color = [rand, rand, rand];
hp4.FaceColor = hl4.Color;
try
axis([windowSize2 * 2, length(relCmd_hist_sma(:, 2)), ...
min(relCmd_hist_sma(windowSize2 * 2 : end, 2) - tmpSTD(windowSize2 * 2 : end)) * 1.1, ...
max(relCmd_hist_sma(windowSize2 * 2 : end, 2) + tmpSTD(windowSize2 * 2 : end)) * 1.1]);
end
xlabel('Iteration #', 'FontSize', 8);
% ylabel('Value', 'FontSize', 12);
% set(gca,'yaxislocation','right');
% Metabolic costs
xVal = [1 : length(metCost_hist_sma)];
subplot(3, 2, 5 : 6);
hold on;
grid on;
tmpSTD = movingstd(metCost_hist_sma, windowSize3, 'backward');
[hl5, hp5] = boundedline(xVal, ...
metCost_hist_sma, ...
tmpSTD, ...
'alpha');
hl5.Color = [rand, rand, rand];
hp5.FaceColor = hl5.Color;
try
axis([windowSize3 * 2, length(metCost_hist_sma), ...
min(metCost_hist_sma(windowSize3 * 2 : end) - tmpSTD(windowSize3 * 2 : end)) * 0.95, ...
max(metCost_hist_sma(windowSize3 * 2 : end) + tmpSTD(windowSize3 * 2 : end)) * 1.05]);
end
xlabel(sprintf('Iteration # (interval=%d)', this.interval), 'FontSize', 8);
ylabel('Value', 'FontSize', 12);
title('Metabolic Costs', 'FontSize', 14, 'FontWeight','normal');
% Subplot overall title
suptitle('Muscle Activities');
set(figHandle,'PaperPositionMode','auto'); % keep aspect ratio
plotpath = sprintf('%s/muscleGraphs', this.savePath);
saveas(gcf, plotpath, 'png');
else
% Medial Rectus
figure;
% Total muscle commands
xVal = [1 : length(cmd_hist_sma)];
subplot(3, 1, 1);
hold on;
grid on;
tmpSTD = movingstd(cmd_hist_sma, windowSize, 'backward');
[hl3, hp3] = boundedline(xVal, ...
cmd_hist_sma, ...
tmpSTD, ...
'alpha');
hl3.Color = [rand, rand, rand];
hp3.FaceColor = hl3.Color;
axis([windowSize * 2, length(cmd_hist_sma), ...
min(cmd_hist_sma(windowSize * 2 : end) - tmpSTD(windowSize * 2 : end)) * 0.9, ...
max(cmd_hist_sma(windowSize * 2 : end) + tmpSTD(windowSize * 2 : end)) * 1.1]);
xlabel('Iteration * interval^{-1}', 'FontSize', 8);
ylabel('Value', 'FontSize', 12);
title('Total Muscle Commands', 'fontweight','normal');
% Delta muscle commands
xVal = [1 : length(relCmd_hist_sma)];
subplot(3, 1, 2);
hold on;
grid on;
tmpSTD = movingstd(relCmd_hist_sma, windowSize2, 'backward');
[hl4, hp4] = boundedline(xVal, ...
relCmd_hist_sma, ...
tmpSTD, ...
'alpha');
hl4.Color = [rand, rand, rand];
hp4.FaceColor = hl4.Color;
axis([windowSize2 * 2, length(relCmd_hist_sma), ...
min(relCmd_hist_sma(windowSize2 * 2 : end) - tmpSTD(windowSize2 * 2 : end)) * 1.1, ...
max(relCmd_hist_sma(windowSize2 * 2 : end) + tmpSTD(windowSize2 * 2 : end)) * 1.1]);
xlabel('Iteration * interval^{-1}', 'FontSize', 8);
ylabel('Value', 'FontSize', 12);
title('\Delta Muscle Commands');
% Metabolic costs
subplot(3, 1, 3);
hold on;
grid on;
tmpSTD = movingstd(metCost_hist_sma, windowSize3, 'backward');
[hl5, hp5] = boundedline(xVal, ...
metCost_hist_sma, ...
tmpSTD, ...
'alpha');
hl5.Color = [rand, rand, rand];
hp5.FaceColor = hl5.Color;
axis([windowSize3 * 2, length(metCost_hist_sma), ...
min(metCost_hist_sma(windowSize3 * 2 : end) - tmpSTD(windowSize3 * 2 : end)) * 0.9, ...
max(metCost_hist_sma(windowSize3 * 2 : end) + tmpSTD(windowSize3 * 2 : end)) * 1.1]);
xlabel(sprintf('Iteration * interval^{-1} # (interval=%d)', this.interval), 'FontSize', 8);
ylabel('Value', 'FontSize', 12);
title('Metabolic Costs', 'FontSize', 14, 'FontWeight','normal');
% Subplot overall title
suptitle('Muscle Activities (medial rectus)');
plotpath = sprintf('%s/muscleGraphsMedialRectus', this.savePath);
saveas(gcf, plotpath, 'png');
end
if (this.rlModel.CActor.output_dim == 2)
% Muscle correlation check
% extract last x per cent of iterations
pcOffset = 0.01;
tmpOffset = ceil(size(this.cmd_hist, 1) * pcOffset);
tmpEnd = this.trainedUntil;
if tmpOffset > tmpEnd
tmpOffset = tmpEnd;
end
% Total
figure;
hold on;
% scatter(this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, 1), this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, 2), 5,'MarkerFaceColor',[0, 0.7, 0.7]);
histHandle = hist3(this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, :), [40, 40]);
corrl = corr(this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, 1), this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, 2));
xb = linspace(min(this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, 1)), max(this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, 1)), size(histHandle, 1));
yb = linspace(min(this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, 2)), max(this.cmd_hist(tmpEnd - tmpOffset : tmpEnd, 2)), size(histHandle, 1));
xlabel('Lateral rectus [%]', 'FontSize', 12);
ylabel('Medial rectus [%]', 'FontSize', 12);
title(strcat('Total Muscle Commands (training)', sprintf('\nCorrelation = %1.2e at last %d iterations', corrl, tmpOffset)));
pcHandle = pcolor(xb, yb, histHandle);
axis([0, max(xb(end), 0.01), 0, max(yb(end), 0.01)]);
shading interp;
set(pcHandle, 'EdgeColor', 'none');
colormap(createCM(1));
cb = colorbar();
cb.Label.String = '# Occurences';
plotpath = sprintf('%s/muscleTotalCmdTraining', this.savePath);
saveas(gcf, plotpath, 'png');
% Delta
figure;
hold on;
% scatter(this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, 1), this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, 2), 5,'MarkerFaceColor',[0, 0.7, 0.7]);
histHandle = hist3(this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, :), [40, 40]);
corrl = corr(this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, 1), this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, 2));
xb = linspace(min(this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, 1)), max(this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, 1)), size(histHandle, 1));
yb = linspace(min(this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, 2)), max(this.relCmd_hist(tmpEnd - tmpOffset : tmpEnd, 2)), size(histHandle, 1));
xlabel('Lateral rectus [%]', 'FontSize', 12);
ylabel('Medial rectus [%]', 'FontSize', 12);
title(strcat('\Delta Muscle Commands (training)', sprintf('\nCorrelation = %1.2e at last %d iterations', corrl, tmpOffset)));
pcHandle = pcolor(xb, yb, histHandle);
try
axis([xb(1), xb(end), yb(1), yb(end)]);
end
shading interp;
set(pcHandle, 'EdgeColor', 'none');
colormap(createCM(1));
cb = colorbar();
cb.Label.String = '# Occurences';
plotpath = sprintf('%s/muscleDeltaCmdTraining', this.savePath);
saveas(gcf, plotpath, 'png');
end
end
end
%% Weights
if (~isempty(find(level == 5)))
if (this.rlModel.continuous == 1)
figure;
hold on;
grid on;
subplot(3, 1, 1);
plot(this.weight_hist(:, 1), 'color', [0, 0.5882, 0.9608], 'LineWidth', 1.3);
ylabel('\Sigma \midweights\mid', 'FontSize', 12);
title('w_{Vji}');
subplot(3, 1, 2);
plot(this.weight_hist(:, 2), 'color', [0.5882, 0.9608, 0], 'LineWidth', 1.3);
ylabel('\Sigma \midweights\mid', 'FontSize', 12);
title('w_{Pji}');
subplot(3, 1, 3);
plot(this.weight_hist(:, 3), 'color', [1, 0.5098, 0.1961], 'LineWidth', 1.3);
xlabel(sprintf('Iteration # (interval=%d)', this.interval), 'FontSize', 12);
ylabel('\Sigma \midweights\mid', 'FontSize', 12);
title('w_{Pkj}');
plotpath = sprintf('%s/weightsDevelopment', this.savePath);
saveas(gcf, plotpath, 'png');
else
figure;
hold on;
grid on;
subplot(2, 1, 1);
plot(this.weight_hist(:, 1), 'color', [0, 0.5882, 0.9608], 'LineWidth', 1.3);
ylabel('\Sigma \midweights\mid', 'FontSize', 12);
title('w_{Vji}');
subplot(2, 1, 2);
plot(this.weight_hist(:, 2), 'color', [0.5882, 0.9608, 0], 'LineWidth', 1.3);
ylabel('\Sigma \midweights\mid', 'FontSize', 12);
xlabel(sprintf('Iteration # (interval=%d)', this.interval), 'FontSize', 12);
title('w_{Pji}');
plotpath = sprintf('%s/weightsDevelopment', this.savePath);
saveas(gcf, plotpath, 'png');
end
end
%% Reward composition
if (~isempty(find(level == 6)))
if (this.rlModel.continuous == 1)
windowSize = ceil(this.trainTime * 0.01);
% backward compatibility
if (length(this.recerr_hist) == length(this.trainTime))
tmp = this.recerr_hist(ind, :);
this.recerr_hist = zeros(this.trainTime / this.interval, size(this.recerr_hist, 2));
this.recerr_hist = tmp;
end
recerr_hist_sma = filter(ones(1, windowSize) / windowSize, 1, sum(this.recerr_hist, 2));
figure;
hold on;
grid on;
r = [- this.lambdaMet * metCost_hist_sma, ...
- this.lambdaRec * recerr_hist_sma];
handle = area(r, 'LineStyle','none');
xlabel(sprintf('Iteration # (interval=%d)', this.interval), 'FontSize', 12);
ylabel('Value', 'FontSize', 12);
l = legend('\lambdaMetabolic_{cost}', 'Reconstruction_{error}');
handle(1).FaceColor = [1, 0.25, 0];
handle(2).FaceColor = [1, 0.549, 0];
axis([windowSize, inf, -inf, 0]);
l.Location = 'best';
tmpstr = '\lambda = ';
title(strcat(sprintf('Reward composition (SMA)\n%s %6.4f, ~%4.1f%% Metabolic Costs',tmpstr, this.lambdaMet, (this.lambdaMet / 0.1622) * 100)));
plotpath = sprintf('%s/rewardComp', this.savePath);
saveas(gcf, plotpath, 'png');
else
figure;
hold on;
grid on;
plot(-sum(this.recerr_hist, 2), 'color', [1, 0.549, 0]);
xlabel(sprintf('Iteration # (interval=%d)', this.interval), 'FontSize', 12);
ylabel('Value', 'FontSize', 12);
title('Reward');
plotpath = sprintf('%s/rewardHistory', this.savePath);
saveas(gcf, plotpath, 'png');
end
%% TODO: implement plot solely for this.reward_hist
end
%% Testing performance as a function of traintime
if (~isempty(find(level == 7)))
% if ((~(isempty(this.testAt))) ...
% && (~(isempty(this.testHist))) ...
% && (this.testHist(1, 1) == 1.1593) ...
% && (size(this.testHist, 1) > 1)) ...
% && (this.testHist(end) ~= 0)
% sort fields in ascending order
% [this.testAt, sortIndex] = sort(this.testAt);
% this.testHist = this.testHist(sortIndex, :);
% RMSE vergErr
figure;
subplot(2, 2, 1);
hold on;
grid on;
plot(this.testAt, this.testHist(:, 1), 'x-', 'LineWidth', 1.3);
xlabel('Traintime', 'FontSize', 12);
% ylabel('RMSE(verg_{err}) [deg]', 'FontSize', 12);
ylabel('Mean(Red. of VE [%])', 'FontSize', 12);
% mean, std vergErr
subplot(2, 2, 2);
hold on;
grid on;
[hl, hp] = boundedline(this.testAt, this.testHist(:, 2), this.testHist(:, 3), 'alpha');
hl.Marker = 'x';
hl.MarkerSize = 5;
hl.Color = [rand, rand, rand];
hp.FaceColor = hl.Color;
hl.LineWidth = 1.6;
xlabel('Traintime', 'FontSize', 12);
% ylabel('|verg_{err}| [deg]', 'FontSize', 12);
ylabel('Median, IQR(Red. of VE [%])', 'FontSize', 12);
% RMSE deltaMetCost
subplot(2, 2, 3);
hold on;
grid on;
plot(this.testAt, this.testHist(:, 4), 'x-', 'LineWidth', 1.3);
xlabel('Traintime', 'FontSize', 12);
% ylabel('RMSE(|\Deltamc|)', 'FontSize', 12);
ylabel('Mean(Red. of MetCost [%])', 'FontSize', 12);
% mean, std deltaMetCost
subplot(2, 2, 4);
hold on;
grid on;
[hl, hp] = boundedline(this.testAt, this.testHist(:, 5), this.testHist(:, 6), 'alpha');
hl.Marker = 'x';
hl.MarkerSize = 5;
hl.Color = [rand, rand, rand];
hp.FaceColor = hl.Color;
hl.LineWidth = 1.6;
xlabel('Traintime', 'FontSize', 12);
% ylabel('|\Deltamc| = |mc_{actual} - mc_{desired}|', 'FontSize', 12);
ylabel('Median, IQR(Red. of MC [%])', 'FontSize', 12);
% Subplot overall title
suptitle('Test Performance vs. Traintime');
plotpath = sprintf('%s/testPerformanceVsTraintime', this.savePath);
saveas(gcf, plotpath, 'png');
% else
% % depricated backward ompatibility
% % try to update the model
% try
% % generate new model attributes
% model = this.copy();
% % set group path
% if (strcmp(model.savePath(1 : 10), '../results'))
% tmpStr = GetFullPath(model.savePath);
% tmpIndex = strfind(tmpStr, 'model');
% if (length(tmpIndex) == 1)
% model.savePath = strcat('/home/aecgroup/aecdata/Results/', tmpStr(tmpIndex(1) : end));
% else
% model.savePath = strcat('/home/aecgroup/aecdata/Results/', tmpStr(tmpIndex(1) : tmpIndex(2) - 2));
% end
% end
% % Get a list of all files and folders in this folder
% files = dir(model.savePath);
% % Get a logical vector that tells which is a directory
% dirFlags = [files.isdir];
% % Extract only those that are directories.
% subFolders = files(dirFlags);
% % generate testAt & testHist fields
% if (isempty(model.testAt))
% model.testAt = zeros(1, length(subFolders) - 2); % skip '.' and '..'
% end
% if (isempty(model.testHist))
% model.testHist = zeros(length(subFolders) - 2, 6); % skip '.' and '..'
% end
% % add modelAt0 entry
% if (str2num(subFolders(3).name(8 : end)) ~= 0)
% if (model.testAt(1) ~= 0)
% model.testAt = horzcat(0, model.testAt);
% end
% if (model.testHist(1, 1 : 2) ~= [1.1593, 1.3440])
% model.testHist = vertcat([1.1593, 1.3440, 1.5243, 1.0736, 1.1527, 0.9517], model.testHist);
% end
% end
% % Extract all relevant data
% for k = 3 : length(subFolders) % skip '.' and '..'
% % load intermediate model objects
% tmpModel = load(strcat(model.savePath, '/', subFolders(k).name, '/model.mat'));
% tmpModel = tmpModel.model;
% % get test iteration number
% model.testAt(k - 1) = str2num(subFolders(k).name(8 : end));
% % check if testing procedure needs to be repeated
% % generate test history, assumes testInterval = 20
% testInterval = tmpModel.interval * 2;
% % testInterval = 200;
% if (isempty(tmpModel.testResult7))
% warning('testResult7 is empty in %s, please execute testModelContiuous again.', subFolders(k).name);
% model.testHist(k - 1, :) = [sqrt(mean(tmpModel.testResult3(:, testInterval) .^ 2)), ...
% mean(abs(tmpModel.testResult3(:, testInterval) .^ 2)), ...
% std(abs(tmpModel.testResult3(:, testInterval) .^ 2)), ...
% 0, ...
% 0, ...
% 0];
% else
% model.testHist(k - 1, :) = [sqrt(mean(tmpModel.testResult3(:, testInterval) .^ 2)), ...
% mean(abs(tmpModel.testResult3(:, testInterval) .^ 2)), ...
% std(abs(tmpModel.testResult3(:, testInterval) .^ 2)), ...
% sqrt(mean(tmpModel.testResult7(:, testInterval) .^ 2)), ...
% mean(abs(tmpModel.testResult7(:, testInterval) .^ 2)), ...
% std(abs(tmpModel.testResult7(:, testInterval) .^ 2))];
% end
% end
% % sort fields in ascending order
% [model.testAt, sortIndex] = sort(model.testAt);
% model.testHist = model.testHist(sortIndex, :);
% % overwrite old model
% save(strcat(model.savePath, '/model'), 'model');
% % try to plot again
% model.allPlotSave(7);
% catch
% error('One or more file operations failed. Check path strings and directory items.');
% end
% end
end
%% Binocularity plot
if (~isempty(find(level == 8)))
for s = 1:length(this.scModel)
this.plotBinocularity(s, this.savePath);
end
end
% Fit Gabors to BFs and plot histogram of orientations and
% disparities
if (~isempty(find(level == 9)))
fname = strcat(this.savePath, '/modelAt', num2str(this.trainedUntil));
mkdir(fname);
% Thresfold used to exculde gabor fits with high residual error
Threshold = 0.2;
for s = 1:length(this.scModel)
% if isempty(this.scModel{s}.BFfitFreq)
% fitFreq = 0; % for older models, use former procedure
% else
% fitFreq = this.scModel{s}.BFfitFreq;
% end
fitFreq = 1;
name = strcat(fname, '/scModel', num2str(s));
for eye = 1:3
if fitFreq == 1
name_orientation_plot = strcat(name, 'eyes', num2str(eye), '_freq');
else
name_orientation_plot = strcat(name, 'eyes', num2str(eye)); % for fitting wavelength instead of frequency
end
[Fitted_Gabor_Filter_Parameters, Error] = Gabor_Fitting_for_Basis(this.scModel{s}.basis, eye, fitFreq);
% Save fit parameters and errors
save(name_orientation_plot, 'Fitted_Gabor_Filter_Parameters', 'Error')
% Exclude fits with high error
idx = find(Error < Threshold);
% Plot histogram of disparities, only for both eyes
if eye == 1
hh = figure;
bins_disp = -8.5:1:8.5;
res = [];
for i=1:length(idx)
% res(end+1) = (Fitted_Gabor_Filter_Parameters(i,3)/(2*pi)*cos(Fitted_Gabor_Filter_Parameters(i,2)))*(Fitted_Gabor_Filter_Parameters(i,5)-Fitted_Gabor_Filter_Parameters(i,6));
res(end+1) = (Fitted_Gabor_Filter_Parameters(i,3)/(2*pi*cos(Fitted_Gabor_Filter_Parameters(i,2))))*(Fitted_Gabor_Filter_Parameters(i,5)-Fitted_Gabor_Filter_Parameters(i,6));
end
N = histcounts(res, bins_disp);
bar(bins_disp(1:end-1)+0.5, (N./sum(N))*100, 1);
grid on;
xlabel('\theta [deg]')
ylabel('Percentage of Bases [%]')
xlim([-8.5 8.5]);
xticks([-8:1:8]);
% ylim([0 60]);
set(gca,'FontSize',12,'fontWeight','bold'); %,'FontName','Courier')
set(findall(hh,'type','text'),'FontSize',15,'fontWeight','bold'); %,'FontName','Courier')
text(5, 50, strcat('N = ', num2str(length(idx))), 'FontSize', 12,'fontWeight','bold');
saveas(hh, strcat(strcat(name, '_disparities'), '.png'),'png');
end
% Histogram of Thetas
h = figure;
bins = -7.5:15:172.5; %0:15:165;% Edges of the bins. Bin centred around 0 deg and bin centred around 180 deg corresponds
% to same orientation (vertical)
N = histcounts(mod(Fitted_Gabor_Filter_Parameters(idx,2)*180/pi+7.5, 180)-7.5,bins);
bar(bins(1:end-1)+7.5, (N./sum(N))*100, 1);
grid on;
xlabel('\theta [deg]');
ylabel('Percentage of Bases [%]');
xticks([0, 45, 90, 135]);
xlim([-7.5 172.5]);
ylim([0 50]);
set(gca,'FontSize',12,'fontWeight','bold'); %,'FontName','Courier')
set(findall(h,'type','text'),'FontSize',15,'fontWeight','bold'); %,'FontName','Courier')
text(140, 45, strcat('N = ', num2str(length(idx))), 'FontSize', 12,'fontWeight','bold');
saveas(h, strcat(name_orientation_plot, '_orientations', '.png'),'png');
sprintf('Eye %d done', eye)
end
sprintf('Scale %d done', s)
end
end
end
%%% Generates anaglyphs of the large and small scale fovea and
% one of the two unpreprocessed gray scale images
% If you desire the save images to have the same size, open a new
% figure before starting this script with
% figure('Position', [0, 0, 960, 640])
% After generating all images, create a video with
% avconv -r 10 -i anaglyphs%3d.png -b:v 1000k video.mp4
function generateAnaglyphs(this, identifier, markScales, infos, imgNumber, saveTag)
numberScales = length(this.scModel);
imgOrigSize = [240, 320];
% defining colors in the image: (from larges to smallest scale)
scalingColors = {'blue', 'red', 'green'};
textColor = 'yellow';
% textColor = 'green';
scaleImages = cell(numberScales);
if ~isdir(sprintf('%s/movies/', this.savePath))
mkdir(sprintf('%s/movies/', this.savePath));
end
for scale = 1 : numberScales
% Downsampling Large
% imgLeft = this.imgGrayLeft(:);
% imgLeft = reshape(imgLeft, size(this.imgGrayLeft));
% imgRight = this.imgGrayRight(:);
% imgRight = reshape(imgRight, size(this.imgGrayRight));
imgLeft = this.imgGrayLeft(end/2+1 - 120 : end/2 + 120, end/2+1 - 160 : end/2 + 160);
imgRight = this.imgGrayRight(end/2+1 - 120 : end/2 + 120, end/2+1 - 160 : end/2 + 160);
for ds = 1 : log2(this.dsRatio(scale))
imgLeft = impyramid(imgLeft, 'reduce');
imgRight = impyramid(imgRight, 'reduce');
end
% cut fovea in the center
[h, w, ~] = size(imgLeft);
cutOutVertical = [fix(h / 2 + 1 - this.pxFieldOfView(scale) / 2), fix(h / 2 + this.pxFieldOfView(scale) / 2)];
cutOutHorizontal = [fix(w / 2 + 1 - this.pxFieldOfView(scale) / 2), fix(w / 2 + this.pxFieldOfView(scale) / 2)];
imgLeft = imgLeft(cutOutVertical(1) : cutOutVertical(2) , cutOutHorizontal(1) : cutOutHorizontal(2));
imgRight = imgRight(cutOutVertical(1) : cutOutVertical(2) , cutOutHorizontal(1) : cutOutHorizontal(2));
% create an anaglyph of the two pictures, scale it up and save it
anaglyph = imfuse(imgLeft, imgRight, 'falsecolor');
% [hAna, vAna, ~] = size(anaglyph);
if (markScales)
anaglyph = insertShape(anaglyph, 'rectangle', [this.pxFieldOfView(scale) + 1 - this.patchSize, 1, this.patchSize, this.patchSize], 'color', scalingColors(scale));
end
scaleImages{scale} = anaglyph;
end
% imwrite(anaglyph, [savePath '/anaglyph.png']);
anaglyph = imfuse(this.imgGrayLeft(end/2+1 - 120 : end/2 + 120, end/2+1 - 160 : end/2 + 160), this.imgGrayRight(end/2+1 - 120 : end/2 + 120, end/2+1 - 160 : end/2 + 160), 'falsecolor');
[h, w, ~] = size(anaglyph);
if (markScales)
for scale = 1:numberScales
% this creates a rectangle inside the image for each scale and
% an examplary patch
scaleSizeOrigImg = this.pxFieldOfView(scale) * this.dsRatio(scale);
patchSizeOrigImg = this.patchSize * this.dsRatio(scale);
windowStart = [fix(w / 2 + 1 - scaleSizeOrigImg / 2), fix(h / 2 + 1 - scaleSizeOrigImg / 2)];
% indexMatrix = [x, y, scaleWindow, scaleWindow; x, y, patchSize, patchSize]
% indexMatrix = [windowStart(1), windowStart(2), scaleSizeOrigImg, scaleSizeOrigImg; windowStart(1), windowStart(2), patchSizeOrigImg, patchSizeOrigImg];
% try on other side
indexMatrix = [windowStart(1), windowStart(2), scaleSizeOrigImg, scaleSizeOrigImg; windowStart(1)+scaleSizeOrigImg-patchSizeOrigImg, windowStart(2), patchSizeOrigImg, patchSizeOrigImg];
anaglyph = insertShape(anaglyph, 'rectangle', indexMatrix, 'Color', scalingColors(scale)); % whole region of scale
end
end
% anaglyph = imfuse(leftGray, rightGray, 'falsecolor');
% imwrite(anaglyph, sprintf('%s/anaglyph%.3d.png', savePath, identifier));
% imwrite(imresize(anaglyphL, 20), [savePath '/anaglyphLargeScale.png']);
% imwrite(anaglyphL, sprintf('%s/anaglyphLargeScale%.3d.png', savePath, identifier));
% largeScaleView = imfuse(imgLeftL, imgRightL, 'montage');
% imwrite(imresize(largeScaleView, 20), sprintf('%s/LargeScaleMontage%.3d.png', savePath, identifier));
% imwrite(imresize(anaglyphS, 8), [savePath '/anaglyphSmallScale.png']);
% imwrite(anaglyphS, sprintf('%s/anaglyphSmallScale%.3d.png', savePath, identifier));
% smallScaleView = imfuse(imgLeftL, imgRightL, 'montage');
% imwrite(imresize(smallScaleView, 8), sprintf('%s/smallScaleMontage%.3d.png', savePath, identifier));
% xRange = [0, 320]; yRange = [0, 240];
%% infos = {iteration, tmpObjRange, vseRange(vseIndex), angleDes, angleNew, command', relativeCommand', reward, recErrorArray};
% xPos = [10, 220, 10, 10, 260, 10, 10, 240]; %display in headful modus: [10, 200, 10, 10, 260, 10, 10]
% yPos = [10, 10, 230, 220, 230, 190, 200, 220];
% % imName = strsplit(currentTexture, '/'); % stable renderer
% % imName = strsplit(texture{currentTexture}, '/'); % experimental renderer
% imName = strsplit(imgNumber, '/'); % still necessary / functional?
% imName = imName{end};
% insert = {sprintf('Image: \t%s', imName), ...
% sprintf('Object distance: \t%.2f', infos{2}), ...
% sprintf('Vergence Error: \t%.3f', infos{4}), ...
% sprintf('Start Vergence Error: \t%.3f', infos{3}), ...
% sprintf('Iteration: \t%d', infos{1}), ...
% sprintf('Muscle Activation: \t%.3f, \t%.3f', infos{5}(1), infos{5}(2)), ...
% sprintf('Relative Command: \t%.3f, \t%.3f', infos{6}(1), infos{6}(2)), ...
% sprintf('Reward: \t%.3f', infos{7})};
angleFix = this.baseline / (2 * tand(infos{5} / 2));
% xPos = [10, 220, 220, 10, 10, 260, 10, 10, 240]; %display in headful modus: [10, 200, 10, 10, 260, 10, 10]
xPos = [10, 220, 220, 10, 10, 260, 10, 10, 10];
yPos = [10, 10, 20, 230, 220, 230, 190, 200, 220];
imName = strsplit(imgNumber, '/');
imName = imName{end};
insert = { sprintf(''), ... %sprintf('Image: \t%s', imName), ...
sprintf('Object distance: %.2f', infos{2}), ...
sprintf('Fixation depth: \t%.2f', angleFix), ...
sprintf('Vergence Error: %.3f', infos{4} - infos{5}), ... %sprintf('Vergence Error: \t%.3f', infos{4} - infos{5})
sprintf(''), ... %sprintf('Start Vergence Error: \t%.3f', infos{3}), ...
sprintf(''), ... %sprintf('Iteration: \t%d', infos{1}), ...
sprintf('Muscle Activation: \t%.5f, \t%.5f', infos{6}(1), infos{6}(2)), ...
sprintf('Relative Command: \t%.5f, \t%.5f', infos{7}(1), infos{7}(2)), ...
sprintf('Reward: \t%.3f', infos{8}), ...
};
% fig = figure('Position', [0, 0, 960, 640]); % create a figure with a specific size
% subplot(2,3,[1,2,4,5]);
% title(sprintf('Object distance: %d,\titeration: %d,\tvergence error: %d', infos(1), infos(2), infos(3)));
% subplot('Position', [0.05, 0.12, 0.6, 0.8]); % figure('Position', [0, 0, 960, 640]);
goldenRatio = 1 / ((1 +sqrt(5))/2);
goldenRatio = 0.7273;%0.65;
subplot('Position', [0, 0, goldenRatio, 1]);
imshow(anaglyph, 'initialMagnification', 'fit');
%% add text description --> removed for RDS video
text(xPos, yPos, insert, 'color', textColor); % these numbers resemble white, but white will appear black after saving ;P
text(10, 20, num2str(size(anaglyph)), 'color', 'yellow'); %[1-eps, 1-eps, 1-eps]
% text(230, 10, sprintf('Object distance: %.2f', infos{2}), 'color', 'yellow');
% title('Anaglyph');
% subplot(2,3,3);
% positionVector = [left, bottom, width, height], all between 0 and 1
sizeScaleImg = 0.6 / numberScales;
for sp = 1:numberScales
if (sp == 1)
% subplot('Position', [0.7, 0.9 - sizeScaleImg, sizeScaleImg, sizeScaleImg]);% figure('Position', [0, 0, 960, 640]);
subplot('Position', [goldenRatio, 0.5, 1-goldenRatio, 0.5])
else
% subplot('Position', [0.7, 0.9 - ((sizeScaleImg + 0.05) * sp), sizeScaleImg, sizeScaleImg]);% figure('Position', [0, 0, 960, 640]);
subplot('Position', [goldenRatio, 0, 1-goldenRatio, 0.5])
end
imshow(scaleImages{sp});
% descr = {sprintf('Scale %d', sp), sprintf('Reconstruction Error: %.3f', infos{9}(sp))};
descr = {sprintf('Scale %d', sp)};
% todo: the fist two values in text have to be adapted, especially
% for more than 2 scales, also the size of the letters,
text(0.03, 0.1, descr, 'color', textColor, 'Units', 'normalized')
end
if ~isempty(saveTag)
% saveas(fig, sprintf('%s/anaglyph.png', this.savePath), 'png');
saveas(gcf, sprintf('%s/movies/anaglyphs%s_%03d.png', this.savePath, saveTag, identifier), 'png');
end
% fig.delete();
end
%%% Saturation function that keeps motor commands in [0, 1]
% corresponding to the muscelActivity/metabolicCost tables
function [cmd] = checkCmd(this, cmd)
i0 = cmd < 0;
cmd(i0) = 0;
i1 = cmd > 1;
cmd(i1) = 1;
end
%%% Creates a movement trajectory from the given paramters and plots it
% on the plane of object depth.
% Note that this script is intended solely for continuous models.
% param objDist: the object distance
% param startVergErr: the vergence error to muscles start with
% param initMethod: either 'simple' or 'random'
% param numIters: number of iterations that are executed
% param stimuliIndices: an array of indizes from the texture files
% param simulator: either a simulator object or [] for a new one
% param titleStr: string identifier that is used for the title and the saved image
% param savePlot: true or false if the resulting plots should be saved
function h = plotTrajectory(this, objDist, startVergErr, initMethod, numIters, stimuliIndices, simulator, directory, titleStr, savePlot)
% simulator check
if (isempty(simulator))
error('An initialized simulator is necessary to continue.\nPlease execute simulator = prepareSimulator();');
end
%%% Saturation function that keeps motor commands in [0, 1]
% corresponding to the muscelActivity/metabolicCost tables
function [cmd] = checkCmd(cmd)
i0 = cmd < 0;
cmd(i0) = 0;
i1 = cmd > 1;
cmd(i1) = 1;
end
% preperation
rng(22);
if strcmp(initMethod, 'advanced')
initMethod = uint8(0);
elseif strcmp(initMethod, 'fixed')
initMethod = uint8(1);
% hand-picked inits for muscles, used in initMethod 'random'
cmdInit = [[0.03; 0.16], [0.05; 0.12], [0.07; 0.12], [0.04; 0.08], [0.06; 0.06], [0.08; 0.06]];
elseif strcmp(initMethod, 'simple')
initMethod = uint8(2);
elseif strcmp(initMethod, 'perturbed')
initMethod = uint8(3);
else
error('Muscle initialization method %s not supported.', initMethod);
end
plotAnaglyphs = false;
nStimuli = length(stimuliIndices);
trajectory = zeros(length(objDist), length(startVergErr), nStimuli, numIters + 1, 2);
%% main loop
if (plotAnaglyphs == true)
figure;
figIter = 1;
end
command = [0.07, 0.1];
% vergErrMax = 2;
% angleMin = (atand(this.baseline / (2 * this.objDistMax)) * 2) - vergErrMax; %angle for both eyes
% angleMax = (atand(this.baseline / (2 * this.objDistMin)) * 2) + vergErrMax;
angleMinT = 0;
angleMaxT = (atand(this.baseline / (2 * this.objDistMax)) * 2) + 6;
for odIndex = 1 : length(objDist)
angleDes = 2 * atand(this.baseline / (2 * objDist(odIndex)));
for stimIter = 1 : nStimuli
currentTexture = stimuliIndices(stimIter);
for vergErrIndex = 1 : length(startVergErr)
% muscle init
if (initMethod == 0)
try
% catch negative/divergent vergence angle
[command, angleNew] = this.getMFedood(objDist(odIndex), min(startVergErr(vergErrIndex), this.getVergErrMax(objDist(odIndex))));
catch
% catch non-existing variables error, occuring in non-up-to-date models
try
clone = this.copy();
delete(this);
clear this;
this = clone;
[command, angleNew] = this.getMFedood(objDist(odIndex), startVergErr(vergErrIndex));
clear clone;
catch
% catch when new model property isn't present in Model class yet
error('One or more new model properties (variables) are not present in Model.m class yet!');
end
end
elseif (initMethod == 1)
command = cmdInit(:, stimIter);
angleNew = this.getAngle(command) * 2;
elseif (initMethod == 2)
[command, angleNew] = this.getMF2(objDist(odIndex), startVergErr(vergErrIndex));
elseif (initMethod == 3)
% 'perturbed' init: take the last command and add a random vector to it but stay within boundaries
dummy = true;
commandOld = command;
rangeMax = 0.02;
while dummy
[relativeCommand(1, 1), relativeCommand(2, 1)] = pol2cart(rand(1) * pi * 2, rand(1) * rangeMax);
command = command + relativeCommand;
command = checkCmd(command);
angleNew = this.getAngle(command) * 2;
if (angleNew > angleMinT) && (angleNew < angleMaxT)
dummy = false;
else
command = commandOld;
rangeMax = rangeMax + 0.005;
end
end
end
trajectory(odIndex, vergErrIndex, stimIter, 1, :) = command;
for iter = 1 : numIters
this.refreshImagesNew(simulator, currentTexture, angleNew / 2, objDist(odIndex), 3, [0,0,0]);
%% change left and right images to simulate altered rearing conditions
if ~isempty(this.filterLeft)
% model.imgGrayLeft = conv2(model.imgGrayLeft, model.filterLeft, 'same');
% sligthly faster version
this.imgGrayLeft = conv2(this.filterLeft{1}, this.filterLeft{2}, this.imgGrayLeft, 'same');
end
if ~isempty(this.filterRight)
% model.imgGrayRight = conv2(model.imgGrayRight, model.filterRight, 'same');
% slightly faster version
this.imgGrayRight = conv2(this.filterRight{1}, this.filterRight{2}, this.imgGrayRight, 'same');
end
% show anaglyphs for quit performance check
if (plotAnaglyphs && ((iter == 1) || (iter == numIters)))
subplot(length(objDist) * length(startVergErr) * nStimuli, 2, figIter);
% imshow(stereoAnaglyph(this.imgGrayLeft, this.imgGrayRight))
imshow(imfuse(this.imgGrayLeft, this.imgGrayRight, 'falsecolor'))
if (iter == 1)
title(sprintf('fix. depth = %1.1fm (%.3f°)\nverg. error = %.3f', ...
(this.baseline / 2) / tand(angleNew / 2), angleNew, angleDes - angleNew));
end
figIter = figIter + 1;
end
for i = 1 : length(this.scModel)
this.preprocessImage(i, 1);
this.preprocessImage(i, 2);
currentView{i} = vertcat(this.patchesLeft{i}, this.patchesRight{i});
end
[bfFeature, ~, ~] = this.generateFR(currentView); % encode image patches
if (this.normFeatVect == 0)
%% Standard feature vector compilation:
% append muscle activities to basis function vector
feature = [bfFeature; command * this.lambdaMuscleFB];
else
%% Normalized feature vector:
% z-transform raw feature vector (no muscle feedback scaling)
feature = [bfFeature; command];
for i = 1 : length(feature)
feature(i) = this.onlineNormalize(this.trainedUntil, feature(i), i, 0);
end
feature = [feature(1 : end - 2); feature(end - 1 : end) * this.lambdaMuscleFB];
end
%% bias analysis
if (this.rlModel.bias > 0)
feature = [feature; this.rlModel.bias];
end
relativeCommand = this.rlModel.act(feature); % generate change in muscle activity
command = checkCmd(command + relativeCommand); % calculate new muscle activities
angleNew = this.getAngle(command) * 2; % transform into angle
trajectory(odIndex, vergErrIndex, stimIter, iter + 1, :) = command;
if (plotAnaglyphs && (iter == numIters))
title(sprintf('fix. depth = %1.1fm (%.3f°)\nverg. error = %.3f', ...
(this.baseline / 2) / tand(angleNew / 2), angleNew, angleDes - angleNew));
end
end
end
end
end
%% Plot results
h = figure();
hold on;
if (isempty(titleStr))
title('Object Fixation Trajectories');
else
title(sprintf('Object Fixation Trajectories\nmodel trained for #%s iterations', titleStr));
end
% pcHandle = pcolor(this.degreesIncRes); % use vergence degree as color dimension (background)
pcHandle = pcolor(this.metCostsIncRes .* 2); % use metabolic costs as color dimension (background)
% shading interp;
set(pcHandle, 'EdgeColor', 'none');
% colormap(createCM(3));
cb = colorbar();
% cb.Label.String = 'vergence degree'; % use vergence degree as color dimension (background)
cb.Label.String = 'metabolic costs'; % use metabolic costs as color dimension (background)
ax = gca;
set(ax, 'Layer','top'); % bring axis to the front
ax.XTick = linspace(1, size(this.degreesIncRes, 2), 11);
ax.YTick = linspace(1, size(this.degreesIncRes, 1), 11);
ax.XTickLabel = strsplit(num2str(linspace(0, 10, 11)));
ax.YTickLabel = strsplit(num2str(linspace(0, 20, 11)));
axis([1, size(this.degreesIncRes, 2), 1, size(this.degreesIncRes, 1)]);
for odIndex = 1 : length(objDist)
% draw +1 pixel offset in respect to desired vergence distance
[lateralDes, medialDes] = this.getAnglePoints(objDist(odIndex), 0.22);
plot(lateralDes ./ this.scaleFacLR, medialDes ./ this.scaleFacMR, ...
'color', [0, 0.5882, 0], 'LineStyle', ':', 'LineWidth', 1.8);
% draw -1 pixel offset in respect to desired vergence distance
[lateralDes, medialDes] = this.getAnglePoints(objDist(odIndex), -0.22);
plot(lateralDes ./ this.scaleFacLR, medialDes ./ this.scaleFacMR, ...
'color', [0, 0.5882, 0], 'LineStyle', ':', 'LineWidth', 1.8);
% draw a line of points into the plane that represent the desired vergence
[lateralDes, medialDes] = this.getAnglePoints(objDist(odIndex), 0);
plot(lateralDes ./ this.scaleFacLR, medialDes ./ this.scaleFacMR, ...
'color', [0.6510, 1.0000, 0.6588], 'LineWidth', 1.8);
% add corresponding distance value to desired vergence graph
text(lateralDes(end - ceil(length(lateralDes) / 10)) / this.scaleFacLR, ...
medialDes(end - ceil(length(medialDes) / 10)) / this.scaleFacMR, ...
sprintf('%3.1fm', objDist(odIndex)));
end
% draw trajectories
for odIndex = 1 : length(objDist)
% tmpRed = [rand, 0, 0];
% lightOrange = [1, 201 / 255, 41 / 255]
% orange = [1, 94 / 255, 41 / 255],
for stim = 1 : length(stimuliIndices)
for vergErrIndex = 1 : length(startVergErr)
% trajectory(:, :, :, :, 1) = trajectory(:, :, :, :, 1) - 0.7;
% trajectory(:, :, :, :, 2) = trajectory(:, :, :, :, 2) - 0.6;
% first plot all
hl2 = plot(reshape(trajectory(odIndex, vergErrIndex, stim, :, 1), [numIters + 1, 1]) ./ this.scaleFacLR + 1, ...
reshape(trajectory(odIndex, vergErrIndex, stim, :, 2), [numIters + 1, 1]) ./ this.scaleFacMR + 1, ...
'color', [1, 201 / 255, 41 / 255], 'LineWidth', 1);
% plot iter 1-interval in differen color if numIters >= model.interval
if (numIters >= this.interval)
hl1 = plot(reshape(trajectory(odIndex, vergErrIndex, stim, 1 : (this.interval * 2), 1), [this.interval * 2, 1]) ./ this.scaleFacLR + 1, ...
reshape(trajectory(odIndex, vergErrIndex, stim, 1 : (this.interval * 2), 2), [this.interval * 2, 1]) ./ this.scaleFacMR + 1, ...
'-or', 'LineWidth', 1, 'MarkerEdgeColor','k', 'MarkerFaceColor', 'r', 'MarkerSize', 4);
else
hl1 = [];
end
% plot init point
% plot(trajectory(odIndex, vergErrIndex, stim, 1, 1) / this.scaleFacLR + 1, ...
% trajectory(odIndex, vergErrIndex, stim, 1, 2) / this.scaleFacMR + 1, ...
% 'MarkerEdgeColor','k', 'MarkerFaceColor', 'r', 'MarkerSize', 4);
% plot destination point
hl3 = plot(trajectory(odIndex, vergErrIndex, stim, end, 1) / this.scaleFacLR + 1, ...
trajectory(odIndex, vergErrIndex, stim, end, 2) / this.scaleFacMR + 1, ...
'o', 'LineWidth', 1, 'MarkerEdgeColor','k', 'MarkerFaceColor', 'g', 'MarkerSize', 4);
end
end
end
% just use last handle instanciation for legend
% hl1.DisplayName = sprintf('first %d iterations', this.interval);
% hl2.DisplayName = sprintf('additional %d iterations', numIters - this.interval);
% hl3.DisplayName = 'end fixation';
% l = legend([hl1, hl2, hl3]);
% % l.FontSize = 7;
% l.Orientation = 'horizontal';
% l.Location = 'southoutside';
% l.Box = 'off';
xlabel('lateral rectus activation [%]');
ylabel('medial rectus activation [%]');
if (savePlot == 1)
if (~isempty(directory))
plotpath = sprintf('%s/muscleActivityTrajectory.png', directory);
else
plotpath = 'muscleActivityTrajectory.png';
end
saveas(h, plotpath, 'png');
end
end
%%% This methods displays the current binocular basis functions of the model.
% If the history of basis functions was saved, their development is displayed.
% pathExtension lets you specify another subfolder or name
% extension for the saved image.
function displayBasis(this, savePlot, pathExtension)
% r = 16; c = 18; %how to arrange the basis in rows and cols
r = 20;
numScales = length(this.scModel);
len = size(this.scModel{1}.basisHist, 3); %# of trials saved
basisTrack = cell(numScales, len); %variable to store all the saved basis
for scale = 1:numScales
if len == 1
basisTrack{scale, 1} = this.scModel{scale}.basis;
else
for j = 1:len
basisTrack{scale, j} = this.scModel{scale}.basisHist(:, :, j);
end
end
end
h = figure(1);
scrsz = get(0,'ScreenSize');
set(h,'Position',[scrsz(1) scrsz(2) scrsz(3) scrsz(4)]);
% loop over scales
for s = 1:numScales
% sort basis according to left energy norm
endBasis = basisTrack{s,end}(1:end/2,:);
leftEnergy = abs(sum(endBasis.^2)-0.5);
[~,I] = sort(leftEnergy);
subplot(1,numScales,s);
[di,num] = size(basisTrack{s,1});
fun1 = @(blc_struct) padarray(padarray(reshape(permute(padarray(reshape(blc_struct.data, sqrt(di / 2), ...
sqrt(di / 2), 2), [1, 1], 'pre'), [1, 3, 2]), (sqrt(di / 2) + 1) * 2, sqrt(di / 2) + 1), [1, 1], ...
'post') - 1, [1 1], 'pre') + 1;
for j = 1:len
A = basisTrack{s,j}(:,I);
% B = reshape(A,di*r,c);
B = reshape(A,di * r,num / r);
B = B/max(max(abs(B))) + 0.5;
C = padarray(padarray(blockproc(B,[di,1],fun1)-1,[1 1],'post')+1,[2,2]);
imshow(C);
title(sprintf('Basis functions at\n%d%% of training\n(scale %d)', ceil((j / len) * 100), s))
% title(num2str(this.trainTime*0.1*(j-1)));
drawnow; pause(.001);
end
end
if savePlot
saveas(h, sprintf('%s/%sbasisFunctions.png', this.savePath, pathExtension), 'png');
end
end
% displays the most selected basis functions defined by shape
% usage: displaySelectedBasis([2, 5], '10mostSelBFs')
function displaySelectedBasis(this, shape, savePlot, pathExtension)
r = 20;
number = shape(1) * shape(2);
numScales = length(this.scModel);
[~, inds] = sort(this.scModel{1}.selectedBasis, 'descend');
inds = inds(1:number);
h = figure(1);
% scrsz = get(0,'ScreenSize');
% set(h,'Position',[scrsz(1) scrsz(2) scrsz(3) scrsz(4)]);
% loop over scales
for s = 1:numScales
% sort basis according to left energy norm
endBasis = this.scModel{s}.basis(:, inds);
% leftEnergy = abs(sum(endBasis.^2)-0.5);
% [~,I] = sort(leftEnergy);
subplot(1,numScales,s);
% [di,num] = size(basisTrack{s,1});
[di,num] = size(endBasis);
fun1 = @(blc_struct) padarray(padarray(reshape(permute(padarray(reshape(blc_struct.data, sqrt(di / 2), ...
sqrt(di / 2), 2), [1, 1], 'pre'), [1, 3, 2]), (sqrt(di / 2) + 1) * 2, sqrt(di / 2) + 1), [1, 1], ...
'post') - 1, [1 1], 'pre') + 1;
B = reshape(endBasis,di*shape(1),shape(2));
B = B/max(max(abs(B))) + 0.5;
C = padarray(padarray(blockproc(B,[di,1],fun1)-1,[1 1],'post')+1,[2,2]);
imshow(C);
% title(sprintf('%d most often selected Basis functions\n at %d%% of training\n(scale %d)', number, ceil((this.trainedUntil / this.trainTime) * 100), s))
title(sprintf('%d most often selected\nBasis functions\n(scale %d)', number, s))
% title(num2str(this.trainTime*0.1*(j-1)));
end
if savePlot
saveas(h, sprintf('%s/%s%dmostSelBFs.png', this.savePath, pathExtension, number), 'png');
end
end
end
end
% %%% Not overlapping Patch generation
% function patchesNoOv = preprocessImageNoOv(img, fovea, downSampling, patchSize)
% img = .2989 * img(:,:,1) + .5870 * img(:,:,2) + .1140 * img(:,:,3);
% for i = 1:log2(downSampling)
% img = impyramid(img, 'reduce');
% end
% % convert to double
% img = double(img);
% % cut fovea in the center
% [h, w, ~] = size(img);
% img = img(fix(h / 2 + 1 - fovea / 2) : fix(h / 2 + fovea / 2), ...
% fix(w / 2 + 1 - fovea / 2) : fix(w / 2 + fovea / 2));
% % cut patches and store them as col vectors
% % no overlapping patches (for display)
% patchesNoOv = im2col(img, [patchSize patchSize], 'distinct');
% end
% %%% Generation of random vergence angles according to truncated Laplace distribution
% function l = truncLaplacian(diversity, range)
% % see wikipedia for the generation of random numbers according to the
% % LaPlace distribution via the inversion method
% r = rand;
% switch r < 0.5
% case 1
% l = 1 / diversity * log(2 * r);
% case 0
% l = -1 / diversity * log(2 * (1 - r));
% end
% if (abs(l) > range)
% l = 0;
% end
% end