%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  calc_VIDEVAL_feat.m
%
%  This function extract the VIDEVAL feature vector (60-dim)
%
%
%  Input:
%           test_video:             Path to the test video file (YUV420 format)
%           width, height:          Resolution of the YUV video
%           framerate:              number of frames per second
%
%  Output:
%           VIDEVAL_all_features:   Resulting VIDEVAL feature vector (60-dim)
%
% Note: this is a light version of VIDEVAL, which has been spatially and
%       temporally sped up.
%
%  - Resized frames to 720p if larger than 720p keeping aspect ratio
%  - Sampled at most 6 frames for each 1-second block

%%
function [VIDEVAL_all_features] = calc_VIDEVAL_feats_light(test_video, ...
        width, height, framerate, max_reso, frs_per_blk)
    % setup speededup params
%     max_reso = 480;
%     frs_per_blk = 3;

    % subsampling ratio
    ratio = max_reso / min(width, height);
    fr_step = max(floor(framerate / frs_per_blk),1);

    % Try to open test_video; if cannot, return
    test_file = fopen(test_video,'r');
    if test_file == -1
        fprintf('Test YUV file not found.');
        VIDEVAL_all_features = [];
        return;
    end
    % Open test video file
    fseek(test_file, 0, 1);
    file_length = ftell(test_file);
    fprintf('Video file size: %d bytes (%d frames)\n',file_length, ...
            floor(file_length/width/height/1.5));
    % get frame number
    frame_start = 1;
    frame_end = (floor(file_length/width/height/1.5)-3);
    first_frame_loaded = 0;

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Loop through all the frames in the frame_range to compute frame features
    %
    fprintf('Computing frame features on frames %d..%d\n', ...
            frame_start, frame_end);

    frame_features_all = [];
    for i = frame_start:fr_step:frame_end
        % Read frames i-i, i and i+1 (note that frame_start must be > 0)
        if first_frame_loaded
            % prev_YUV_frame = next_YUV_frame;
            prev_YUV_frame = YUVread(test_file,[width height],i-1);
            this_YUV_frame = YUVread(test_file,[width height],i);
            next_YUV_frame = YUVread(test_file,[width height],i+1);
            if ratio < 1
                this_YUV_frame = imresize(this_YUV_frame, ratio);
                next_YUV_frame = imresize(next_YUV_frame, ratio);
            end
        else
            prev_YUV_frame = YUVread(test_file,[width height],i-1);
            this_YUV_frame = YUVread(test_file,[width height],i);
            next_YUV_frame = YUVread(test_file,[width height],i+1);
            first_frame_loaded = 1;
            if ratio < 1
                prev_YUV_frame = imresize(prev_YUV_frame, ratio);
                this_YUV_frame = imresize(this_YUV_frame, ratio);
                next_YUV_frame = imresize(next_YUV_frame, ratio);
            end
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % Compute BRISQUE features # 5(std), 9, 23, 29
        % features_all(1:4) #4
        brisque_feats = compute_brisque_features(this_YUV_frame(:,:,1));

        % compute GM-LOG features # 2(std), 4(std), 8, 18(std), 20, 23, 34(std), 40(both)
        % features_all(5:12) #8
        gmlog_feats = compute_gmlog_features(this_YUV_frame(:,:,1));

        % compute HIGRADE features # 3, 13, 14, 16, 21(both), 28, 30, 33
        % features_all(13:20) #8
        higrade_feats = compute_higrade_features(this_YUV_frame(:,:,1));

        % compute FRIQUEE_lUMA features # 5(std), 8(std), 27(std), 32(std), 58, 62(std), 66, 68(std), 70(both), 71, 72(std)
        % features_all(21:31) #11
        friquee_luma_feats = compute_friquee_luma_features(this_YUV_frame(:,:,1));

        % compute FRIQUEE_CHRO features # 4  7(std) 15   24   26   47   60   62   72   73   79
        % features_all(32:42) #11
        friquee_chroma_feats = compute_friquee_chroma_features(this_YUV_frame(:,:,:));

        % compute FRIQUEE LMS features # 52
        % features_all(43:43) # 1
        friquee_lms_feats = compute_friquee_lms_features(this_YUV_frame(:,:,:));

        % compute TLVQM LCF features # 1 5(both) 12 16 18 19(std) 22(std)
        % features_all(44:50) # 7
        tlvqm_lcf_feats = compute_tlvqm_lcf_features(this_YUV_frame./255, ...
                                                     prev_YUV_frame./255, ...
                                                     next_YUV_frame./255);
        % concat features
        frame_features_all = [frame_features_all; [brisque_feats, gmlog_feats, higrade_feats, ...
            friquee_luma_feats, friquee_chroma_feats, friquee_lms_feats, tlvqm_lcf_feats]];
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Compute mean and std pooling
    %
    std_feats = [];
    avg_feats = [];
    std_pool_idx = [1, 5, 6, 8, 11, 12, 17, 21, 22, 23, 24, 26, 28, 29, 31, ...
        33, 45, 49, 50];
    avg_pool_idx = [2:4, 7, 9, 10, 12, 13:20, 25, 27, 29, 30, 32, 34:48];
    n_temp_vecs = length(frame_features_all(:,1));
%     half_framerate = floor(framerate/2);

    fprintf('Mean and standard deviation pooling of frame-wise features...\n');
    for j = 1:frs_per_blk:n_temp_vecs - frs_per_blk
        j_start = j;
        j_end = min(n_temp_vecs, j + frs_per_blk);
        % mean and std pooling
        std_feats = [std_feats; nanstd(frame_features_all(j_start:j_end, std_pool_idx))];
        avg_feats = [avg_feats; nanmean(frame_features_all(j_start:j_end, avg_pool_idx))];
    end

    % compute TLVQM HCF features # 1 2 17 22 24 30
    tlvqm_hcf_feats = [];
    fprintf('Computing TLVQM-HCF features at 1fps...\n');
    for fr=floor(framerate/2):framerate:frame_end
        YUV_frame = YUVread(test_file,[width height],fr-1);
        if ratio < 1
            YUV_frame = imresize(YUV_frame, ratio);
        end
        ftrs = compute_tlvqm_hcf_features(YUV_frame./255);
        tlvqm_hcf_feats = [tlvqm_hcf_feats; ftrs];
    end

    % Combine feature vectors
    VIDEVAL_all_features = [nanmean(avg_feats, 1)   ...
                    nanmean(std_feats, 1) ...
                    nanmean(tlvqm_hcf_feats, 1)];

    fclose(test_file);
    fprintf('Extracting VIDEVAL features for %s finished!\n', test_video);
end

%% compute_brisque_features
function feat = compute_brisque_features(imgray)
% Compute BRISQUE features # 5(std), 9, 23, 29
    window = fspecial('gaussian',7,7/6);
    window = window/sum(sum(window));
    feat = [];
    %% scale1 5, 9
    mu = filter2(window, imgray, 'same');
    mu_sq = mu.*mu;
    sigma = sqrt(abs(filter2(window, imgray.*imgray, 'same') - mu_sq));
    imstruct = (imgray-mu)./(sigma+1);
    shifts = [0 1; 1 0];
    for itr_shift =1:2
        shifted_imstruct = circshift(imstruct,shifts(itr_shift,:));
        pair = imstruct(:).*shifted_imstruct(:);
        [~, leftstd, ~] = estimateaggdparam(pair);
        feat =[feat leftstd^2];
    end

    %% scale2 5, 11
    imgray = imresize(imgray,0.5);
    mu = filter2(window, imgray, 'same');
    mu_sq = mu.*mu;
    sigma = sqrt(abs(filter2(window, imgray.*imgray, 'same') - mu_sq));
    imstruct = (imgray-mu)./(sigma+1);
    shifts = [0 1; 1 1];
    for itr_shift =1:2
        shifted_imstruct = circshift(imstruct,shifts(itr_shift,:));
        pair = imstruct(:).*shifted_imstruct(:);
        [alpha, leftstd, ~] = estimateaggdparam(pair);
        if itr_shift == 1
            feat = [feat leftstd^2];
        else
            feat = [feat alpha];
        end
    end
end

%% compute_gmlog_features
function feat = compute_gmlog_features(imgray)
% compute GM-LOG features # 2(std), 4(std), 8, 18(std), 20, 23, 34(std), 40(both)
    sigma = 0.5;
    [gx,gy] = gaussian_derivative(imgray,sigma);
    grad_im = sqrt(gx.^2+gy.^2);

    window2 = fspecial('log', 2*ceil(3*sigma)+1, sigma);
    window2 =  window2/sum(abs(window2(:)));
    log_im = abs(filter2(window2, imgray, 'same'));

    ratio = 2.5; % default value 2.5 is the average ratio of GM to LOG on LIVE database
    grad_im = abs(grad_im/ratio);

    %Normalization
    c0 = 4*0.05;
    sigmaN = 2*sigma;
    window1 = fspecial('gaussian',2*ceil(3*sigmaN)+1, sigmaN);
    window1 = window1/sum(window1(:));
    Nmap = sqrt(filter2(window1,mean(cat(3,grad_im,log_im).^2,3),'same'))+c0;
    grad_im = (grad_im)./Nmap;
    log_im = (log_im)./Nmap;
    % remove the borders, which may be the wrong results of a convolution
    % operation
    h = ceil(3*sigmaN);
    grad_im = abs(grad_im(h:end-h+1,h:end-h+1,:));
    log_im = abs(log_im(h:end-h+1,h:end-h+1));

    ctrs{1} = 1:10;ctrs{2} = 1:10;
    % histogram computation
    step1 = 0.20;
    step2 = 0.20;
    grad_qun = ceil(grad_im/step1);
    log_im_qun = ceil(log_im/step2);

    N1 = hist3([grad_qun(:),log_im_qun(:)],ctrs);
    N1 = N1/sum(N1(:));
    NG = sum(N1,2); NL = sum(N1,1);

    alpha1 = 0.0001;
    % condition probability: Grad conditioned on LOG
    cp_GL = N1./(repmat(NL,size(N1,1),1)+alpha1);
    cp_GL_H=  sum(cp_GL,2)';
    cp_GL_H = cp_GL_H/sum(cp_GL_H);
    % condition probability: LOG conditioned on Grad
    cp_LG = N1./(repmat(NG,1,size(N1,2))+alpha1);
    cp_LG_H = sum(cp_LG,1);
    cp_LG_H = cp_LG_H/(sum(cp_LG_H));

    out = [NG', NL, cp_GL_H,cp_LG_H];

    feat = out([2, 4, 8, 18, 20, 23, 34, 40]);
end

function [gx,gy] = gaussian_derivative(imd,sigma)
    window1 = fspecial('gaussian',2*ceil(3*sigma)+1+2, sigma);
    winx = window1(2:end-1,2:end-1)-window1(2:end-1,3:end);winx = winx/sum(abs(winx(:)));
    winy = window1(2:end-1,2:end-1)-window1(3:end,2:end-1);winy = winy/sum(abs(winy(:)));
    gx = filter2(winx,imd,'same');
    gy = filter2(winy,imd,'same');
end

%% compute_higrade_features
function feat = compute_higrade_features(imgray)
% compute HIGRADE features # 3, 13, 14, 16, 21(both), 28, 30, 33
[Gmag, ~] = imgradient(imgray);
window = fspecial('gaussian',7,7/6);
window = window/sum(sum(window));
scale_num = 2;
feat = [];
for scale = 1:scale_num
    if scale == 1
        im = Gmag;
%         ggdpara = [];
        shifts = [0 1;1 0 ; 1 1; -1 1];

        mu = filter2(window, im, 'same');
        mu_sq = mu.*mu;
        sigma = sqrt(abs(filter2(window, im.*im, 'same') - mu_sq));
        structdis = (im-mu)./(sigma+1);
        [sigma_tmp, ~] = gaussian_para_esti(structdis(:));
        feat = [feat sigma_tmp]; %3
        structdis = log(abs(structdis) + .1);
        for itr_shift = 1:4
            structdis_shift_tmp = circshift(structdis, shifts(itr_shift,:));
            structdis_diff = structdis - structdis_shift_tmp;
            structdis_shift(itr_shift) = {[structdis_shift_tmp]};
        end
        structdis_diff = structdis + structdis_shift{1,3} - structdis_shift{1,1} - structdis_shift{1,2};
        [sigma_tmp_0, alpha_tmp_0] = gaussian_para_esti(structdis_diff(:));
        feat = [feat  sigma_tmp_0 alpha_tmp_0]; % 13 14
        win_tmp_1 = [0 1 0; -1 0 -1; 0 1 0;];
        structdis_diff_1 = filter2(win_tmp_1, structdis, 'same');
        [~, alpha_tmp_1] = gaussian_para_esti(structdis_diff_1(:));%16
        feat = [feat alpha_tmp_1];
    else
        im = Gmag;
        shifts = [0 1;1 0 ; 1 1; -1 1];

        mu = filter2(window, im, 'same');
        mu_sq = mu.*mu;
        sigma = sqrt(abs(filter2(window, im.*im, 'same') - mu_sq));
        structdis = (im-mu)./(sigma+1);
        [sigma_tmp, ~] = gaussian_para_esti(structdis(:));
        feat = [feat sigma_tmp]; %3

        for itr_shift = 1:4
            structdis_shift_tmp = circshift(structdis, shifts(itr_shift,:));
            if itr_shift == 3
                structdis_diff = structdis - structdis_shift_tmp;
                [~, alpha_tmp] = gaussian_para_esti(structdis_diff(:));
                feat = [feat alpha_tmp]; % 10
            elseif itr_shift == 4
                structdis_diff = structdis - structdis_shift_tmp;
                [~, alpha_tmp] = gaussian_para_esti(structdis_diff(:));
                feat = [feat alpha_tmp]; % 12
            end
            structdis_shift(itr_shift) = {[structdis_shift_tmp]};
        end
        win_tmp_1 = [0 1 0; -1 0 -1; 0 1 0;];
        structdis_diff_1 = filter2(win_tmp_1, structdis, 'same');
        [sigma_tmp_1, ~] = gaussian_para_esti(structdis_diff_1(:));%15
        feat = [feat sigma_tmp_1];
    end
    Gmag = imresize(Gmag, 0.5);
end
end

%% compute_friquee_luma_features
function feat = compute_friquee_luma_features(imgray)
% # 5(std), 8(std), 27(std), 32(std), 58, 62(std), 66, 68(std), 70(both), 71, 72(std)
% 1:31 32:62

    scalenum=2;
    imGray1=imgray;

    % The array that aggregates all the features from different feature
    % maps.
    feat = [];
    %% 1 scale 31 features x2 = 62
    %% scale1: 5, 8, 27
    [structdis,~] = divisiveNormalization(imgray);
    shifts = [ 0 1;1 0];
    for itr_shift =1:2
        % Construct the product neighborhood map.
        shifted_structdis = circshift(structdis,shifts(itr_shift,:));
        pair = structdis(:).*shifted_structdis(:); % Element wise product.
        if itr_shift == 1
            feat = [feat skewness(pair(:))]; %5
        else
                    % Fit an AGGD and extract its parameters.
            [alpha, leftstd, rightstd] = estimateaggdparam(pair);
            const = (sqrt(gamma(1/alpha))/sqrt(gamma(3/alpha)));
            meanparam = (rightstd-leftstd)*(gamma(2/alpha)/gamma(1/alpha))*const;
            feat = [feat meanparam]; %8
        end
    end
    %Extract the sigma field from the image.
    sigmaMap = computeSigmaMap(imgray);

    % Compute kurtosis, skewness and average of the sigma field
    feat = [feat mean2(sigmaMap)];%27

    %% sclae2: 32,58,62 - 31 = 1, 27,31
    imgray = imresize(imgray,0.5);
    shifts = [ 0 1];
    for itr_shift =1:1
        % Construct the product neighborhood map.
        shifted_structdis = circshift(structdis,shifts(itr_shift,:));
        pair = structdis(:).*shifted_structdis(:); % Element wise product.

        % Fit an AGGD and extract its parameters.
        [alpha, ~, ~] = estimateaggdparam(pair);
%         const = (sqrt(gamma(1/alpha))/sqrt(gamma(3/alpha)));
%         meanparam = (rightstd-leftstd)*(gamma(2/alpha)/gamma(1/alpha))*const;
        feat = [feat alpha];%% 1
    end
     %Extract the sigma field from the image.
    sigmaMap = computeSigmaMap(imgray);

    % Compute kurtosis, skewness and average of the sigma field
    feat = [feat mean2(sigmaMap)];%27

      % Apply DNT
    [imDivNorm,~] = divisiveNormalization(imgray);
    feat = [feat skewness(imDivNorm(:))]; %31

    %%
    imgray=imGray1;
    %% Original Scales

    % 63:64
    % Features from the Yellow color channel map.
%     yFeat=yellowColorChannelMap(rgb);
%     feat = [feat yFeat];

    % 65:70 --> 63:68
    % Features from the Difference of Gaussian (DoG) of Sigma Map

    %Get the sigma map of the image.
    sigmaMap = computeSigmaMap(imgray);

    % Construct two Gaussian windows where sig2 = 1.5*sig1;
    k=1.5;

    window1 = fspecial('gaussian',7,7/6);
    window1 = window1/sum(sum(window1));

    window2 = fspecial('gaussian',7,7*k/6);
    window2 = window2/sum(sum(window2));

    %Compute DOG of the sigmaMap
    DoGSigma= filter2((window1-window2), sigmaMap, 'same');

    %Compute the sigmaMap of DoGSigma
    DoGSigma1 = computeSigmaMap(DoGSigma);

    %DNT of DOG of sigma.
    divNormDoGSigma = divisiveNormalization(DoGSigma);

    %shape, variance, skewness.
    [~,lsigma] = estimateggdparam(divNormDoGSigma(:));

    % Apply DNT on DoGSigma1
    divNormDoGSigma1 = divisiveNormalization(DoGSigma1);

    feat = [feat lsigma kurtosis(divNormDoGSigma(:)) kurtosis(divNormDoGSigma1(:))];

    % 71:74 --> 69:72
    % Laplacian of the Luminance Map
    addpath(genpath(fullfile('include', 'matlabPyrTools')));

    % Extract the first laplacian of the image.
    [pyr pind] = buildLpyr(imgray,4);
    res =  pyrBand(pyr, pind, 1);

    % Fit a GGD to the laplacian image.
    [alpha, sigma] = estimateggdparam(res(:));

    feat = [feat alpha sigma];
%     feat = feat([5,8,27,32,58,62,64,66,68,69,70]);
end

%% compute_friquee_chroma_features
function feat = compute_friquee_chroma_features(imyuv)
%% # 4  7(std) 15   24   26   47   60   62   72   73   79
    rgb = ycbcr2rgb(uint8(imyuv));
%     lab=rgb2lab(rgb);
    colorTransform = makecform('srgb2lab');
    lab = applycform(rgb, colorTransform);

    % Get the A and B components of the LAB color space.
    A = double(lab(:,:,2));
    B = double(lab(:,:,3));

    % Compute the chroma map.
    imChroma = sqrt(A.*A + B.*B);

    % Initializations
    scalenum=2;
    imChroma1=imChroma;

    % The array that aggregates all the features from different feature
    % maps.
    feat = [];

    %% scale1: 4 7 15 24
    % Applying divisive normalization operation on the given gray scale image.
    [structdis,~] = divisiveNormalization(imChroma);

    % The four neighborhood maps that would be constructed [1]
    shifts = [ 0 1;1 0 ; 1 1; -1 1];
    for itr_shift =1:4
        % Construct the product neighborhood map.
        shifted_structdis = circshift(structdis,shifts(itr_shift,:));
        pair = structdis(:).*shifted_structdis(:); % Element wise product.

        if itr_shift ==1
        % Fit an AGGD and extract its parameters.
        [~, ~, rightstd] = estimateaggdparam(pair);
        feat = [feat rightstd^2];%4
        elseif itr_shift ==2
        % Fit an AGGD and extract its parameters.
        [alpha, ~, ~] = estimateaggdparam(pair);
        feat = [feat alpha];%7
        elseif itr_shift ==3
        [~, leftstd, ~] = estimateaggdparam(pair);
        feat = [feat  leftstd^2];%15
        elseif itr_shift ==4
          feat = [feat kurtosis(pair(:))];%24
        end
    end
    sigmaMap = computeSigmaMap(imChroma);
    feat = [feat skewness(sigmaMap(:))];%26
    %% scale2:   47   60   62 - 35 = 12    25    27
    imChroma = imresize(imChroma,0.5);

    % Applying divisive normalization operation on the given gray scale image.
    [structdis,~] = divisiveNormalization(imChroma);

    shifts = [1 0];

    % Construct the product neighborhood map.
    shifted_structdis = circshift(structdis,shifts);
    pair = structdis(:).*shifted_structdis(:); % Element wise product.

    feat = [feat kurtosis(pair(:))]; %12

    sigmaMap = computeSigmaMap(imChroma);
    feat = [feat kurtosis(sigmaMap(:)) mean2(sigmaMap)]; %25 27
    % Some features are computedcccccccc at multiple scales %% 1 scale 35 features x2 =
    % 70

    imChroma=imChroma1;

    %Get the sigma map of the image.
    sigmaMap = computeSigmaMap(imChroma);
        % Construct two Gaussian windows where sig2 = 1.5*sig1;
    k=1.5;

    window1 = fspecial('gaussian',7,7/6);
    window1 = window1/sum(sum(window1));

    window2 = fspecial('gaussian',7,7*k/6);
    window2 = window2/sum(sum(window2));
     %Compute DOG of the sigmaMap
    DoGSigma= filter2((window1-window2), sigmaMap, 'same');
    %DNT of DOG of sigma.
    divNormDoGSigma = divisiveNormalization(DoGSigma);
    %shape, variance, skewness.
    [~,lsigma] = estimateggdparam(divNormDoGSigma(:));
    feat = [feat lsigma skewness(divNormDoGSigma(:))];% 72 73

    % Laplacian of the Chroma Map
    addpath(genpath(fullfile('include', 'matlabPyrTools')));

    [pyr pind] = buildLpyr(imChroma,4);
    res =  pyrBand(pyr, pind, 1);
    feat = [feat skewness(res(:))];%79
end

%% compute_friquee_lms_features
function feat = compute_friquee_lms_features(imyuv)
    % compute FRIQUEE LMS features # 52
    rgb = ycbcr2rgb(uint8(imyuv));
    lms = (colorspace('RGB->CAT02 LMS',rgb));
     % Get the M and S components from the LMS color space.
%     LM = double(lms(:,:,2));
    LS = double(lms(:,:,3));
    %FRIQUEE features from the LMS color space.
%     fM = friqueeMS(LM);%1:32
%     fS = friqueeMS(LS);%33:64
    LS = imresize(LS,0.5);
    sigmaMap = computeSigmaMap(LS);
    dnSigmaFeat = debiasedNormalizedFeats(sigmaMap);%8:11 19:20:21
%     featOpp = lmsColorOpponentFeats(lms); % Color opponency features.
    feat = dnSigmaFeat(2);
end

%% compute_tlvqm_lcf_features
function features = compute_tlvqm_lcf_features(this_fr, prev_fr, next_fr)
 % compute TLVQM LCF features # 1 5(both) 12 16 18 19(std) 22(std)
    [height,width,~] = size(this_fr);
    % Initialize parameters
    bl_size = floor(width/40);
    src_win = floor(width/40);

    % The following computations are done with reduced resolution
    this_fr = imresize(this_fr,0.5);
    prev_fr = imresize(prev_fr,0.5);
    next_fr = imresize(next_fr,0.5);
    [height,width,~] = size(this_fr);
    this_Y = this_fr(:,:,1);
    prev_Y = prev_fr(:,:,1);
    next_Y = next_fr(:,:,1);
    this_fr = ycbcr2rgb(this_fr);

    % Apply Sobel filter to the frames
    H = [-1 -2 -1; 0 0 0; 1 2 1]./8;
    sob_h_this = imfilter(this_Y,H');
    sob_v_this = imfilter(this_Y,H);

    % Reset edge pixels in the Sobeled frames
    sob_h_this(1:4,1:width)=0;
    sob_h_this(height-3:height,1:width)=0;
    sob_h_this(1:height,1:4)=0;
    sob_h_this(1:height,width-3:width)=0;
    sob_v_this(1:4,1:width)=0;
    sob_v_this(height-3:height,1:width)=0;
    sob_v_this(1:height,1:4)=0;
    sob_v_this(1:height,width-3:width)=0;

    sob_tot = sqrt(sob_v_this.^2+sob_h_this.^2);
    sob_h_prev = imfilter(prev_Y,H');
    sob_v_prev = imfilter(prev_Y,H);
    sob_h_next = imfilter(next_Y,H');
    sob_v_next = imfilter(next_Y,H);

    H1 = [1 1 1 1 1;1 1 1 1 1;-2 -2 0 1 1;-2 -2 -2 1 1;-2 -2 -2 1 1]./32;
    H2 = [-2 -2 -2 1 1;-2 -2 -2 1 1;-2 -2 0 1 1;1 1 1 1 1;1 1 1 1 1]./32;
    H3 = [1 1 -2 -2 -2;1 1 -2 -2 -2;1 1 0 -2 -2;1 1 1 1 1;1 1 1 1 1]./32;
    H4 = [1 1 1 1 1;1 1 1 1 1;1 1 0 -2 -2;1 1 -2 -2 -2;1 1 -2 -2 -2]./32;

    corner_avg(:,:,1) = abs(imfilter(this_Y, H1));
    corner_avg(:,:,2) = abs(imfilter(this_Y, H2));
    corner_avg(:,:,3) = abs(imfilter(this_Y, H3));
    corner_avg(:,:,4) = abs(imfilter(this_Y, H4));
    corner_max = max(corner_avg,[],3);
    corner_this = corner_max-min(corner_avg,[],3);

    mot_threshold = 0.01;

%     cor_max = sort(corner_max(:),'ascend');
%     glob_blockiness = 0;
%     if std2(cor_max(1:floor(0.99*end)))>0
%         glob_blockiness = 0.5*((mean(cor_max(1:floor(0.99*end)))/ ...
%                           std2(cor_max(1:floor(0.99*end))))^2);
%     end

    % Reset edge pixels in the corner point filtered frame
    corner_this(1:src_win+3,1:width)=0;
    corner_this(height-src_win-2:height,1:width)=0;
    corner_this(1:height,1:src_win+3)=0;
    corner_this(1:height,width-src_win-2:width)=0;

    corner_this_copy = corner_this(:);
    key_pix = zeros((height-6)*(width-6),2);
    n_key_pix = 0;

    im_y_vec = mod(0:width*height, height)+1;
    im_x_vec = floor((0:width*height-1)/height)+1;
    sob_this_cp = corner_this_copy(corner_this_copy>mot_threshold);
    im_y_vec = im_y_vec(corner_this_copy>mot_threshold);
    im_x_vec = im_x_vec(corner_this_copy>mot_threshold);

    % In the following loop, find the key pixels
    [mx,idx] = max(sob_this_cp);
    if ~isempty(idx)
        while mx>mot_threshold
            i = im_y_vec(idx(1));
            j = im_x_vec(idx(1));

            n_key_pix = n_key_pix + 1;
            key_pix(n_key_pix,:) = [i j];

            idx_remove = find(im_y_vec>=i-floor(bl_size) & ...
                              im_y_vec<=i+floor(bl_size) & ...
                              im_x_vec>=j-floor(bl_size) & ...
                              im_x_vec<=j+floor(bl_size));
            sob_this_cp(idx_remove)=[];
            im_y_vec(idx_remove)=[];
            im_x_vec(idx_remove)=[];

            [mx,idx] = max(sob_this_cp);
        end
    end
    key_pix=key_pix(1:n_key_pix,:);

    non_mot_area = ones(height, width);

    num_mot_points = 0;
    max_mot_points = (height/bl_size)*(width/bl_size);

    %tic
    % In the following loop, find the motion vectors for each key pixel
    motion_vec = [];

    distance_matrix = ones(2*src_win+1);
    for i=1:2*src_win+1
        for j=1:2*src_win+1
            distance_matrix(i,j) = ...
                sqrt((1+src_win-i).^2+(1+src_win-j).^2)/sqrt(2*src_win^2);
        end
    end
    distances = distance_matrix(:);

    uncertain = 0;

    % Loop through the key pixels
    for z = 1:n_key_pix

        tar_y = key_pix(z,1);
        tar_x = key_pix(z,2);
        match_y_bw = tar_y;
        match_x_bw = tar_x;
        match_y_fw = tar_y;
        match_x_fw = tar_x;

        surr_win_v_prev = sob_v_prev(tar_y-src_win-2:tar_y+src_win+2, ...
                                     tar_x-src_win-2:tar_x+src_win+2);
        surr_win_h_prev = sob_h_prev(tar_y-src_win-2:tar_y+src_win+2, ...
                                     tar_x-src_win-2:tar_x+src_win+2);
        diff_win_prev = (sob_v_this(tar_y, tar_x)-surr_win_v_prev).^2 + ...
                        (sob_h_this(tar_y, tar_x)-surr_win_h_prev).^2;

        surr_win_v_next = sob_v_next(tar_y-src_win-2:tar_y+src_win+2, ...
                                     tar_x-src_win-2:tar_x+src_win+2);
        surr_win_h_next = sob_h_next(tar_y-src_win-2:tar_y+src_win+2, ...
                                     tar_x-src_win-2:tar_x+src_win+2);
        diff_win_next = (sob_v_this(tar_y, tar_x)-surr_win_v_next).^2 + ...
                        (sob_h_this(tar_y, tar_x)-surr_win_h_next).^2;

        for i=-1:1
            for j=-1:1
                if i~=0 || j~=0
                    diff_win_prev(3:end-2,3:end-2) = ...
                        diff_win_prev(3:end-2,3:end-2) + ...
                        (sob_v_this(tar_y+i, tar_x+j)- ...
                          surr_win_v_prev(3+i:end-2+i,3+j:end-2+j)).^2+ ...
                        (sob_h_this(tar_y+i, tar_x+j)- ...
                          surr_win_h_prev(3+i:end-2+i,3+j:end-2+j)).^2;
                    diff_win_next(3:end-2,3:end-2) = ...
                        diff_win_next(3:end-2,3:end-2) + ...
                        (sob_v_this(tar_y+i, tar_x+j)- ...
                          surr_win_v_next(3+i:end-2+i,3+j:end-2+j)).^2+...
                        (sob_h_this(tar_y+i, tar_x+j)- ...
                        surr_win_h_next(3+i:end-2+i,3+j:end-2+j)).^2;
                end
            end
        end
        diff_win_prev = diff_win_prev(3:end-2,3:end-2);
        diff_win_next = diff_win_next(3:end-2,3:end-2);

        orig_diff_bw = diff_win_prev(1+src_win,1+src_win);
        orig_diff_fw = diff_win_next(1+src_win,1+src_win);

        diff_bw = diff_win_prev(1+src_win,1+src_win);
        if orig_diff_bw>0.005
            [sorted,idx] = sort(diff_win_prev(:),'ascend');
            min_diff = orig_diff_bw;
            if length(sorted)>=2
                if sorted(1)<=0.8*sorted(2) || ...
                   distances(idx(1))<distances(idx(2))
                    min_diff = sorted(1);
                else
                    [idx,~] = find(0.8.*diff_win_prev(:)<=sorted(1));
                    [~,idx2] = sort(distances(idx),'ascend');
                    if diff_win_next(idx(idx2(1)))<1.1*sorted(1)
                        min_diff = diff_win_prev(idx(idx2(1)));
                    elseif sorted(1)<diff_bw*0.9
                        min_diff = sorted(1);
                    end
                    uncertain = uncertain + 1;
                end
                if min_diff*1.01<orig_diff_bw
                    [y,x] = find(diff_win_prev==min_diff);
                    match_y_bw = tar_y+y(1)-src_win-1;
                    match_x_bw = tar_x+x(1)-src_win-1;
                    diff_bw = diff_win_prev(y(1),x(1));
                end
            end
        end

        diff_fw = diff_win_next(1+src_win,1+src_win);
        if orig_diff_fw>0.005
            [sorted,idx] = sort(diff_win_next(:),'ascend');
            min_diff = orig_diff_fw;
            if length(sorted)>=2
                if sorted(1)<0.8*sorted(2) || ...
                   distances(idx(1))<distances(idx(2))
                    min_diff = sorted(1);
                else
                    [idx,~] = find(0.8.*diff_win_next(:)<=sorted(1));
                    [~,idx2] = sort(distances(idx),'ascend');
                    if diff_win_next(idx(idx2(1)))<1.1*sorted(1)
                        min_diff = diff_win_next(idx(idx2(1)));
                    elseif sorted(1)<diff_fw*0.9
                        min_diff = sorted(1);
                    end
                    uncertain = uncertain + 1;
                end
                if min_diff*1.01<orig_diff_fw
                    [y,x] = find(diff_win_next==min_diff);
                    match_y_fw = tar_y+y(1)-src_win-1;
                    match_x_fw = tar_x+x(1)-src_win-1;
                    diff_fw = diff_win_next(y(1),x(1));
                end
            end
        end

        % Add motion vector to the list of motion vectors
        if (orig_diff_bw > diff_bw*1.01 && ...
                (tar_y ~= match_y_bw || tar_x ~= match_x_bw)) || ...
           (orig_diff_fw > diff_fw*1.01 && ...
                (tar_y ~= match_y_fw || tar_x ~= match_x_fw))

            non_mot_area(max(1,tar_y-bl_size):min(height,tar_y+bl_size),...
                max(1,tar_x-bl_size):min(width,tar_x+bl_size))=0;
            non_mot_area(max(1,match_y_bw-bl_size): ...
                min(height,match_y_bw+bl_size),...
                max(1,match_x_bw-bl_size):...
                min(width,match_x_bw+bl_size)) = 0;
            non_mot_area(max(1,match_y_fw-bl_size):...
                min(height,match_y_fw+bl_size),...
                max(1,match_x_fw-bl_size):...
                min(width,match_x_fw+bl_size)) = 0;
        end

        num_mot_points = num_mot_points + 1;
        motion_vec = [motion_vec; ...
                      tar_y-match_y_bw tar_x-match_x_bw ...
                      match_y_fw-tar_y match_x_fw-tar_x ...
                      tar_y tar_x ...
                      orig_diff_bw diff_bw ...
                      orig_diff_fw diff_fw];
    end
    %toc
 % Compute motion point related statistics
%     motion_uncertainty = 0.5*uncertain/max_mot_points;
%     motion_density = 0;
    motion_intensity = 0;
%     std_mot_intensity = 0;
%     avg_mot_pos = 0;
%     avg_mot_sprd = 0;
%     mot_pred_acc = 0;
    mot_y = 0.5;
%     mot_x = 0.5;
    jerkiness = 0;
%     jerk_cons = 0;
    motion_vec_bg = [];
    num_bg_mot_points = 0;
    if num_mot_points>0
%         motion_density = num_mot_points/(width*height/bl_size^2);
        mot_intensity_vec = sqrt(((motion_vec(:,1)./src_win).^2 + ...
                                  (motion_vec(:,2)./src_win).^2 + ...
                                  (motion_vec(:,3)./src_win).^2 + ...
                                  (motion_vec(:,4)./src_win).^2)./4.0);
        sum_mot_int = sum(mot_intensity_vec);
        motion_intensity = (sum(mot_intensity_vec)/max_mot_points)^0.25;
%         std_mot_intensity = std(mot_intensity_vec);

        if sum_mot_int>0
            % Compute motion position in relation with the screen midpoint
%             avg_motp_y = sum(mot_intensity_vec.*motion_vec(:,5))/...
%                            sum_mot_int;
%             std_motp_y = sqrt(sum(mot_intensity_vec.*...
%                            (motion_vec(:,5)-avg_motp_y).^2)/sum_mot_int);
%             avg_mot_pos_y = (avg_motp_y-height/2)/(height/2);
%             sprd_mot_pos_y = std_motp_y/height;
%             avg_motp_x = sum(mot_intensity_vec.*motion_vec(:,6))/...
%                            sum_mot_int;
%             std_motp_x = sqrt(sum(mot_intensity_vec.*...
%                            (motion_vec(:,6)-avg_motp_x).^2)/sum_mot_int);
%             avg_mot_pos_x = (avg_motp_x-width/2)/(width/2);
%             sprd_mot_pos_x = std_motp_x/width;
%
%             avg_mot_pos = sqrt(avg_mot_pos_y^2+avg_mot_pos_x^2);
%             avg_mot_sprd = sqrt(sprd_mot_pos_y^2+sprd_mot_pos_x^2);

            % Mean motion along x and y axis
            mot_y = mean(0.25.*(motion_vec(:,1)+motion_vec(:,3))./ ...
                      src_win+0.5);
%             mot_x = mean(0.25.*(motion_vec(:,2)+motion_vec(:,4))./ ...
%                       src_win+0.5);

            % Average motion prediction improvement
%             mot_pred_acc_bw = mean(motion_vec(:,7)-motion_vec(:,8));
%             mot_pred_acc_fw = mean(motion_vec(:,9)-motion_vec(:,10));
%             mot_pred_acc = 0.5*(mot_pred_acc_bw+mot_pred_acc_fw).^0.5;

            % Motion jerkiness
            mot_y_diff = 0.5.*(motion_vec(:,1)'-motion_vec(:,3)')./src_win;
            mot_x_diff = 0.5.*(motion_vec(:,2)'-motion_vec(:,4)')./src_win;
            mot_diff = sqrt(mot_y_diff.^2+mot_x_diff.^2);
            jerkiness = mean(mot_diff.^0.5);
%             jerk_cons = std(mot_diff.^0.5);
        end

        avg_mot_x = mean(0.5.*motion_vec(:,2)+0.5.*motion_vec(:,4));
        avg_mot_y = mean(0.5.*motion_vec(:,1)+0.5.*motion_vec(:,3));
        std_mot_x = std(0.5.*motion_vec(:,2)+0.5.*motion_vec(:,4));
        std_mot_y = std(0.5.*motion_vec(:,1)+0.5.*motion_vec(:,3));

        for z=1:num_mot_points
            mot_x_this = 0.5*motion_vec(z,2)+0.5*motion_vec(z,4);
            mot_y_this = 0.5*motion_vec(z,1)+0.5*motion_vec(z,3);
            if mot_x_this > avg_mot_x-std_mot_x && ...
               mot_x_this < avg_mot_x+std_mot_x && ...
               mot_y_this > avg_mot_y-std_mot_y && ...
               mot_y_this < avg_mot_y+std_mot_y

                num_bg_mot_points = num_bg_mot_points + 1;
                motion_vec_bg = [motion_vec_bg; motion_vec(z,:)];
            end
        end
    end
 % Compute motion point related statistics
%     egomotion_density = 0;
%     egomotion_intensity = 0;
    std_egomot_intensity = 0;
%     avg_egomot_pos = 0;
%     avg_egomot_sprd = 0;
%     egomot_pred_acc = 0;
%     mot_y_bg = 0.5;
%     mot_x_bg = 0.5;
    if num_bg_mot_points>0
%         egomotion_density = num_bg_mot_points/(width*height/bl_size^2);
        bg_mot_intensity_vec = sqrt(((motion_vec_bg(:,1)./src_win).^2 + ...
                                     (motion_vec_bg(:,2)./src_win).^2 + ...
                                     (motion_vec_bg(:,3)./src_win).^2 + ...
                                     (motion_vec_bg(:,4)./src_win).^2)  ...
                                      ./4.0);
%         sum_bg_mot_int = sum(bg_mot_intensity_vec);
%         egomotion_intensity = (sum(bg_mot_intensity_vec)/...
%                                 max_mot_points)^0.25;
        std_egomot_intensity = std(bg_mot_intensity_vec);

        % Compute motion position in relation with the screen midpoint
%         if sum_bg_mot_int>0
%             avg_motp_y = sum(bg_mot_intensity_vec.*motion_vec_bg(:,5))/...
%                            sum_bg_mot_int;
%             std_motp_y = sqrt(sum(bg_mot_intensity_vec.*...
%                            (motion_vec_bg(:,5)-avg_motp_y).^2)/...
%                               sum_bg_mot_int);
%             avg_mot_pos_y = (avg_motp_y-height/2)/(height/2);
%             sprd_mot_pos_y = std_motp_y/height;
%             avg_motp_x = sum(bg_mot_intensity_vec.*motion_vec_bg(:,6))/...
%                            sum_bg_mot_int;
%             std_motp_x = sqrt(sum(bg_mot_intensity_vec.*...
%                            (motion_vec_bg(:,6)-avg_motp_x).^2)/...
%                            sum_bg_mot_int);
%             avg_mot_pos_x = (avg_motp_x-width/2)/(width/2);
%             sprd_mot_pos_x = std_motp_x/width;
%
%             avg_egomot_pos = sqrt(avg_mot_pos_y^2+avg_mot_pos_x^2);
%             avg_egomot_sprd = sqrt(sprd_mot_pos_y^2+sprd_mot_pos_x^2);

            % Average egomotion prediction improvement
%             mot_pred_acc_bw = mean(motion_vec_bg(:,7)-motion_vec_bg(:,8));
%             mot_pred_acc_fw = mean(motion_vec_bg(:,9)-motion_vec_bg(:,10));
%             egomot_pred_acc = 0.5*(mot_pred_acc_bw+mot_pred_acc_fw).^0.5;

%             mot_y_bg = mean(0.25.*(motion_vec_bg(:,1)+...
%                                    motion_vec_bg(:,3))./src_win+0.5);
%             mot_x_bg = mean(0.25.*(motion_vec_bg(:,2)+...
%                                    motion_vec_bg(:,4))./src_win+0.5);
%         end
    end

%     mot_size = sum(sum(1-non_mot_area));
    non_mot_size = sum(sum(non_mot_area));
%  static_area_flicker = 0;
    static_area_flicker_std = 0;
    if non_mot_size>0
        % Sum of the pixel differences in the static area
        static_area_flicker_bw = sum(non_mot_area(:) .* ...
                                 abs(this_Y(:)-prev_Y(:)))/non_mot_size;
        static_area_flicker_fw = sum(non_mot_area(:) .* ...
                                 abs(this_Y(:)-next_Y(:)))/non_mot_size;
        static_area_flicker = 0.5*(static_area_flicker_bw + ...
                                   static_area_flicker_fw);
        % Variance of pixel differences in the static area
        st_diff_bw = abs(this_Y(:)-prev_Y(:));
        st_diff_fw = abs(this_Y(:)-next_Y(:));
        static_area_flicker_std = sum(non_mot_area(:)' .* ...
                                  abs(max([st_diff_bw'; st_diff_fw']) - ...
                                  static_area_flicker))/non_mot_size;
    end

    % Spatial activity in the static area
    si = std2(sob_tot).^0.25;

    %[blur glob_blockiness si]

    % Temporal activity standard deviation in the static area
    ti_prev = mean(abs(this_Y(:)-prev_Y(:)));
    ti_next = mean(abs(this_Y(:)-next_Y(:)));
    ti_mean = mean([ti_prev ti_next]).^0.25;

    % Normalize static area size
%     mot_size = mot_size / (width*height);
    features = [motion_intensity,  ...
                std_egomot_intensity, ...
                si, ...
                jerkiness, ...
                ti_mean, ...
                mot_y, ...
                static_area_flicker_std];
end

%% compute_tlvqm_hcf_features
function features = compute_tlvqm_hcf_features(imyuv)
% compute TLVQM HCF features # 1 2    17    22    24    30
% Initializations
    mono_image = imyuv(:,:,1);
    image = ycbcr2rgb(imyuv);
    lab_image = rgb2lab(image);
    [height,width,depth] = size(image);

    % Make Sobeled image
    mask = zeros(height,width);
    mask(2:end-1,2:end-1)=1;
    H = [1 2 1; 0 0 0; -1 -2 -1]./8;

    % Make Sobeled image in CIELAB color space
    sob_image_lab_x = (imfilter(lab_image(:,:,1)./100.0,H).^2 + ...
                       imfilter(lab_image(:,:,2)./50.0,H).^2 + ...
                       imfilter(lab_image(:,:,3)./50.0,H).^2).*mask;
    sob_image_lab_y = (imfilter(lab_image(:,:,1)./100.0,H').^2 + ...
                       imfilter(lab_image(:,:,2)./50.0,H').^2 + ...
                       imfilter(lab_image(:,:,3)./50.0,H').^2).*mask;
    sob_image = sqrt(sob_image_lab_x+sob_image_lab_y);

    % Compute fetures for different feature groups
%     [a,b,sat_image1] = compute_saturation(mono_image,1);
% %     sat_bright = [a];
%     [a,b,sat_image2] = compute_saturation(mono_image,0);
% %     sat_dark = [a b];
%     sat_image = max(sat_image1, sat_image2);
%     saturation_ftr = [sat_bright sat_dark]; % 5:8
%     saturation_ftr = sat_bright;
    spatial_ftr = spatial_activity_features(sob_image, []);% 1:4
% %     noisiness_ftr = noise_features(mono_image, sat_image, lab_image); % 9:11
%     blockiness_ftr = blockiness_features(sob_image_lab_x.^0.5, ...
%                                          sob_image_lab_y.^0.5); % 12:14
    contrast_color_ftr = contrast_chroma_features(lab_image, []); % 15:18
%     dct_ftr = dct_features(mono_image); % 19:21
    sharpness_ftr = sharpness_features(sob_image); % 22:30

    % Make the HC feature vector
    features = [spatial_ftr   ...
                contrast_color_ftr   ...
                sharpness_ftr];
end

% This function computes the saturation (bright or dark)
function [len,num,segs] = compute_saturation(image, is_bright)

    [height,width] = size(image);

    lens = [];
    num = 0;

    segs = zeros(height,width);

    if (is_bright==1 && max(max(image))>0.9) || ...
       (is_bright==0 && min(min(image))<0.1)

        segs = seg_loop(image,segs,3,3,0.05, is_bright);
        for i=1:max(max(segs))
            len = length(find(segs==i));
            if len<50
                segs(find(segs==i))=0;
            else
                lens = [lens len];
                num = num + 1;
            end
        end
        segs(find(segs>0))=1;
    end

    len = sum(lens)/(width*height);
    if num > 0
        num = len / num;
    end

end

% This function is used for segmentation by measure_saturation
function segim = seg_loop(image, segs, wh, ww, interval, is_bright)

    [height,width] = size(image);

    segim = segs;

    maxi = max(max(image));
    mini = min(min(image));

    for i=1:height-wh+1
        for j=1:width-ww+1
            if (is_bright == 1 && ...
              min(min(image(i:i+wh-1,j:j+ww-1)))>maxi-interval) || ...
              (is_bright == 0 && ...
              max(max(image(i:i+wh-1,j:j+ww-1)))<mini+interval)

                maxsg = max(max(segim(i:i+wh-1,j:j+ww-1)));
                if maxsg>0
                    segs_temp = reshape(segim(i:i+wh-1,j:j+ww-1),wh*ww,1);
                    minsg=min(segs_temp(find(segs_temp>0)));
                    segim(i:i+wh-1,j:j+ww-1)=minsg;
                    if minsg<maxsg
                        segim(find(segim==maxsg))=minsg;
                    end
                else
                    segim(i:i+wh-1,j:j+ww-1)=max(max(segim(:,:)))+1;
                end
            end
        end
    end

end

% This function is used to compute noise related features
function out = noise_features(mono_image, sat_im, lab_image)

    [height,width] = size(mono_image);

    new_im = zeros(height, width, 3);

    nonsat_pix = 0;
    noise_pix = 0;
    noise_int = [];

    % Loop through pixels to find noise pixels
    for i=5:height-4
        for j=5:width-4
            if sat_im(i,j)==0
                surr_pix = mono_image(i-2:i+2,j-2:j+2);
                surr_pix = surr_pix(:);
                surr_pix = [surr_pix(1:12); surr_pix(14:25)];
                if (mono_image(i,j)>max(surr_pix) || ...
                    mono_image(i,j)<min(surr_pix))
                    surr_pix = mono_image(i-4:i+4,j-4:j+4);
                    if std(surr_pix)<0.05
                        new_im(i,j,2) = 1;
                        pix_diff = sqrt( ...
                            (mean(lab_image(i-3:i+3,j-3:j+3,1))-...
                                 lab_image(i,j,1)).^2 + ...
                            (mean(lab_image(i-3:i+3,j-3:j+3,2))-...
                                 lab_image(i,j,2)).^2 + ...
                            (mean(lab_image(i-3:i+3,j-3:j+3,3))-...
                                  lab_image(i,j,3)).^2);
                        noise_int = [noise_int pix_diff/100];
                        noise_pix = noise_pix + 1;
                    end
                end
                nonsat_pix = nonsat_pix + 1;
            end
        end
    end

    a = 0;
    b = 0;
    c = 0;

    if nonsat_pix > 0 && noise_pix > 0
        % noise density
        a = noise_pix / nonsat_pix;
        b = mean(noise_int);
        c = std(noise_int);
    end

    out = [a b c];

end

% This function is used to compute spatial activity features
function out = spatial_activity_features(sobel_image, ~)

%     [height,width] = size(sobel_image);
%
%     sob_dists = zeros(1,height*width);
%     sob_dists2 = zeros(height*width,2);
%     sob_str = zeros(1,height*width);
%     sumstr = 0;
%
%     n = 0;
%     for i=1:height
%         for j=1:width
%             if sat_image(i,j)==0
%                 if sobel_image(i,j)<0.01
%                     sobel_image(i,j)=0;
%                 end
%                 sumstr = sumstr + sobel_image(i,j);
%                 if sobel_image(i,j) > 0
%                     n = n + 1;
%                     sob_str(n) = sobel_image(i,j);
%                     sob_dists(n) = sqrt((i/height-0.5)^2+(j/width-0.5)^2);
%                     sob_dists2(n,1) = i/height-0.5;
%                     sob_dists2(n,2) = j/width-0.5;
%                 end
%             end
%         end
%     end
%
%     sob_str = sob_str(1:n);
%     sob_dists = sob_dists(1:n);
%     sob_dists2 = sob_dists2(1:n,:);

%     a = 0;
%     b = 0;
%     c = 0;
%     d = 0;

%     if ~isempty(sob_str)>0
        a = mean(mean(sobel_image));
        b = std2(sobel_image);
%         d = w_std(sob_dists, sob_str);
%         mean_y = sum(sob_str'.*sob_dists2(:,1))/sum(sob_str);
%         mean_x = sum(sob_str'.*sob_dists2(:,2))/sum(sob_str);
%         c = sqrt(mean_y^2+mean_x^2);
%     end

%     out = [a b c d];
    out = [ a b];

end

% Function for "weighted standard deviation", used by function
% measure_spatial_activity
function res = w_std(input, weights)

    wg_n = sum(weights);
    wg_input = input.*weights;
    wg_mean = mean(input.*weights);

    res = sqrt(sum((wg_input-wg_mean).^2)/wg_n);
end

% This function is used to compute blockiness index
function blockiness = blockiness_features(sob_y, sob_x)

    [height,width] = size(sob_y);

    hor_tot = zeros(1,height-4);
    ver_tot = zeros(1,width-4);

    for i=3:height-2
        hor_tot(i)=mean(sob_y(i,:)-sob_x(i,:));
    end
    for j=3:width-2
        ver_tot(j)=mean(sob_x(:,j)-sob_y(:,j));
    end

    % compute autocorrelations
    autocr_hor = zeros(1,23);
    autocr_ver = zeros(1,23);
    for i=0:22
        autocr_hor(i+1) = sum(hor_tot(1:end-i).*hor_tot(1+i:end));
        autocr_ver(i+1) = sum(ver_tot(1:end-i).*ver_tot(1+i:end));
    end

    % Find the highest local maximum (other than 0)
    localpeaks = 0;
%     peakdist = 0;
    max_hor = 0;
    max_ver = 0;
    min_hor = autocr_hor(1);
    min_ver = autocr_ver(1);
    max_hor_diff = 0;
    max_ver_diff = 0;
    for i=2:22
        if autocr_hor(i)>max(autocr_hor(i-1),autocr_hor(i+1))
            localpeaks = localpeaks+1/42;
        end
        if autocr_hor(i)<min(autocr_hor(i-1),autocr_hor(i+1)) && ...
                autocr_hor(i)<min_hor
            min_hor = autocr_hor(i);
        elseif autocr_hor(i)>max(autocr_hor(i-1),autocr_hor(i+1)) && ...
                autocr_hor(i)-min_hor>max_hor_diff
            max_hor = autocr_hor(i);
            max_hor_diff = max_hor-min_hor;
%             peakdist = (i-1)/21;
        end
        if autocr_ver(i)>max(autocr_ver(i-1),autocr_ver(i+1))
            localpeaks = localpeaks + 1/42;
        end
        if autocr_ver(i)<min(autocr_ver(i-1),autocr_ver(i+1)) && ...
                autocr_ver(i)<min_ver
            min_ver = autocr_ver(i);
        elseif autocr_ver(i)>max(autocr_ver(i-1),autocr_ver(i+1)) && ...
                autocr_ver(i)-min_ver>max_ver_diff
            max_ver = autocr_ver(i);
            max_ver_diff = max_ver-min_ver;
%             peakdist = (i-1)/21;
        end
    end

    a = 0;
    if autocr_hor(1)>0 && autocr_ver(1)>0
        if max_hor>0 && max_ver>0
            a = max((max_hor_diff/autocr_hor(1)), ...
                             (max_ver_diff/autocr_ver(1)))^0.5;
        elseif max_hor>0
            a = (max_hor_diff/autocr_hor(1))^0.5;
        elseif max_ver>0
            a = (max_ver_diff/autocr_ver(1))^0.5;
        end
    end

%     b = peakdist;
%     c = localpeaks;
    blockiness = a;
end

% This function is used to compute contrast and chroma related features
function out = contrast_chroma_features(lab_image, ~)

%     a=0;
%     b=0;
    c=0;
%     d=0;

%     [height,width,depth] = size(lab_image);
%     yuv_int = floor(lab_image(:,:,1));

    %sat_image = sat_image(:);
%     yuv_int2 = yuv_int(sat_image(:)==0);
%     cumu_err = 0;
%     cumu_tar = 0;
%     if ~isempty(yuv_int2)
%         for i=0:100
%             cumu_tar = cumu_tar + 1/100;
%             cumu_err = cumu_err + (sum(yuv_int2<=i)/length(yuv_int2) - ...
%                                    cumu_tar)/100;
%         end
% %         a = (cumu_err+1.0)/2.0;
%         b = 0.5*(1-cumu_err);
%     else
% %         a = 1;
%         b = sum(sum(lab_image(:,:,1)))/50;
%     end
    c = sqrt(mean(mean((lab_image(:,:,2)./50).^2 + ...
         (lab_image(:,:,3)./50).^2)));
%     d = 0;
%     if std2(lab_image(:,:,1))>0
%         d = 0.01*(std2(lab_image(:,:,2))+std2(lab_image(:,:,3)));
%     end

    out = [c];
end

% This function is used to compute dct derived features
function out = dct_features(im)

    % Input is monochrome image
    [height,width] = size(im);

    out_im = abs(dct2(im)).^.5;

    area1 = imresize(out_im(1:floor(height/2),1:floor(width/2)),0.25);
    area2 = imresize(out_im(1:floor(height/2),...
                            width:-1:width-floor(width/2)+1),0.25);
    area3 = imresize(out_im(height:-1:height-floor(height/2)+1,...
                            1:floor(width/2)),0.25);
    area4 = imresize(out_im(height:-1:height-floor(height/2)+1,...
                            width:-1:width-floor(width/2)+1),0.25);
    a = max(0,max(corr(area1(:),area2(:)),corr(area1(:),area3(:))));
%     b = 0;
%     if mean(area1)>0
%         b = mean(area4)/mean(area1);
%     end
%     c = 0;
%     if max(mean(area2),mean(area3))>0
%         c = min(mean(area2),mean(area3))/max(mean(area2),mean(area3));
%     end

    out = [a];

end

% This function is used to compute sharpness related features
function out = sharpness_features(im)

    [~, width] = size(im);

    % Full HD video could be downsized
    if width>1280
        im = imresize(im,0.5);
    end

    H = [-1 -2 -1; 0 0 0; 1 2 1]./8;
    im_s_h = imfilter(im,H');
    im_s_v = imfilter(im,H);
    im_s = sqrt(im_s_h.^2+im_s_v.^2);

    [height,width] = size(im_s_h);
    bl_size = 16;
    conv_list = [];

    blur_im = zeros(height,width);
    edge_strong = [];
    edge_all = [];

    conv_cube = [];
    blurvals = [];

    n_blks = 0;

    conv_val_tot = zeros(17);
    for y=floor(bl_size/2):bl_size:height-ceil(3*bl_size/2)
        for x=floor(bl_size/2):bl_size:width-ceil(3*bl_size/2)

            n_blks = n_blks + 1;

            conv_val = zeros(17);
            for i=0:6
                for j=0:6
                    if i==0 || j==0 || i==j
                        weight_h = 1;
                        weight_v = 1;
                        if i~=0 || j~=0
                            weight_h = abs(i)/(abs(i)+abs(j));
                            weight_v = abs(j)/(abs(i)+abs(j));
                        end
                        diff_h = (im_s_h(y+i:y+bl_size+i,   ...
                                         x+j:x+bl_size+j).* ...
                                  im_s_h(y:y+bl_size,       ...
                                         x:x+bl_size));
                        diff_v = (im_s_v(y+i:y+bl_size+i,   ...
                                         x+j:x+bl_size+j).* ...
                                  im_s_v(y:y+bl_size,       ...
                                         x:x+bl_size));
                        conv_val(i+9,j+9) = weight_h*(mean(diff_h(:)))+ ...
                                            weight_v*(mean(diff_v(:)));
                    end
                end
            end
            blur_im(y:y+bl_size-1,x:x+bl_size-1)=0.5;
            edge_all =  [edge_all conv_val(9,9)];
            if conv_val(9,9)>0.0001
                edge_strong =  [edge_strong conv_val(9,9)];
                conv_val=conv_val./conv_val(9,9);
                conv_val_tot = conv_val_tot + conv_val;

                new_conv_v = [];
                for i=1:6
                    new_conv_v = [new_conv_v sum(sum(conv_val(9-i:9+i,...
                                                            9-i:9+i)))- ...
                                             sum(sum(conv_val(10-i:8+i, ...
                                                            10-i:8+i)))];
                end
                if new_conv_v(1)>0
                    new_conv_v=new_conv_v./new_conv_v(1);
                end

                conv_list = [conv_list; new_conv_v];
                conv_cube(:,:,1)=conv_val;
                blurvals = [blurvals std2(im_s(y:y+bl_size, x:x+bl_size))];

                blur_im(y:y+bl_size-1,x:x+bl_size-1) = ...
                                  0.5 + mean(new_conv_v(2:6))/5;
            end
        end
    end

    % Find the sharpest blocks
    blurs_sharp = [];
    blurs_blur = [];
    if ~isempty(blurvals)
        for i=1:length(blurvals)
            if blurvals(i)>mean(blurvals)
                conv_val_tot = + conv_val_tot + conv_cube(:,:,1);
                blurs_sharp = [blurs_sharp blurvals(i)];
            else
                blurs_blur = [blurs_blur blurvals(i)];
            end
        end
    end

    n_sharps = length(blurs_sharp)/n_blks;
    n_blurs = length(blurs_blur)/n_blks;
    mean_sharps = 0;
    mean_blurs = 0;
    if ~isempty(blurs_sharp)
        mean_sharps = mean(blurs_sharp);
    end
    if ~isempty(blurs_blur)
        mean_blurs = mean(blurs_blur);
    end

%     if conv_val_tot(9,9)>0
%         conv_val_tot=conv_val_tot./conv_val_tot(9,9);
%     end

    new_conv_v = zeros(1,9);
    if ~isempty(edge_strong)>0
        if length(conv_list(:,1))>1
            new_conv_v = mean(conv_list);
        else
            new_conv_v = conv_list;
        end
    end

    % find local min and/or local max
%     localmin=0;
%     localmindist=0;
%     localmax=0;
%     localmaxdist=0;
%     for i=9:14
%         for j=9:14
%             if (i~=9 && j==9) || (j~=9 && i==9) || (i==j && i>9)
%                 conv_val_comp=conv_val_tot(i-1:i+1,j-1:j+1);
%                 conv_val_comp=conv_val_comp(:);
%                 if i==9
%                     conv_val_comp=conv_val_comp([4 6]);
%                 elseif j==9
%                     conv_val_comp=conv_val_comp([2 8]);
%                 else
%                     conv_val_comp=conv_val_comp([1 9]);
%                 end
%                 if conv_val_tot(i,j)>max(conv_val_comp) && ...
%                         conv_val_tot(i,j)>localmax
%                     localmax = conv_val_tot(i,j);
% %                     localmaxdist = sqrt((i-9)^2+(j-9)^2);
%                 elseif conv_val_tot(i,j)<min(conv_val_comp) && ...
%                         conv_val_tot(i,j)<localmin
%                     localmin = conv_val_tot(i,j);
% %                     localmindist = sqrt((i-9)^2+(j-9)^2);
%                 end
%             end
%         end
%     end

%     out = [mean(new_conv_v(2:6)) mean(new_conv_v(2:4)) new_conv_v(2) ...
%            localmaxdist/5 localmindist/5 ...
%            n_sharps n_blurs mean_sharps mean_blurs];

    out = [mean(new_conv_v(2:6)) new_conv_v(2) mean_blurs];
end

% Read one frame from YUV file
function YUV = YUVread(f,dim,frnum)

    % This function reads a frame #frnum (0..n-1) from YUV file into an
    % 3D array with Y, U and V components

    fseek(f,dim(1)*dim(2)*1.5*frnum,'bof');

    % Read Y-component
    Y=fread(f,dim(1)*dim(2),'uchar');
    if length(Y)<dim(1)*dim(2)
        YUV = [];
        return;
    end
    Y=cast(reshape(Y,dim(1),dim(2)),'double');

    % Read U-component
    U=fread(f,dim(1)*dim(2)/4,'uchar');
    if length(U)<dim(1)*dim(2)/4
        YUV = [];
        return;
    end
    U=cast(reshape(U,dim(1)/2,dim(2)/2),'double');
    U=imresize(U,2.0);

    % Read V-component
    V=fread(f,dim(1)*dim(2)/4,'uchar');
    if length(V)<dim(1)*dim(2)/4
        YUV = [];
        return;
    end
    V=cast(reshape(V,dim(1)/2,dim(2)/2),'double');
    V=imresize(V,2.0);

    % Combine Y, U, and V
    YUV(:,:,1)=Y';
    YUV(:,:,2)=U';
    YUV(:,:,3)=V';
end


function out = Pauli(data, sicd_meta)
%PAULI Pauli Decomposition
%
% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////

if (nargin>1) && isfield(sicd_meta,'ImageFormation') && ...
        isfield(sicd_meta.ImageFormation,'TxRcvPolarizationProc')
    % Try standard linear polarizations first
    HH_ind=find(strcmpi('H:H',sicd_meta.ImageFormation.TxRcvPolarizationProc));
    HV_ind=find(strcmpi('H:V',sicd_meta.ImageFormation.TxRcvPolarizationProc));
    VH_ind=find(strcmpi('V:H',sicd_meta.ImageFormation.TxRcvPolarizationProc));
    VV_ind=find(strcmpi('V:V',sicd_meta.ImageFormation.TxRcvPolarizationProc));
    % Less common circular polarization next in not linear
    if isempty([HH_ind HV_ind VH_ind VV_ind])
        pols = cellfun(@(x) split(x,':'), sicd_meta.ImageFormation.TxRcvPolarizationProc, 'UniformOutput', false);
        HH_ind = find(cellfun(@(x) strcmpi(x{1},'LHC') && ...
            (strcmpi(x{2},'LHC') || x{2}=='H'), pols));
        HV_ind = find(cellfun(@(x) strcmpi(x{1},'LHC') && ...
            (strcmpi(x{2},'RHC') || x{2}=='V'), pols));
        VH_ind = find(cellfun(@(x) strcmpi(x{1},'RHC') && ...
            (strcmpi(x{2},'LHC') || x{2}=='V'), pols));
        VV_ind = find(cellfun(@(x) strcmpi(x{1},'RHC') && ...
            (strcmpi(x{2},'RHC') || x{2}=='H'), pols));
    end
else % Make band assumptions based on order
    switch size(data,3)
        case 2 % Co/cross
            HH_ind = 1; HV_ind = 2; VH_ind = []; VV_ind = [];
        case 3 % HH/cross/VV
            HH_ind = 1; HV_ind = 2; VH_ind = []; VV_ind = 3;
        case 4 % Full
            HH_ind = 1; HV_ind = 2; VH_ind = 3; VV_ind = 4;
    end
end

out = zeros(size(data,1),size(data,2),3);
% Co-pol,cross-pol
if xor(isscalar(HH_ind), isscalar(VV_ind)) && xor(isscalar(HV_ind), isscalar(VH_ind))
    %incoherent assignment (co is magenta, cross is green)
    out(:,:,1) = data(:,:,[HH_ind VV_ind]);
    out(:,:,2) = data(:,:,[HV_ind VH_ind]);
    out(:,:,3) = data(:,:,[HH_ind VV_ind]);
% HH/VV
elseif isscalar(HH_ind) && isscalar(VV_ind) && isempty(HV_ind) && isempty(VH_ind)
    out(:,:,1) = data(:,:,HH_ind) - data(:,:,VV_ind);
    out(:,:,2) = 0;
    out(:,:,3) = data(:,:,HH_ind) + data(:,:,VV_ind);
% HH,cross-pol,VV
elseif isscalar(HH_ind) && isscalar(VV_ind) && xor(isscalar(HV_ind), isscalar(VH_ind))
    out(:,:,1) = data(:,:,HH_ind) - data(:,:,VV_ind);
    out(:,:,2) = 2*data(:,:,[HV_ind VH_ind]);
    out(:,:,3) = data(:,:,HH_ind) + data(:,:,VV_ind);
% Full-pol (HH,HV,VH,VV)
elseif isscalar(HH_ind) && isscalar(VV_ind) && isscalar(HV_ind) && isscalar(VH_ind)
    out(:,:,1) = data(:,:,HH_ind) - data(:,:,VV_ind);
    out(:,:,2) = data(:,:,HV_ind) + data(:,:,VH_ind);
    out(:,:,3) = data(:,:,HH_ind) + data(:,:,VV_ind);
end

end

% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////


function [p, stats] = symamd2 (S, knobs)
%SYMAMD Symmetric approximate minimum degree permutation.
%    P = SYMAMD2(S) for a symmetric positive definite matrix S, returns the
%    permutation vector p such that S(p,p) tends to have a sparser Cholesky
%    factor than S.  Sometimes SYMAMD works well for symmetric indefinite
%    matrices too.  The matrix S is assumed to be symmetric; only the
%    strictly lower triangular part is referenced.   S must be square.
%    Note that p = amd(S) is much faster and generates comparable orderings.
%    The ordering is followed by an elimination tree post-ordering.
%
%    Note that this function is source code for the built-in MATLAB symamd
%    function.  It has been renamed here to symamd2 to avoid a filename clash.
%    symamd and symamd2 are identical.
%
%    See also SYMAMD, AMD, COLAMD, COLAMD2.
%
%    Example:
%            P = symamd2 (S)
%            [P, stats] = symamd2 (S, knobs)
%
%    knobs is an optional one- to two-element input vector.  If S is n-by-n,
%    then rows and columns with more than max(16,knobs(1)*sqrt(n)) entries are
%    removed prior to ordering, and ordered last in the output permutation P.
%    No rows/columns are removed if knobs(1)<0.  If knobs(2) is nonzero, stats
%    and knobs are printed.  The default is knobs = [10 0].  Note that knobs
%    differs from earlier versions of symamd.

%    Copyright 1998-2007, Timothy A. Davis, and Stefan Larimore
%    Developed in collaboration with J. Gilbert and E. Ng.
%    Acknowledgements: This work was supported by the National Science
%       Foundation, under grants DMS-9504974 and DMS-9803599.

%-------------------------------------------------------------------------------
% perform the symamd ordering:
%-------------------------------------------------------------------------------

if (nargout <= 1 & nargin == 1)						    %#ok
    p = symamd2mex (S) ;
elseif (nargout <= 1 & nargin == 2)					    %#ok
    p = symamd2mex (S, knobs) ;
elseif (nargout == 2 & nargin == 1)					    %#ok
    [p, stats] = symamd2mex (S) ;
elseif (nargout == 2 & nargin == 2)					    %#ok
    [p, stats] = symamd2mex (S, knobs) ;
else
    error('symamd:  incorrect number of input and/or output arguments.') ;
end

%-------------------------------------------------------------------------------
% symmetric elimination tree post-ordering:
%-------------------------------------------------------------------------------

[ignore, q] = etree (S (p,p)) ;
p = p (q) ;

%    stats is an optional 20-element output vector that provides data about the
%    ordering and the validity of the input matrix S.  Ordering statistics are
%    in stats (1:3).  stats (1) = stats (2) is the number of dense or empty
%    rows and columns ignored by SYMAMD and stats (3) is the number of
%    garbage collections performed on the internal data structure used by
%    SYMAMD (roughly of size 8.4*nnz(tril(S,-1)) + 9*n integers).
%
%    MATLAB built-in functions are intended to generate valid sparse matrices,
%    with no duplicate entries, with ascending row indices of the nonzeros
%    in each column, with a non-negative number of entries in each column (!)
%    and so on.  If a matrix is invalid, then SYMAMD may or may not be able
%    to continue.  If there are duplicate entries (a row index appears two or
%    more times in the same column) or if the row indices in a column are out
%    of order, then SYMAMD can correct these errors by ignoring the duplicate
%    entries and sorting each column of its internal copy of the matrix S (the
%    input matrix S is not repaired, however).  If a matrix is invalid in other
%    ways then SYMAMD cannot continue, an error message is printed, and no
%    output arguments (P or stats) are returned.  SYMAMD is thus a simple way
%    to check a sparse matrix to see if it's valid.
%
%    stats (4:7) provide information if SYMAMD was able to continue.  The
%    matrix is OK if stats (4) is zero, or 1 if invalid.  stats (5) is the
%    rightmost column index that is unsorted or contains duplicate entries,
%    or zero if no such column exists.  stats (6) is the last seen duplicate
%    or out-of-order row index in the column index given by stats (5), or zero
%    if no such row index exists.  stats (7) is the number of duplicate or
%    out-of-order row indices.
%
%    stats (8:20) is always zero in the current version of SYMAMD (reserved
%    for future use).


%%  StoichTools: Tools for Doing Stoichiometry
%
% StoichTools comprises a set of Matlab functions for doing stoichiometric
% analysis. These functions parse standard chemical notation for a variety
% of stoichiometric calculations including finding molecular weights,
% balancing reactions for atom and charge conservation, finding independent
% reactions, and displaying formulas in Hill notation. The functions
% account for both change and atomic balances so they can be used to
% balance ionic reactions and chemical half reactions.
%
% StoichTools has extensive documentation including a set of worked
% homework problems demonstrating use of the functions.
%
% These functions were developed to support introductory courses in
% Chemical Engineering.
%
%  Jeff Kantor
%  December 18, 2010

%% What is StoichTools?
%
% StoichTools works with two types of data:
%
% # *Chemical formulas*. Each chemical formula is a string written in a
% nearly universal chemical notation. For example, |H2SO4| represents
% Sulfuric Acid. Grouping is allowed (e.g., |CH3(CH2)6CH3| for octane) with
% either parentheses '()' or brackets '[]'.  Charge is indicated by a
% trailing + or - followed by an optional number (e.g., |Fe+3| or |HSO4-|).
% Phase information may be included as a terminal (aq), (l), (g), or (s).
% Cell arrays can be used in most places to work with multiple formulas at
% one time (e.g., {'H2SO4','H+','SO4-2'}).
% # *Atomic representation*. Many calculations require knowledge of the
% charge, and of number of atoms of each type in a chemical species. This
% is maintained in a Matlab structure where r.C, for example, is the
% number of carbon atoms. The symbol after the dot is the standard 1 or 2
% character symbol for an element. The symbol |Q| is reserved to indicated
% charge. A Matlab structure array is used to store multiple atomic
% reprentations in a single variable.
%
% StoichTools provides functions for the following types of chemical
% calculations:
%
% *Working with Chemical Formulas*
%
% * |r = parse_formula(s)| processes a chemical formula to produce an
%   atomic representation. This function is mainly used by other functions
%   to process chemical formulas.
% * |hillformula| processes a chemical formula or atomic reprentation to
%   produce a chemical formula in standard Hill notation. The Hill notation
%   widely used to represent species in chemical databases, such as the
%   NIST Chemistry Webbook.
%
% *Calculating Molecular Weights*
%
% * |mw = molweight(s)| computes the molecular weights of chemical
%   compounds. Input can be a chemical formula, a cell array of chemical
%   formulas, or an array of atomic representations. If no output is
%   indicated, then a table of molecular weights is printed.
%
% *Stoichiometry*
%
% * |[A,atoms,species] = atomic(s)| constructs the atomic matrix for a set
%   of chemical compounds. Element |A(i,j)| is the number of |atoms{i}| in
%   |species{j}|. Inputs may be chemical formula, a cell array of chemical
%   formulas, If there are ionic species, then a special atom 'Q' is
%   indicates the charge of the species. If no output is indicated, then
%   the atomic matrix is displayed in tabular form.
% * |V = stoich(s)| computes the stoichiometric matrix for a set of
%   chemical compounds. The input is a cell array of chemical formulas, or
%   an array of atomic representations. The columns of |V| correspond to
%   independent chemical reactions satisfying atomic and charge balances.
%   Element |V(j,k)| is the stoichiometric coefficient for species |j| in
%   reaction |k|. A negative value denotes a reactant, a positive value
%   denotes a product. If no output is indicated, then |disp_reaction| is
%   used to display all independent reactions.
% * |Vout = disp_reaction(V,s)| If no output is indicated, then format
%   and displays the chemical reactions denoted by stoichiometric matrix
%   |V| and the array of species |s|. The species may be cell array of
%   formulas or an array of atomic representations. If feasible, the
%   coefficients are scaled to integers.  It integer coefficients are too
%   long, then either rational or floating point coefficients are
%   displayed. If an output is indicated, then |Vout| is a stoichiometric
%   matrix with rescaled coefficients, and the reactions are not displayed.
%
% *Homework Problems with Solutions*
%
% The StoichTools folder includes a number of worked homework problems.
% These are Matlab scripts with titles in the pattern |HW_xx.m|. Each
% script begins with a cell containing the problem statement. Subsequent
% cells demonstrate solution to the problem. The homework files can be
% sviewed by using the Matlab publishing function.

%% Parsing Chemical Formulas
%
% Given a set of chemical species, |r = parse_formula(s)| parses a cell
% array of chemical formulas to produce a structure array r. The value is
% the number of atoms of that element present in the corresponding formula.
% The structure array includes a field for each atomic element in the set
% of species. We call this the atomic represenation of the species.

% Parsing methane

parse_formula('CH4')

%% Additional Parsing Examples

ex{1} = 'NaHCO3';
ex{2} = 'KFe3(SO4)2(OH)6';     % Jorosite
ex{3} = 'KFe3(AsO4)2(HAsO4)2'; % Potassium-Iron-Arsenate
ex{4} = '(CH4)8(H2O)46';       % Methane Clathrate
ex{4} = 'HSO4-(aq)';

for k = 1:length(ex)
    disp(ex{k});
    parse_formula(ex{k})
end

%% Chemical Abbreviations and Isotopes
%
% * Formulas may include D (Deuterium) or T (Tritium). These are treated as
%   elements and included as distinct species in any atom balances.
% * The common organic chemistry abbreviations Me (Methyl, CH3), Et (Ethyl,
%   C2H5), Bu (Butyl, C4H9), Ph (Phenol, C6H5) may be included in formulas.
%   These are replaced by their atomic formulas during the parsing process.
% * The symbols M (any metal) and X (any halogen) may be used in formulas.
%   Formulas containing the symbol M or X have unknown molecular weight.

parse_formula('D2O')
parse_formula('EtOH')
molweight({'H2O','D2O','T2O','EtOH','PhOH','TiO2','MO2'});

%% Non-stoichiometric Formulas
%
% Some applications of stoichiometry involve complex chemical compounds not
% easily described by simple chemical fomulas. So-called
% 'non-stoichiometric' compounds can be also be parsed.

bacteria = 'CH1.8N0.24O0.36';
parse_formula(bacteria);

%% From Atoms to Chemical Formulas
%
% Given a structure array of atomic representations, |s = hillformula(r)}
% constructs a cell array of corresponding chemical formulas.

% Formula for octane

octane.C = 8;
octane.H = 18;
hillformula(octane)

%% Hill Notation & Canonical Representations
%
% The Hill notation is a commonly used system for writing chemical formulas
% in a standard form. % |hillformula(r)| produces a simple canonical
% representation of a chemical species. Note, however, that there may be
% many isomers for a given formula.

s = {'Zr3B2','HBr','HCl','CH3(CH2)6CH3','NaCO3','CaC2','CH3OH', ...
     'CH3COOH','HNO3','H2SO4','NH3','SnH4','CH3HgCH3','(CH3CH2)4Pb', ...
     '[Co(NH3)6]+3','[B12H12]-2'};

fprintf('\n%-15s %-15s\n----------      ----------\n', ...
    'Formula','Hill Notation');
for k = 1:length(s)
    fprintf('%-15s %-15s\n',s{k},char(hillformula(s{k})));
end

%% Molecular Weight
%
%  mw = molweight(s)
%  mw = molweight(r)
%
% Given a cell array of chemical formulas, or a structure array of atomic
% representations, |molweight| computes a corresponding vector of molecular
% weights.

% Molecular Mass of Dimethyl Mercury

s = 'CH3HgCH3';
mw = molweight('CH3HgCH3');
fprintf('Molecular Weight of Dimethyl Mercury (%s) = %g\n',s,mw);

%% Creating Molecular Weight Tables
%
% If molweight as no output, then it prints a table of molecular weights.

molweight(s);

%% Atomic Matrix
%
%  [A,atoms,species] = atomic(s)
%  [A,atoms,species] = atomic(r)
%
% Given a cell array of chemical formulas |s|, or a structure array of
% atomic representations |r|, |atomic| computes the atomic matrix A.
% |atoms| is a a cell array of the atomic elements, |species| is a cell
% array of species. A(i,j) is the number of atoms of element atoms{i} in
% species species{j}. % When called without an output argument, |atomic|
% displays the atomic matrix.

s = {'CH4','O2','H2O','CO2'};

atomic(s);
A = atomic(s);
disp(' ');
disp('A = ');
disp(A);

%% Atomic Matrix for Ionic Species
%
% For ionic species an additional row is added, labeled by 'Q', indicating
% the net charge on each of the species included in the matrix.

s = {'Fe+3','SO4-2','H+','OH-','H2O','Fe2(SO4)3'};
atomic(s);

%% Balancing a Reaction
%
% Given a cell array of chemical formulas, or an array of atomic
% representations, |stoich(s)| computes stoichiometric coefficients that
% satisfy charge and atom balances. If no output is specified, then
% balanced reactions are displayed.

stoich({'NaPb','CH3CH2Cl','(CH3CH2)4Pb','NaCl','Pb'});
stoich({'H+(aq)','OH-(aq)','H2O(l)'});

%% Stoichiometric Matrix
%
% Given a cell array of chemical formulas, or a structure array of atomic
% representations, |V = stoich(s)| computes the stoichiometric matrix |V|.
% |V(n,r)| is the stoichiometric coeffient of species |n| in reaction |r|.
% The atomic and stoichiometric matrices satisfies the relationship |A*V =
% 0|.

s = {'C8H18','O2','C','CO','CO2','H2O'};
V = stoich(s);
disp('Stoichiometric Matrix V = ');
disp(V);

%% Mulitple Independent Reactions
%
%  V = stoich(s)
%  disp_reaction(V,s)
%
% The columns of the stoichiometric matrix |V| represent independent
% reactions. The function |disp_reaction(V,s)| displays the reactions in a
% conventional human readable form.

s = {'C8H18','O2','C','CO','CO2','H2O'};
V = stoich(s);
disp_reaction(V,s);

%% Further Examples of Complex Reactions
%
% Examples from
% <http://www.chemistryhelp.net/chemistry-calculator/chemical-equation-balancer>

stoich({'P2I4','P4','H2O','H3PO4','PH4I'});

stoich({'[Cr(N2H4CO)6]4[Cr(CN)6]3','KMnO4','H2SO4','K2Cr2O7', ...
     'MnSO4','CO2','KNO3','K2SO4','H2O'});

stoich({'Cu(s)','HNO3(aq)','Cu(NO3)2(aq)','NO(g)','H2O(l)'});

stoich({'Cu','HNO3','H2O','Cu(NO3)2','NO'});

stoich({'KMnO4','C3H5(OH)3','K2CO3','Mn2O3','CO2','H2O'});

stoich({'K2Cr2O7','FeCl2','HCl','KCl', ...
    'CrCl3','FeCl3','H2O'});

stoich({'Bi(NO3)3(H2O)5','NaOH','H2O2','RuCl3', ...
     'NaNO3','NaCl','Bi2Ru2O7','H2O'});

stoich({'(NH4)2MoO4','NH4NO3','Na3PO4','H2O', ...
    '(NH4)3[P(Mo3O10)4]','NaNO3','NH3'});

stoich({'H2','Ca(CN)2','NaAlF4','FeSO4','MgSiO3','KI','H3PO4', ...
    'PbCrO4','BrCl','CF2Cl2','SO2','PbBr2','CrCl3','MgCO3', ...
    'KAl(OH)4','Fe(SCN)3','PI3','Na2SiO3','CaF2','H2O'});

stoich({'NH4ClO4','NaY(OH)4','Ru(SCN)3','PBr5','TiCl2CrI4','BeCO3', ...
    'Rb2ZrO3','ZnAt2','CAt2I2','Rb0.998YAt4','RuS2','BeZrO3','Zn(CN)2', ...
    'NaHBr1.997','H3PO4','TiCrO4','ClI','H2SO4','H2O'});

%% Chemical Equations with Ionic Charges
%
% The charge on ionic species is indicated by + or - followed by an
% optional digit indicating the amount of charge.  If ionic species are
% present, then a charge balance is include in the computation of the
% stoichiometric coefficients.

stoich({'ClO2+(aq)','H3O+(aq)','Cl2(g)','H2O(l)','ClO3-(aq)','ClO2(aq)'});
stoich({'Bi+3(aq)','HSnO2-(aq)','OH-(aq)','Bi(s)','H2O','SnO3-2(aq)'});
stoich({'CH3CH2OH','Cr2O7-2','H+','CH3COOH','Cr+3','H2O'});
stoich({'I-','I2','Mn+2','MnO4-','H+','H2O'});
stoich({'Cl2','Cl-','Fe+2','Fe+3'});
stoich({'Mn+2','BiO-3','H+','MnO4-','Bi3+','H2O'});
stoich({'NpO2+2','NpO2(OH)H2C2O4+','NpO2+','CO2','H+','O2'});
stoich({'H3PO4','(NH4)6Mo7O24','H+','(NH4)3PO4(MoO3)12','NH4+','H2O'});

%% Chemical Half Equations
%
% Include the bare electron 'e-' to balance chemical half reactions. In
% acidic solutions, if one of the main reactants contains oxygen, add 'H+'
% and 'H2O'. In basic solutions, if one of the main reactants contains
% oxygen then add 'OH-' and 'H2O'.

stoich({'Al+3(aq)','Al(s)','e-'});
stoich({'Cl-(aq)','Cl2(g)','e-'});

% Acidic Solutions

stoich({'MnO4-(aq)','Mn+2(aq)','H2O(l)','H+(aq)','e-'});
stoich({'O2(g)','H2O(l)','H+(aq)','e-'});
stoich({'Ag2O3','Ag+','H2O','H+','e-'});
stoich({'S2O3-2(aq)','S(s)','H2O(l)','H+(aq)','e-'});
stoich({'HOOCCOOH(aq)','CO2(g)','H2O(l)','H+(aq)','e-'});

% Alkali Solutions

stoich({'MnO4-(aq)','Mn+2(aq)','H2O(l)','OH-(aq)','e-'});
stoich({'Cr(OH)6-2','CrO4-2','H2O','OH-','e-'});
stoich({'NH3OH(aq)','N2(g)','H2O(l)','OH-(aq)','e-'});
stoich({'Al(OH)4-(aq)','Al(s)','H2O(l)','OH-(aq)','e-'});
stoich({'ZrO(OH)2','Zr','H2O','OH-','e-'});

%% Nested Formulas
%
% Matlab regular expressions capabilities are used to parse chemical
% formulas. While this keeps StoichTools simple and fast, one of the
% drawbacks of regular expressions is the difficulty of matching nested
% expressions. Thus nesting is limited to bracketed expressions inside of
% parentheses, or parentheses inside of brackets. By this rule, [Fe2(SO4)3]
% and (Fe2[SO4]3) are allowed, but (Fe2(SO4)3) and [Fe2[SO4]3] are not. In
% practice, chemical formula rarely need more than two levels of nesting.

disp('These work fine.');
molweight({'[Fe2(SO4)3]','(Fe2[SO4]3)'});

fprintf('\n\n');
try
    molweight({'(Fe2(SO4)3)','[Fe2[SO4]3]'})
catch exception
    disp('But this does not.');
    disp(exception.message);
end

%% Version History
%
% * 2010/12/18  Submitted to Matlab Central
% * 2010/12/19  Updated documentation, added solved homeworks
% * 2010/12/19  Put rows of the atomic matrix in Hill order
% * 2010/12/19  Expanded regular expression parsing to include phases
% * 2010/12/20  Enhanced parser to accept non-stoichiometric formulas
% * 2010/12/20  Enhanced disp_reaction for better coefficient formatting
% * 2010/12/21  Parser to include common symbols D, T, Et, Me, Bu, Ph
% * 2010/12/30  Fixed all mlint messages, reduced McCabe complexity
% * 2010/12/30  Update to Matlab Central
% * 2010/12/30  Further improvements to error handling (assert's)
% * 2010/12/31  Fixed bug with NaN in molweight
% * 2010/12/31  Renamed homework files so it makes more sense on MC
% * 2010/12/31  Update to Matlab Central
%
%
% To Do's
%
% * Add Generation/Consumption Analysis
% * Add Extent of Reaction Analysis
% * Include an electrochemistry howework example (battery?)
% * Add a display feature for stoich
% * Add webbook lookup for chemical property data


function [flow_out, time_shifted_points_out] = em1_flow_with_prior(...
    events, ...
    feature_pos, ...
    prev_shifted_points, ...
    prev_shifted_weights, ...
    flow_init, ...
    params, ...
    fig)
%EM1_FLOW_WITH_PRIOR Estimates optical flow for an event feature.
%
% EM1_FLOW_WITH_PRIOR estimates the optical flow of a set of events
% combined to form a 'feature'. This EM step uses the time shifted points
% from the previous iteration as a template, as in:
% Alex Zihao Zhu, Nikolay Atanasov and Kostas Daniilidis.
% "Event-based Visual Inertial Odometry",
% IEEE International Conference on Computer Vision and Pattern Recognition (CVPR), 2017.
%
% Syntax:  [flow_out, time_shifted_points_out] = EM1_FLOW_WITH_PRIOR(...
%            events, ...
%            feature_pos, ...
%            prev_shifted_points, ...
%            prev_shifted_weights, ...
%            flow_init, ...
%            params, ...
%            fig)
%
% Inputs:
%    events               - 4xN, each column is (x,y,t,p).
%    feature_pos          - 2x1, pixel position of the feature.
%    prev_shifted_points  - 2xM, time shifted points from the previous
%                           iteration, used as a template.
%    prev_shifted_weights - 1xM, weights for each shifted point.
%    flow_init            - 2x1, initialization for the flow.
%    params               - parameters, defined in get_params().
%    fig                  - figure handle for plotting.
%
% Outputs:
%    flow_out                - 2x1, estimated flow.
%    time_shifted_points_out - 2xN, input events in the feature window
%                              shifted by the flow [x; y] + dt * flow.
%
% See also GET_PARAMS
%
% Author: Alex Zihao Zhu, University of Pennsylvania
% Email: alexzhu(at)seas.upenn.edu
% Copyright 2018 University of Pennsylvania
% Alex Zihao Zhu, Nikolay Atanasov, Kostas Daniilidis
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, CONTRIBUTORS, AND THE
% TRUSTEES OF THE UNIVERSITY OF PENNSYLVANIA "AS IS" AND ANY EXPRESS OR
% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
% IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE TRUSTEES OF
% THE UNIVERSITY OF PENNSYLVANIA BE LIABLE FOR ANY DIRECT, INDIRECT,
% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
% NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
% THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

%% Initialization
flow = flow_init;
prev_flow = flow;
% Estimated flow
flow_out = [nan; nan];
% Estimated canonical events
time_shifted_points_out = [];
delta_flows = zeros(params.em1_params.max_iters, 1);
num_iter = 0;
scatter_plot_handle = [];
event_window = [];

target_time = events(3, 1);

centered_events = events;
centered_events(1:2, :) = bsxfun(@minus, events(1:2, :), feature_pos);

kdtree = KDTreeSearcher(...
    prev_shifted_points' / (sqrt(2) * params.em1_params.sigma), ...
    'Distance', 'euclidean');

if params.debug
    set(0, 'CurrentFigure', fig)
    subplot(2,1,2)
    if ~isempty(prev_shifted_points)
        scatter(prev_shifted_points(1, :), prev_shifted_points(2, :), prev_shifted_weights * 10, 'b.')
    end
end

%% Main EM Loop
while true
    if num_iter > params.em1_params.max_iters
        return
    end

    time_shifted_points = centered_events(1:2,:) + ...
        bsxfun(@times, flow,(target_time-centered_events(3,:)));

    if isempty(event_window)
        event_window = ...
            time_shifted_points(1, :) >= -params.window_size/2 &...
            time_shifted_points(2, :) >= -params.window_size/2 & ...
            time_shifted_points(1, :) <= params.window_size/2 & ...
            time_shifted_points(2, :) <= params.window_size/2;

        n_in_window = sum(event_window);
        if n_in_window < params.min_events_for_em
            return
        end

        centered_events = centered_events(:, event_window);
        time_shifted_points = time_shifted_points(:, event_window);

        weights = zeros(size(prev_shifted_points, 2), size(time_shifted_points, 2));
    end

    [neighbors_cell, distances_cell] = rangesearch(...
        kdtree, ...
        time_shifted_points' / (sqrt(2) * params.em1_params.sigma), ...
        params.em1_params.max_distance);

    distancesstacked = cell2mat(cellfun(...
        @transpose, distances_cell, 'UniformOutput', false))';

    if isempty(distancesstacked)
        return
    end

    % NOTE: 'length' is not the same as @length
    num_neighbors = cellfun('length', neighbors_cell);
    prev_correspondences = cell2mat(cellfun(...
        @transpose, neighbors_cell, 'UniformOutput', false))';
    curr_correspondences = repelem(1:size(time_shifted_points, 2), num_neighbors);

    % It's cheaper to multiply to 0 to reset the weights matrix than to
    % reinitialize it using zeros.
    weightsstacked = exp(-distancesstacked);
    weights = weights * 0;

    valid_inds = sub2indc(...
        curr_correspondences, ...
        prev_correspondences, ...
        size(weights));

    weights(valid_inds) = weightsstacked;
    weights = bsxfun(@times, prev_shifted_weights, weights);
    weights_sum = sum(weights, 1);
    valid_weights = weights_sum > 0;
    weights = bsxfun(@rdivide, weights, weights_sum + 1e-10);

    % Simultaneously minimize over flow and translation
    weighted_prior_points = prev_shifted_points * weights;
    weighted_prior_points = weighted_prior_points(:, valid_weights);

    valid_centered_events = centered_events(:, valid_weights);
    dx = weighted_prior_points(1:2, :) - valid_centered_events(1:2, :);
    dt = target_time - valid_centered_events(3, :);

    flow = (dx*dt') / (dt*dt');

    %% Calculate change in flow, plot debug information.
    if (norm(flow - prev_flow) < params.em1_params.min_err)
        break;
    end

    delta_flows(num_iter+1) = norm(flow - prev_flow);
    prev_flow = flow;

    if params.debug
        set(0, 'CurrentFigure', fig)
        subplot(2,1,1)
        plot(delta_flows(delta_flows > 0),'b')
        title('EM1 with prior change in flow (convergence criterion)')
        xlim([0 params.em1_params.max_iters])

        subplot(2,1,2)
        if (~isempty(scatter_plot_handle))
            delete(scatter_plot_handle)
        end

        if ~isempty(prev_shifted_points)
            hold on
        end

        scatter_plot_handle = scatter(time_shifted_points(1, :), time_shifted_points(2, :),'r.');
        hold off
        axis equal
        axis([-params.window_size/2-5 params.window_size/2+5 -params.window_size/2-5 params.window_size/2+5])
        axis ij

        title('EM1 with prior time shifted events')
        pause(0.01)
    end

    num_iter = num_iter + 1;
end

if params.debug
    pause(0.5)
end

flow_out = flow;

dt = events(3, end) - events(3, 1);
centered_events = bsxfun(@minus, events(1:2, :), feature_pos + flow * dt);
time_shifted_points = centered_events(1:2, :) + ...
    bsxfun(@times, flow, (events(3, end) - events(3,:)));

% Make the window a little bigger.
window_size = round(params.window_size * 1.5);

event_window = time_shifted_points(1, :) >= -window_size/2 & ...
    time_shifted_points(2, :) >= -window_size/2 & ...
    time_shifted_points(1, :) <= window_size/2 & ...
    time_shifted_points(2, :) <= window_size/2;

time_shifted_points_out = time_shifted_points(:, event_window);
end


% Shear Waves And Critical Angle Reflection Example
%
% This example illustrates Snell's law for elastic media using a weakly
% focused ultrasound transducer incident on a soft-tissue / bone interface.
% It builds on the Explosive Source In A Layered Medium and Snell's Law And
% Critical Angle Reflection examples.
%
% author: Bradley Treeby
% date: 14th Nov 2013
% last update: 15th February 2014
%
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
% Copyright (C) 2009-2014 Bradley Treeby and Ben Cox

% This file is part of k-Wave. k-Wave is free software: you can
% redistribute it and/or modify it under the terms of the GNU Lesser
% General Public License as published by the Free Software Foundation,
% either version 3 of the License, or (at your option) any later version.
%
% k-Wave is distributed in the hope that it will be useful, but WITHOUT ANY
% WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
% FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
% more details.
%
% You should have received a copy of the GNU Lesser General Public License
% along with k-Wave. If not, see <http://www.gnu.org/licenses/>.

clear all;

% =========================================================================
% SIMULATION PARAMETERS
% =========================================================================

% change scale to 2 to reproduce the higher resolution figures used in the
% help file
scale = 1;

% create the computational grid
PML_size = 10;               % size of the PML in grid points
Nx = 128*scale - 2*PML_size; % number of grid points in the x direction
Ny = 192*scale - 2*PML_size; % number of grid points in the y direction
dx = 0.1e-3/scale;           % grid point spacing in the x direction [m]
dy = 0.1e-3/scale;           % grid point spacing in the y direction [m]
kgrid = makeGrid(Nx, dx, Ny, dy);

% define the medium properties
cp1 = 1540;         % compressional wave speed [m/s]
cs1 = 0;            % shear wave speed [m/s]
rho1 = 1000;        % density [kg/m^3]
alpha0_p1 = 0.1;    % compressional wave absorption [dB/(MHz^2 cm)]
alpha0_s1 = 0.1;    % shear wave absorption [dB/(MHz^2 cm)]

cp2 = 3000;         % compressional wave speed [m/s]
cs2 = 1400;         % shear wave speed [m/s]
rho2 = 1850;        % density [kg/m^3]
alpha0_p2 = 1;      % compressional wave absorption [dB/(MHz^2 cm)]
alpha0_s2 = 1;      % shear wave absorption [dB/(MHz^2 cm)]

% create the time array
cfl   = 0.1;
t_end = 12e-6;
kgrid.t_array= makeTime(kgrid, cp1, cfl, t_end);

% define position of heterogeneous slab
slab = zeros(Nx, Ny);
slab(Nx/2:Nx, :) = 1;

% define the source properties
source_freq = 2e6;              % [Hz]
source_strength = 1e6;          % [Pa]
source_cycles = 3;              % number of tone burst cycles
source_focus_dist = 5*scale;    % position of focus inside slab [grid points]
source_slab_dist = 5*scale;     % distance between source and slab [grid points]
source_mask = makeCircle(Nx, Ny, Nx/2 + source_focus_dist, Ny/2, 50*scale, pi/3);
source_mask(Nx/2 - source_slab_dist:end, :) = 0;

% define the sensor to record the maximum particle velocity everywhere
sensor.record = {'u_max_all'};

% set the input arguments
input_args = {'PMLSize', PML_size, 'PMLAlpha', 2, 'PlotPML', false, ...
    'PMLInside', false, 'PlotScale', [-1, 1]*source_strength, ...
    'DisplayMask', 'off', 'DataCast', 'single'};

% =========================================================================
% FLUID SIMULATION
% =========================================================================

% assign the medium properties
medium.sound_speed            = cp1*ones(Nx, Ny);
medium.sound_speed(slab == 1) = cp2;
medium.density                = rho1*ones(Nx, Ny);
medium.density(slab == 1)     = rho2;
medium.alpha_coeff            = alpha0_p1*ones(Nx, Ny);
medium.alpha_coeff(slab == 1) = alpha0_p2;
medium.alpha_power            = 2;

% assign the source
source.p_mask = source_mask;
source.p = source_strength*toneBurst(1/kgrid.dt, source_freq, source_cycles);

% run the fluid simulation
sensor_data_fluid = kspaceFirstOrder2D(kgrid, medium, source, sensor, input_args{:});

% =========================================================================
% ELASTIC SIMULATION
% =========================================================================

% define the medium properties
clear medium
medium.sound_speed_compression            = cp1*ones(Nx, Ny);
medium.sound_speed_compression(slab == 1) = cp2;
medium.sound_speed_shear                  = cs1*ones(Nx, Ny);
medium.sound_speed_shear(slab == 1)       = cs2;
medium.density                            = rho1*ones(Nx, Ny);
medium.density(slab == 1)                 = rho2;
medium.alpha_coeff_compression            = alpha0_p1*ones(Nx, Ny);
medium.alpha_coeff_compression(slab == 1) = alpha0_p2;
medium.alpha_coeff_shear                  = alpha0_s1*ones(Nx, Ny);
medium.alpha_coeff_shear(slab == 1)       = alpha0_s2;

% assign the source
clear source
source.s_mask = source_mask;
source.sxx = -source_strength*toneBurst(1/kgrid.dt, source_freq, source_cycles);
source.syy = source.sxx;

% run the elastic simulation
sensor_data_elastic = pstdElastic2D(kgrid, medium, source, sensor, input_args{:});

% =========================================================================
% VISUALISATION
% =========================================================================

% define plot vector
x_vec = kgrid.x_vec(1 + PML_size:end - PML_size)*1e3;
y_vec = kgrid.y_vec(1 + PML_size:end - PML_size)*1e3;

% calculate square of velocity magnitude
u_e = sensor_data_elastic.ux_max_all.^2 + sensor_data_elastic.uy_max_all.^2;
u_f = sensor_data_fluid.ux_max_all.^2 + sensor_data_fluid.uy_max_all.^2;

% plot layout
figure;
imagesc(y_vec, x_vec, double(source_mask | slab));
xlabel('y [mm]');
ylabel('x [mm]');
axis image;
colormap(flipud(gray));

% plot beam patterns
figure;
subplot(2, 1, 1);
imagesc(y_vec, x_vec, 20*log10(u_f./max(u_f(:))));
xlabel('y [mm]');
ylabel('x [mm]');
axis image;
colorbar;
caxis([-50, 0]);
title('Fluid Model');

subplot(2, 1, 2);
imagesc(y_vec, x_vec, 20*log10(u_e./max(u_e(:))));
xlabel('y [mm]');
ylabel('x [mm]');
axis image;
colorbar;
caxis([-50, 0]);
title('Elastic Model');
colormap(jet(256));