function [region_meta_info]=preprocess_mcg_candidates(imnames, mcgdir, gtdir, ovoutdir, sptextdir, regspimgdir, N)
if(~exist(ovoutdir, 'file'))
    mkdir(ovoutdir);
end
if(~exist(sptextdir, 'file'))
    mkdir(sptextdir);
end
if(~exist(regspimgdir, 'file'))
    mkdir(regspimgdir);
end

for i=1:numel(imnames)
    tmp=load(fullfile(mcgdir, [imnames{i} '.mat']));
    candidates=tmp.candidates; clear tmp;
    %get reg2sp
    sp=candidates.superpixels;
    N1=min(N, numel(candidates.labels));
    reg2sp=false(max(sp(:)),N1);
    for j=1:N1
        reg2sp(candidates.labels{j},j)=true;
    end

    %compress to remove irrelevant sps
    [sp, reg2sp]=compressSp2reg(sp, reg2sp);

    if(~exist(fullfile(ovoutdir, [imnames{i} '.mat']), 'file'))
    %compute overlaps between everything
    ov=double(reg2sp')*double(reg2sp);
    ov=ov>0;
    save(fullfile(ovoutdir, [imnames{i} '.mat']), 'ov');
    end
    %load gt
    if(isempty(gtdir))
        %no gt
        inst=zeros(size(sp));
        categories=[];
    else
    [cls, inst, categories]=load_gt(gtdir, imnames{i});
    end
    %compute overlaps with ground truth
    overlap=get_gt_overlaps(reg2sp, sp, inst);
    box_overlap=get_gt_overlaps_box(reg2sp, sp, inst);
    %populate meta info
    region_meta_info.num_regs(i)=size(reg2sp,2)+numel(categories);
    region_meta_info.overlaps{i}=overlap;
    region_meta_info.box_overlaps{i}=box_overlap;
    region_meta_info.gt{i}=[categories(:)' zeros(1,size(reg2sp,2))];

    %add ground truth to reg2sp
    gt_sp=double(inst+1);
    gt_reg2sp=[zeros(1,numel(categories)); eye(numel(categories))];
    gt_reg2sp=logical(gt_reg2sp);
    [sp, reg2sp]=intersect_partitions(gt_sp, sp, gt_reg2sp, reg2sp);
    region_meta_info.boxes{i}=get_region_boxes(sp, reg2sp);
    %write sprep

    textfile=fullfile(sptextdir, [imnames{i} '.txt']);
    if(~exist(textfile, 'file'))
    write_sprep_text(sp, textfile);
    reg2spfile=fullfile(regspimgdir, [imnames{i} '.png']);
    imwrite(uint8(reg2sp),reg2spfile);
    end
    fprintf('Done %d\n', i);
end

function write_sprep_text(sp, filename)
fid=fopen(filename, 'w');
fprintf(fid,'%d %d\n', size(sp,1), size(sp,2));
fprintf(fid, '%d ', sp(:));
fclose(fid);

function [sp, sp2reg]=intersect_partitions(sp1, sp2, sp2reg1, sp2reg2)
%do a unique
[unique_pairs, junk, sp]=unique([sp1(:) sp2(:)], 'rows');
sp=reshape(sp, size(sp1));

%create new sp2reg
sp2reg=[sp2reg1(unique_pairs(:,1),:) sp2reg2(unique_pairs(:,2),:)];

%break disconnected sp
 spNew = zeros(size(sp));
  sp2regNew = false(size(sp2reg));
  cnt = 0;
  %% Check connectivity of superpixels
  for i = 1:max(sp(:))
    tt = bwconncomp(sp == i);
    for j = 1:tt.NumObjects,
      cnt = cnt+1;
      spNew(tt.PixelIdxList{j}) = cnt;
      sp2regNew(cnt, :) = sp2reg(i, :);
    end
  end

sp=spNew;
sp2reg=sp2regNew;

function [spNew sp2regNew] = compressSp2reg(sp, sp2reg)
  assert(islogical(sp2reg), 'sp2reg should be logical');
  [sp2reg bb spSame] = unique(sp2reg, 'rows');
  sp = spSame(sp);

  spNew = zeros(size(sp));
  sp2regNew = false(size(sp2reg));
  cnt = 0;
  %% Check connectivity of superpixels
  for i = 1:max(sp(:))
    tt = bwconncomp(sp == i);
    for j = 1:tt.NumObjects,
      cnt = cnt+1;
      spNew(tt.PixelIdxList{j}) = cnt;
      sp2regNew(cnt, :) = sp2reg(i, :);
    end
  end


function [h_ctx,h_iskl,h_oskl,h_slp] = spm_eeg_inv_checkmeshes(varargin)
% Display tesselated surfaces of cortex, skull and scalp
% FORMAT [h_ctx,h_iskl,h_oskl,h_slp] = spm_eeg_inv_checkmeshes(S)
% S      - input data struct (optional)
%
% h_ctx  - handle to cortex patch
% h_iskl - handle to inner skull patch
% h_oskl - handle to outer skull patch
% h_slp  - handle to scalp patch
%__________________________________________________________________________
% Copyright (C) 2005-2014 Wellcome Trust Centre for Neuroimaging

% Jeremie Mattout
% $Id: spm_eeg_inv_checkmeshes.m 6182 2014-09-18 12:03:18Z guillaume $

%-Initialisation
%--------------------------------------------------------------------------
[D,val] = spm_eeg_inv_check(varargin{:});
try
    %disp(D.inv{val}.mesh);
    mesh  = spm_eeg_inv_transform_mesh(eye(4), D.inv{val}.mesh);
    Mctx  = mesh.tess_ctx;
    Miskl = mesh.tess_iskull;
    Moskl = mesh.tess_oskull;
    Mslp  = mesh.tess_scalp;
catch
    spm('alert!','Please create meshes',mfilename);
    return
end

if spm('CmdLine'), [h_ctx,h_iskl,h_oskl,h_slp] = deal([]); return; end

%-SPM Graphics figure
%--------------------------------------------------------------------------
Fgraph  = spm_figure('GetWin','Graphics'); spm_figure('Clear',Fgraph)

%-Cortex Mesh Display
%--------------------------------------------------------------------------
h_ctx   = patch('vertices',Mctx.vert,'faces',Mctx.face,...
            'EdgeColor','b','FaceColor','b');
hold on

%-Inner-skull Mesh Display
%--------------------------------------------------------------------------
h_iskl  = patch('vertices',Miskl.vert,'faces',Miskl.face,...
            'EdgeColor','r','FaceColor','none');

%-Outer-skull Mesh Display
%--------------------------------------------------------------------------
h_oskl  = patch('vertices',Moskl.vert,'faces',Moskl.face,...
            'EdgeColor',[1 .5 .35],'FaceColor','none');

%-Inner Scalp Mesh Display
%--------------------------------------------------------------------------
h_slp   = patch('vertices',Mslp.vert,'faces',Mslp.face,...
            'EdgeColor',[1 .7 .55],'FaceColor','none');

%-Slices
%--------------------------------------------------------------------------
pls = 0.05:0.2:0.9;
N   = nifti(mesh.sMRI);
d   = size(N.dat);
pls = round(pls.*d(3));
for i=1:numel(pls)
    [x,y,z] = ndgrid(1:d(1),1:d(2),pls(i));
    f1 = N.dat(:,:,pls(i));
    M  = N.mat;
    x1 = M(1,1)*x+M(1,2)*y+M(1,3)*z+M(1,4);
    y1 = M(2,1)*x+M(2,2)*y+M(2,3)*z+M(2,4);
    z1 = M(3,1)*x+M(3,2)*y+M(3,3)*z+M(3,4);

    s  = surf(x1,y1,z1,f1);
    set(s,'EdgeColor','none')
end

%-Fiducials
%--------------------------------------------------------------------------
pnt   = mesh.fid.fid.pnt;
h_fid = plot3(pnt(:,1), pnt(:,2), pnt(:,3), '.c', 'MarkerSize', 30);
h_fid_txt = text(pnt(:,1), pnt(:,2), pnt(:,3), mesh.fid.fid.label);

axis image off;
colormap('gray');
view(-135,45);
rotate3d on
drawnow
hold off


function FemMat = fem_remove_elem(FemMat, iRemoveElem)
% FEM_REMOVE_ELEM: Remove some elements from a 3D mesh

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2020

% Remove elements
FemMat.Elements(iRemoveElem, :) = [];
FemMat.Tissue(iRemoveElem) = [];
if isfield(FemMat, 'Tensors') && ~isempty(FemMat.Tensors)
    FemMat.Tensors(iRemoveElem, :) = [];
end

% Find vertices to remove
nVert = size(FemMat.Vertices, 1);
iVertCut = setdiff(1:nVert, unique(FemMat.Elements(:)));
% Re-numbering matrix
iVertKept = setdiff(1:nVert, iVertCut);
iVertMap = zeros(1, nVert);
iVertMap(iVertKept) = 1:length(iVertKept);
% Remove vertices
FemMat.Vertices(iVertCut,:) = [];
% Renumber vertices in elements list
FemMat.Elements = iVertMap(FemMat.Elements);


function [imgOut, BTMO_segments, BTMO_which_operator] = BanterleTMO(img, BTMO_segments, BTMO_rescale)
%
%
%        [imgOut, BTMO_segments, BTMO_which_operator] = BanterleTMO(img, BTMO_segments)
%
%
%       Input:
%           -img: an HDR image with calibrated data in cd/m^2.
%           Note that this algorithm was tested with values
%           from 0.015cd/m^2 to 3,000 cd/m^2
%           -BTMO_segments: a segmented image, each value in a segment is a
%           dynamic range zone; i.e. integer values in [-6,9]. If it is not
%           provided this will be computed with the function CreateSegments
%           -BTMO_rescale:
%
%       Output:
%           -imgOut: the tone mapped image using the HybridTMO
%           -BTMO_segments: the segmentation output; the input image is
%           segmented into different zones of dynamic range
%           -BTMO_which_operator: is a string which outputs if only
%           DragoTMO is used, if only Reinhard is used, or if the full
%           BanterleTMO is used.
%
%       This TMO is an hybrid operator which merges different
%       Tone Mapping Operators: DragoTMO and ReinhardTMO
%
%     Copyright (C) 2013-15  Francesco Banterle
%
%     This program is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
%
%     This program 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 General Public License for more details.
%
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see <http://www.gnu.org/licenses/>.
%
%     The paper describing this technique is:
%     "Dynamic Range Compression by Differential Zone Mapping Based on
%     Psychophysical Experiments"
% 	  by Francesco Banterle, Alessandro Artusi, Elena Sikudova,
%     Thomas Edward William Bashford-Rogers, Patrick Ledda, Marina Bloj, Alan Chalmers
%     in ACM Symposium on Applied Perception (SAP) - August 2012
%
%

checkNegative(img);

if(~exist('BTMO_rescale', 'var'))
    BTMO_rescale = 1;
end

L = lum(img);

if(BTMO_rescale)
    L_max = MaxQuart(L, 0.999);

    if(L_max > 0.0)
        img = img / L_max * 3000.0;
        img = ClampImg(img, 0.015, 3000.0);
        L = lum(img);
    end
end

indx = find(L > 3000);
if(~isempty(indx))
    warning(['The input image has values over 3,000 cd/m^2. ', ...
        'These values were not tested in the original paper.']);
end

indx = find(L < 0.015);
if(~isempty(indx))
    warning(['The input image has values under 0.015 cd/m^2. ', ...
        'These values were not tested in the original paper.']);
end

%segmentation
if(~exist('BTMO_segments', 'var'))
    BTMO_segments = CreateSegments(img);
else
    if(isempty(BTMO_segments))
        BTMO_segments = CreateSegments(img);
    else
        BTMO_segments = round(BTMO_segments);
    end
end

%TMO look-up table for determing the best
%TMO depending on the luminance zone. These
%values were extracted from a psychophysical
%experiment.
% 0 ---> Drago et al. 2003
% 1 ---> Reinhard et al. 2002
LumZone     = [-2, -1, 0, 1, 2, 3, 4];
TMOForZone =  [ 0,  0, 1, 0, 1, 0, 0];

%mask
mask = zeros(size(BTMO_segments));

for i=1:length(LumZone)
    mask(BTMO_segments == LumZone(i)) = TMOForZone(i);
end

imwrite(mask, 'mask.png');
%check for Drago et al.'s operator
indx0 = find(mask == 1);

%check for Reinhard et al.'s operator
indx1 = find(mask == 0);

%are both TMOs used?
if(~isempty(indx0) && ~isempty(indx1)) %pyramid blending with gamma encoding
    img_dra_tmo = DragoTMO(img);
    img_rei_tmo = ReinhardTMO(img, 0.5, -1, 'local', -1);

    gamma = 2.2;
    invGamma = 1.0 / gamma;
    imgA   = img_rei_tmo.^invGamma;
    imgB   = img_dra_tmo.^invGamma;

    imwrite(imgA, 'banterle_rei.png');
    imwrite(imgB, 'banterle_dra.png');
    imwrite(mask, 'banterle_mask.png');

    %removing gamma for linear output
    imgOut = pyrBlend(imgA, imgB, mask).^gamma;
    imwrite(imgOut.^invGamma, 'banterle_mix.png');

    BTMO_which_operator = 'BanterleTMO';
else
    %Only Reinhard et al.'s operator?
    length(indx1)
    length(indx0)
    if(isempty(indx1))
        imgOut = ReinhardTMO(img, -1, -1, 'bilateral', -1);
        BTMO_which_operator = 'ReinhardTMO';
        disp('The BanterleTMO is using ReinhardTMO only');
    end

    %Only Drago et al.'s operator?
    if(isempty(indx0))
        imgOut = DragoTMO(img);
        BTMO_which_operator = 'DragoTMO';
        disp('The BanterleTMO is using DragoTMO only');
    end
end

end


%  Sorts population pf mcmc draws
%
%  ::
%
%    [pop,tags]=sort_population(pop)
%
%  Args:
%
%     - **pop** [struct]: vector of individuals, each with fields
%
%       - **f** [numeric]: fitness level
%       - **x** [vector]: parameters
%
%  Returns:
%     :
%
%     - **pop** [struct]: sorted vector of individuals
%     - **tags** [vector]: reordering index
%
%


 function out = make_block(in, nblock)
%function out = make_block(in, nblock)
% construct blocks of sparse matrix for Gtomo2_sparse object
% called by Gblock.m
% Copyright 2002-2-19, Jeff Fessler, The University of Michigan

if nargin < 2, ir_usage, end

out = in; % copy entire object

nb = in.nb;
na = in.na;
G = in.G;

for iblock=1:nblock
	ia = iblock:nblock:na;
	ii = outer_sum(1:nb, (ia-1)*nb);
	out.blocks{iblock} = G(ii(:),:);
end


function[h]=letterlabels(arg1,arg2,arg3)
%LETTERLABELS	For automatically putting letter labels on subplots.
%
%   LETTERLABELS puts letter labels '(a)','(b)','(c)' etc., on the
%   subplots of the current figure, in the upper left-hand corner of
%   each subplot window.
%
%   LETTERLABELS(I) specifies the labelling position, clockwise from
%   top left:
%
%		I=1	Top left
%		I=2	Top right
%		I=3	Bottom right
%		I=4	Bottom left
%
%   LETTERLABELS(H,I), where H is a vector of handles to subplots,
%   puts labels on the subplots indicated by H.
%
%   LETTERLABELS(H,I,'d'), begins the labelling with letter 'd'.
%
%   LETTERLABELS will not put the letters on the right subplots if you
%   click on them before hand (because this changes the order of the
%   subplot handles).  Also if you want to label the subplots in a
%   nonstandard order, do this by reordering H.
%   _________________________________________________________________
%   This is part of JLAB --- type 'help jlab' for more information
%   (C) 2000--2021 J.M. Lilly --- type 'help jlab_license' for details

if strcmpi(arg1,'--t')
    return
end

py=0.08;
ar=get(gca,'plotboxaspectratio');
xstretch=ar(1)./ar(2);
px=py/xstretch*1.8;

firstlet=real('a');
i=1;
axhand=flipud(get(gcf,'children'));

bool=false(size(axhand));
for i=1:length(bool)
    bool(i)=strcmp(axhand(i).Type,'axes');
end
axhand=axhand(bool);

for j=1:nargin
    xx=eval(['arg' int2str(j)]);
    isaxhandle=false;
    if ishandle(xx)
        if strcmp(get(xx,'Type'),'axes')
            isaxhandle=true;
        end
    end
    if ischar(xx)&~isaxhandle
        firstlet=real(xx);
    elseif (length(xx)==1)&&~isaxhandle
        i=xx;
    else
        axhand=xx;
    end
end
nax=length(axhand);
fact=1/100;

for j=1:length(axhand)
    axes(axhand(j))
    ax=axis;
    isrevx=strcmpi(get(gca,'xdir'),'reverse');
    isrevy=strcmpi(get(gca,'ydir'),'reverse');

    if ~isrevx
        x1=ax(1);
        x2=ax(2);
    else
        x1=ax(2);
        x2=ax(1);
    end
    if ~isrevy
        y1=ax(3);
        y2=ax(4);
    else
        y1=ax(4);
        y2=ax(3);
    end

    if i==1
        t=y2-(y2-y1)*fact;
        b=y2-(y2-y1)*py;
        l=x1+(x2-x1)*fact;
        r=x1+(x2-x1)*px;
    elseif i==2
        t=y2-(y2-y1)*fact;
        b=y2-(y2-y1)*py;
        l=x2-(x2-x1)*px;
        r=x2-(x2-x1)*fact;
    elseif i==3
        t=y1+(y2-y1)*py;
        b=y1+(y2-y1)*fact;
        l=x2-(x2-x1)*px;
        r=x2-(x2-x1)*fact;
    elseif i==4
        t=y1+(y2-y1)*py;
        b=y1+(y2-y1)*fact;
        l=x1+(x2-x1)*fact;
        r=x1+(x2-x1)*px;
    end

    %Tried setting log to lin before adding label but didn't help
    %if ~strcmpi(get(gca,'yscale'),'log')
    %    h(j)=patch([l l r r],[b t t b],'w');
    %    set(h(j),'edgecolor',[1 1 1])
    %end
    cx=l/2+r/2;
    cy=t/2+b/2;
    text(l+(r-l)*0.2,cy,['(' char(firstlet-1+j),')']);
    axis(ax)
end

if nargout==0
    clear h
end


function findedge(node,edge,range,varargin)
%% FINDEDGE highlights edges
%
%    FINDEDGE(node,edge,range) finds all elements whose indices are in the
%    range array by displaying these elements in yellow.
%
%    FINDEDGE(node,edge) finds all elements.
%
%    FINDEDGE(node,edge,'noindex') skip the display of indices.
%
%    FINDEDGE(node,edge,range,'param','value','param','value'...) allows
%    additional patch param/value pairs to highlight the elements.
%
% Example:
%     node = [1,0; 1,1; 0,1; -1,1; -1,0; -1,-1; 0,-1; 0,0];
%     elem = [1,2,8; 3,8,2; 8,3,5; 4,5,3; 7,8,6; 5,6,8];
%     subplot(1,2,1);
%     showmesh(node,elem);
%     T = auxstructure(elem);
%     findedge(node,T.edge,5,'index','color','r','MarkerSize',24);
%     subplot(1,2,2);
%     showmesh(node,elem);
%     findedge(node,T.edge);
%
%   See also findelem3, findnode3, findedge.
%
% Copyright (C) Long Chen. See COPYRIGHT.txt for details.

% set up range
if (nargin<=2) || (isempty(range))
    range = (1:size(edge,1))';
end
if islogical(range)
    range = find(range);
end
if size(range,2)>size(range,1)
    range = range';
end
% draw edges in red
midEdge = (node(edge(range,1),:)+node(edge(range,2),:))/2;
hold on
if length(range) < size(edge,1)
    h = line([node(edge(range,1),1)'; node(edge(range,2),1)'],...
             [node(edge(range,1),2)'; node(edge(range,2),2)'],...
             'LineWidth',2,'Color','r');
 else
    h = plot(midEdge(:,1),midEdge(:,2),'r.','MarkerSize', 18);
end
% else
%     h = plot(midEdge(:,1),midEdge(:,2),'r.','MarkerSize', 18);
% end
if nargin > 3
    if strcmp(varargin{1},'noindex') || strcmp(varargin{1},'index') && length(varargin)>1
        startidx = 2;
    else
        startidx = 1;
    end
    set(h,varargin{startidx:end});
end
if (nargin <=3) || ~(strcmp(varargin{1},'noindex'))
    text(midEdge(:,1)+0.025,midEdge(:,2)-0.025,int2str(range), ...
         'FontSize',12,'FontWeight','bold','Color','k');
end


classdef edgefx
    %   EDGEFX: Edgefunction class
    %   Constructs edgefunctions that are based on numerous geomteric and
    %   topographic criteria that guide the spatially variability of elemental
    %   resolution when building a mesh.
    %   Copyright (C) 2018  Keith Roberts & William Pringle
    %
    %   The following properties are available for input to the edgefx method:
    %      Keyword     Default Val	Variable type
    %      'dis'        defval;     scalar
    %      'fs'         defval;     scalar
    %      'wl'         defval;     scalar/array
    %      'slp'        defval;     scalar/array
    %      'ch'         defval;     scalar
    %      'min_el_ch'  100;     	scalar
    %      'AngOfRe'    60;        	scalar
    %      'max_el'     inf;        scalar/array
    %      'max_el_ns'  inf;     	scalar
    %      'g'          0.20;       scalar/array
    %      'geodata'    defval;     geodata class
    %      'lmsl'       defval;     geodata class
    %      'dt'         -1;         scalar
    %      'fl'         defval;     scalar
    %      'Channels'   defval;   	cell array
    %      'h0'         defval;     scalar
    %      [defval = 0; % placeholder value if arg is not passed.]
    %
    %      The 'wl','slp','max_el','g' options inputs can be scalars to apply
    %      the parameter globally or they can be arrays to bound each parameter
    %      value within topographic elevation bounds, using the following stucture:
    %      [parameter_value1 z_min1 z_max1;
    %       parameter_value2 z_min2 z_max2;
    %       ...
    %       parameter_valuen z_minn z_maxn];
    %
    %       where, z_min is the minimum elevation bound and z_max is the maximum elevation bound.
    %        - similar to axis bounds on matlab plots such as caxis, xlim, ylim, ...
    %        - z is oriented same as the DEM, i.e., +ve value of z is above datum (overland)
    %
    %   This program is free software: you can redistribute it and/or modify
    %   it under the terms of the GNU General Public License as published by
    %   the Free Software Foundation, either version 3 of the License, or
    %   (at your option) any later version.
    %
    %   This program 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 General Public License for more details.
    %
    %   You should have received a copy of the GNU General Public License
    %   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    properties
        nx % number of x coords
        ny % number of y coords
        x0y0 % 2-tuple of bot. left of domain

        lmsl % lmsl boundary as a geodata class

        bbox
        boubox
        used % edge function keywords

        dis  % percent the edgelength changes in space
        fd   % distance function
        hhd  % matrix containing distance fx values.
        fs   % number of elements to resolve a feature
        fsd  % matrix containing feature size fx values.
        wl   % wavelength
        wld  % matrix containing wavelength fx values.
        slp  % slope edge function
        slpd % matrix containing filtered slope fx values.
        fl   % slope filter parameters
        ch   % channels
        chd  % matrix containing channel fx values.
        AngOfRe % Angle of reslope
        g    % max allowable grade of mesh.
        dt   % theoretical simulateable timestep

        F    % edge function in gridded interpolant format.

        h0    % min. resolution of edgelength function in meters
        gridspace % edgelength function resolution in WGS84 degrees.
        max_el % max. resolution in domain
        max_el_ns % max. resolution +-<0.01 from shoreline
        boudist % distance in WGS84 degrees to boundary of mesh

        min_el_ch % min. element size along the channel
        Channels % cell array of streams.

    end

    methods

        %% Class constructor/pass it a shape class and edgefx params.
        function obj = edgefx(varargin)
            % Check for m_map dir
            M_MAP_EXISTS=0 ;
            if exist('m_proj','file')==2
              M_MAP_EXISTS=1 ;
            end
            if M_MAP_EXISTS~=1
              error('Where''s m_map? Chief, you need to read the user guide')
            end

            % Check for utilties dir
            UTIL_DIR_EXISTS=0 ;
            if exist('inpoly.m','file')
              UTIL_DIR_EXISTS=1 ;
            end
            if UTIL_DIR_EXISTS~=1
              error('Where''s the utilities directory? Chief, you need to read the user guide')
            end

            p = inputParser;

            defval = 0; % placeholder value if arg is not passed.
            % add name/value pairs
            addOptional(p,'dis',defval);
            addOptional(p,'fs',defval);
            addOptional(p,'wl',defval);
            addOptional(p,'slp',defval);
            addOptional(p,'ch',defval);
            addOptional(p,'min_el_ch',100);
            addOptional(p,'AngOfRe',60);
            addOptional(p,'max_el',inf);
            addOptional(p,'max_el_ns',inf);
            addOptional(p,'g',0.20);
            addOptional(p,'geodata',defval)
            addOptional(p,'lmsl',defval)
            addOptional(p,'dt',-1);
            addOptional(p,'fl',defval);
            addOptional(p,'Channels',defval);
            addOptional(p,'h0',defval);

            % parse the inputs
            parse(p,varargin{:});
            % store the inputs as a struct
            inp = p.Results;
            % get the fieldnames of the edge functions
            inp = orderfields(inp,{'max_el','min_el_ch','AngOfRe',...
                'geodata','lmsl','Channels',...
                'dis','fs','fl','g','max_el_ns',...
                'wl','slp','ch','dt','h0'});
            fields = fieldnames(inp);
            % loop through and determine which args were passed.
            % also, assign reasonable default values if some options were

            % not assigned.
            for i = 1 : numel(fields)
                type = fields{i};
                switch type
                    % parse aux options first
                    case('max_el')
                        obj.max_el = inp.(fields{i});
                        if ~isempty(obj.max_el)
                            obj.max_el = inp.(fields{i});
                        end
                    case('max_el_ns')
                        obj.max_el_ns= inp.(fields{i});
                        if obj.max_el_ns ~=0
                            obj.max_el_ns = inp.(fields{i});
                        end
                        % obj.max_el_ns = obj.max_el_ns/111e3;
                    case('g')
                        obj.g= inp.(fields{i});
                        if obj.g ~=0
                            obj.g = inp.(fields{i});
                        end
                    case('fl')
                        obj.fl = inp.(fields{i});
                        if obj.fl ~=0
                            obj.fl = inp.(fields{i});
                        end
                    case('geodata')
                        if isa(inp.(fields{i}),'geodata')
                            feat = inp.(fields{i});
                        end
                    case('lmsl')
                        if isa(inp.(fields{i}),'geodata')
                            obj.lmsl = inp.(fields{i});
                        end
                    case('dt')
                        obj.dt=inp.(fields{i});
                        if obj.dt >= 0
                            obj.dt = inp.(fields{i});
                        end
                    case('h0')
                        obj.h0=inp.(fields{i});
                        if obj.h0 ~= 0
                            obj.h0 = inp.(fields{i});
                        else
                            obj.h0 = feat.h0;
                        end
                    case('min_el_ch')
                        obj.min_el_ch=inp.(fields{i});
                        if obj.min_el_ch ~= 100
                            obj.min_el_ch = inp.(fields{i});
                        end
                    case('AngOfRe')
                        obj.AngOfRe=inp.(fields{i});
                        if obj.AngOfRe ==60
                            obj.AngOfRe = inp.(fields{i});
                        end
                    case('Channels')
                        obj.Channels=inp.(fields{i});
                        if ~isempty(obj.Channels) ~=0
                            obj.Channels = inp.(fields{i});
                        end
                end
            end

            % kjr april 28, 2018-form mesh size grid on-the-fly
            obj.fd       = @dpoly;
            obj.x0y0     = feat.x0y0 + sqrt(eps);
            obj.gridspace = obj.h0/111e3;
            obj.nx       = ceil(abs(feat.x0y0(1)-feat.bbox(1,2))/obj.gridspace);
            obj.ny       = ceil(abs(feat.x0y0(2)-feat.bbox(2,2))/obj.gridspace);
            obj.bbox     = feat.bbox;
            obj.boubox   = feat.boubox;

            % WJP: Do Mercator projection for calculating
            % distances from shoreline
            if feat.bbox(1,2) > 180
                m_proj('mercator','lat', [-88 90], 'long',[0 360]);
            else
                m_proj('mercator','lat', [-88 90],'long',[-180 180]);
            end
            % now turn on the edge functions
            for i = 1 : numel(fields)
                type = fields{i};
                switch type
                    case('dis')
                        obj.dis = inp.(fields{i});
                        if obj.dis ~= 0
                            disp('Building distance function...');
                            obj = distfx(obj,feat);
                            obj.used{end+1} = 'dis';
                        end
                    case('fs')
                        obj.fs  = inp.(fields{i});
                        if obj.fs ~= 0
                            disp('Building feature size function...');
                            try
                                obj = featfx(obj,feat);
                                obj.used{end+1} = 'fs';
                            catch
                                disp('man, no medial points!')
                            end
                        end
                    case('wl')
                        obj.wl  = inp.(fields{i});
                        if obj.wl(1)~=0 && ~isempty(feat.Fb)
                            disp('Building wavelength function...');
                            obj = wlfx(obj,feat);
                            obj.used{end+1} = 'wl';
                        end
                    case('slp')
                        obj.slp  = inp.(fields{i});
                        if obj.slp(1) ~= 0 && ~isempty(feat.Fb)
                            disp('Building slope function...');
                            obj = slpfx(obj,feat);
                            obj.used{end+1} = 'slp';
                        end
                    case('ch')
                        obj.ch  = inp.(fields{i});
                        if obj.ch ~= 0
                            disp('Building channel function...');
                            obj = chfx(obj,feat);
                            obj.used{end+1} = 'ch';
                        end
                    case{'g','geodata','lmsl','max_el','min_el_ch',...
                            'Channels','max_el_ns','h0','dt','fl','AngOfRe'}
                        % dummy to avoid warning
                    otherwise
                        warning('Unexpected edge function name/value pairs.')
                end
                if i==numel(fields)
                    obj = finalize(obj,feat);
                    disp('Finalized edge function!');
                end
            end
        end

        %% Traditional distance function, linear distance from coastline.
        function obj = distfx(obj,feat)
            [d,obj] = get_dis(obj,feat);
            obj.boudist = d;
            obj.hhd = obj.h0 + obj.dis*abs(d);
        end
        %% Feature size function, approximates width of nearshore geo.
        function obj = featfx(obj,feat)
            if isempty(feat.mainland) && isempty(feat.inner)
                % No medial points so use distance function using grade
                warning('No mainland or inner')
                xg = CreateStructGrid(obj);
                obj.fsd = xg*NaN;
                clearvars xg
                return
            end
            % Make sure we don't create a singularity on the coast in the
            % distance function!
            [d,obj] = get_dis(obj,feat);
            obj.boudist = d ;

            [xg,yg] = CreateStructGrid(obj);
            % use a harvestine assumption
            dx = obj.h0*cosd(yg(1,:)); % for gradient function
            dy = obj.h0;               % for gradient function
            % Calculate the gradient of the distance function.
            [ddy,ddx] = EarthGradient(d,dy,dx);
            d_fs = sqrt(ddx.^2 + ddy.^2);
            clearvars ddx ddy
            % WJP: This fix is to put a medial point in narrow channels
            rf = []; cf = [];
            for ii = 2:size(d,1)-1
                for jj = 2:size(d,2)-1
                    if d(ii,jj) < 0
                        if (d(ii-1,jj) >= 0 && ...
                                d(ii+1,jj) >= 0) || ...
                                (d(ii,jj-1) >= 0 && ...
                                d(ii,jj+1) >= 0)
                            rf(end+1) = ii;
                            cf(end+1) = jj;
                        end
                    end
                end
            end
            % Find singularties in the distance function that are
            % within the poly to get the medial axis.
            % Lets only create medial points in places that are
            % sufficiently far away from the coastline to capture the
            % feature and thus aren't spurious.
            d_fs = reshape(d_fs,[],1); d = reshape(d,[],1);
            lg = d_fs < 0.90  & d < -0.5*obj.h0 ;
            [r,c] = ind2sub([obj.nx obj.ny],find(lg));
            % create the coordinates using x0y0 + r*h0
            r = [r; rf']; c = [c; cf']; %WJP: add on "fixed points"
            x_kp = obj.x0y0(1) + (r-1)*obj.gridspace;
            y_kp = obj.x0y0(2) + (c-1)*obj.gridspace;
            clearvars lg r c cf rf
            % Ensure there are enough medial points nearby
            % to avoid surpurious medial points.
            % WJP: We want a line of points larger than around 7. This
            % corresponds to having three points back or forward up the
            % line. Let's check for those three closest points and ensure
            % they are within about co*h0 distance from each other.
            % co is the cutoff distance = 2*sqrt(2) (two diagonal dist)
            co = 2*sqrt(2);
            if length(x_kp) > 12
                [~, dmed] = WrapperForKsearch([x_kp,y_kp],[x_kp,y_kp],4);
                prune = dmed(:,2) > co*obj.h0 | dmed(:,3) > 2*co*obj.h0 | ...
                    dmed(:,4) > 3*co*obj.h0;
                x_kp( prune ) = [];
                y_kp( prune ) = [];
            end

            % Now get the feature size along the coastline
            if length(x_kp) > 12
                % Use KD-tree
                dPOS = 0*d;
                noblks = ceil(length(dPOS)*2*8*1e-9);
                blklen = floor(length(dPOS)/noblks);
                ns = 1;
                for blks = 1:noblks
                    if blks == noblks
                        ne = length(dPOS);
                    else
                        ne = ns + blklen - 1;
                    end
                    [~,dPOS(ns:ne)] = WrapperForKsearch([x_kp,y_kp],...
                        [xg(ns:ne)',yg(ns:ne)'],1);
                    ns = ne + 1;
                end
            else
                % No medial points so use distance function using grade
                warning('No medial points, resorting to distance function')
                d = reshape(d,obj.nx,[]);
                obj.fsd = obj.h0 + obj.g*abs(d);
                clear x_kp y_kp d d_fs dPOS xg yg
                return
            end
            % reshape back
            d = reshape(d,obj.nx,[]); dPOS = reshape(dPOS,obj.nx,[]);
            % Feature_size is distance from medial axis plus distance to
            % coastline. min_el is then feature_size*2/R where R is
            % number of elements to model the feature
            % WJP: d needs to be the absolute value because when we
            % include the floodplain since dPOS is not signed but d is,
            % they will cancel out producing really fine resolution
            W = dPOS+abs(d);
            if obj.fs < 0
                % automatic feature size
                fs_auto = min(-obj.fs,ceil(W/obj.h0));
                obj.fsd = 2*W./fs_auto;
            else
                obj.fsd = 2*W/obj.fs;
            end
            clear x_kp y_kp d d_fs dPOS xg yg
        end

        function [d,obj] = get_dis(obj,feat)
            % Function used by distfx and featfx to return distance
            % and make the Fdis interpolant
            d = feval(obj.fd,obj,feat);
            d = reshape(d,obj.nx,[]);
        end

        %% Wavelength edge function.
        function obj = wlfx(obj,feat)

            % interpolate DEM's bathy linearly onto our edgefunction grid.
            [xg,yg] = CreateStructGrid(obj);
            tmpz    = feat.Fb(xg,yg);
            if ~isempty(feat.Fb2)
               tmpz2 = feat.Fb2(xg,yg);
               tmpz(isnan(tmpz)) = tmpz2(isnan(tmpz));
               clear tmpz2
            end
            % Initialise wld
            obj.wld = NaN([obj.nx,obj.ny]);
            grav    = 9.807;
            period  = 12.42*3600; % M2 period in seconds

            for param = obj.wl'
                if numel(param)==1
                    % no bounds specified.
                    wlp = param(1);
                    z_min = -inf;
                    z_max = +inf;
                else
                    wlp = param(1);
                    z_min = param(2);
                    z_max = param(3);
                end
                % limit min. depth to 1 m
                twld = period*sqrt(grav*max(abs(tmpz),1))/wlp;
                limidx = (tmpz >= z_min & tmpz < z_max);
                % Set wld with depth mask applied
                obj.wld(limidx) = twld(limidx);
                clearvars twld
            end
            clearvars tmpz xg yg;
        end

      %% Topographic length scale/slope edge function.
        function obj = slpfx(obj,feat)

            [xg,yg] = CreateStructGrid(obj);

            tmpz    = feat.Fb(xg,yg);
            if ~isempty(feat.Fb2)
               tmpz2 = feat.Fb2(xg,yg);
               tmpz(isnan(tmpz)) = tmpz2(isnan(tmpz));
               clear tmpz2
            end
            tmpz(tmpz > 50) = 50; % ensure no larger than 50 m above land
            % use a Harvestine assumption
            dx = obj.h0*cosd(min(yg(1,:),85)); % for gradient function
            dy = obj.h0;               % for gradient function
            % lets filter the bathy to get only relevant features
            % loop over each set of bandpass filter lengths
            tmpz_f = zeros(size(tmpz));
            if obj.fl(1) < 0 && obj.fl(1) ~= -999
                disp('INFO: Rossby radius of deformation filter on.') ;
                rbfilt = abs(obj.fl(1));
                barot  = 1;
                if length(obj.fl) > 1 && obj.fl(2) < 0
                    disp('INFO: Using 1st-mode baroclinic Rossby radius.');
                    barot = 0;
                else
                    disp('INFO: Using barotropic Rossby radius.') ;
                end
                obj.fl = [];
                filtit = 1;
            elseif obj.fl(1) == 0
                disp('INFO: Slope filter is off.');
                obj.fl = [];
                tmpz_f = tmpz;
                filtit = 0 ;
            elseif obj.fl(1) == -999
                disp('INFO: Slope filter is LEGACY.');
                obj.fl = [];
                tmpz_f = tmpz;
                filtit = -999;
            else
                filtit = 0;
                for lambda = obj.fl'
                    if length(lambda) == 1 || lambda(2) == 0
                        % do a low pass filter
                        tmpz_ft  = filt2(tmpz,dy,lambda(1),'lp') ;
                    elseif all(lambda ~= 0)
                        % do a bandpass filter
                        tmpz_ft  = filt2(tmpz,dy,lambda,'bp') ;
                    else
                        % highpass filter not recommended
                        warning(['Highpass filter on bathymetry in slope' ...
                            'edgelength function is not recommended'])
                        tmpz_ft  = filt2(tmpz,dy,lambda(2),'hp') ;
                    end
                    tmpz_f = tmpz_f + tmpz_ft;
                end
            end

            % Rossby radius of deformation filter
            if filtit == 1
                tic
                bs = NaN([obj.nx,obj.ny]);
                % break into 10 deg latitude chunks, or less if higher res
                div = ceil(min(1e7/obj.nx,10*obj.ny/(max(yg(:))-min(yg(:)))));
                grav = 9.807;
                nb = ceil(obj.ny/div);
                n2s = 1;
                for jj = 1:nb
                    n2e = min(obj.ny,n2s + div - 1);
                    % Rossby radius of deformation filter
                    % See Shelton, D. B., et al. (1998): Geographical variability of the first-baroclinic Rossby radius of deformation. J. Phys. Oceanogr., 28, 433-460.
                    ygg = yg(:,n2s:n2e);
                    dxx = mean(dx(n2s:n2e));
                    f = 2*7.29e-5*abs(sind(ygg));
                    if barot
                        % barotropic case
                        c = sqrt(grav*max(1,-tmpz(:,n2s:n2e)));
                    else
                        % baroclinic case (estimate Nm to be 2.5e-3)
                        Nm = 2.5e-3;
                        c = Nm*max(1,-tmpz(:,n2s:n2e))/pi;
                    end
                    rosb = c./f;
                    clear f;
                    % update for equatorial regions
                    I = abs(ygg) < 5; Re = 6371e3;
                    twobeta = 4*7.29e-5*cosd(ygg(I))/Re;
                    rosb(I) = sqrt(c(I)./twobeta);
                    clear twobeta
                    % limit rossby radius to 10,000 km for practical purposes
                    rosb(rosb > 1e7) = 1e7;
                    % Keep lengthscales rbfilt * barotropic
                    % radius of deformation
                    rosb = min(10,max(0,floor(log2(rosb/dy/rbfilt))));
                    edges = double(unique(rosb(:)));
                    bst = rosb*0;
                    for i = 1:length(edges)
                        if edges(i) > 0
                            mult = 2^edges(i);
                            xl = 1; xu = obj.nx;
                            if (max(xg(:)) > 179 && min(xg(:)) < -179) || ...
                                    (max(xg(:)) > 359 && min(xg(:)) < 1)
                                % wraps around
                                xr = [obj.nx-mult/2+1:1:obj.nx xl:xu 1:mult/2];
                            else
                                xr = xl:xu;
                            end
                            yl = max(1,n2s-mult/2);
                            yu = min(obj.ny,n2e+mult/2);
                            if max(yg(:)) > 89 && yu == obj.ny
                                % create mirror around pole
                                yr = [yl:yu yu-1:-1:2*obj.ny-n2e-mult/2];
                            else
                                yr = yl:yu;
                            end
                            if mult == 2
                                tmpz_ft = filt2(tmpz(xr,yr),...
                                           [dxx dy],dy*2.01,'lp');
                            else
                                tmpz_ft = filt2(tmpz(xr,yr),...
                                           [dxx dy],dy*mult,'lp');
                            end
                            % delete the padded region
                            tmpz_ft(1:find(xr == 1,1,'first')-1,:) = [];
                            tmpz_ft(obj.nx+1:end,:) = [];
                            tmpz_ft(:,1:find(yr == n2s,1,'first')-1) = [];
                            tmpz_ft(:,n2e-n2s+2:end) = [];
                        else
                            tmpz_ft = tmpz(:,n2s:n2e);
                        end
                        [by,bx] = EarthGradient(tmpz_ft,dy,dx(n2s:n2e)); % get slope in x and y directions
                        tempbs  = sqrt(bx.^2 + by.^2);          % get overall slope
                        bst(rosb == edges(i)) = tempbs(rosb == edges(i));
                    end
                    bs(:,n2s:n2e) = bst;
                    n2s = n2e + 1;
                end
                toc
                % legacy filter
            elseif filtit==-999
                bs = NaN([obj.nx,obj.ny]);
                % Rossby radius of deformation filter
                f = 2*7.29e-5*abs(sind(yg));
                % limit to 1000 km
                rosb = min(1000e3,sqrt(9.81*abs(tmpz))./f);
                % autmatically divide into discrete bins
                [~,edges] = histcounts(rosb);
                tmpz_ft  = tmpz; dyb = dy;
                % get slope from filtered bathy for the segment only
                [by,bx] = EarthGradient(tmpz_ft,dy,dx); % get slope in x and y directions
                tempbs  = sqrt(bx.^2 + by.^2); % get overall slope
                for i = 1:length(edges)-1
                    sel = rosb >= edges(i) & rosb <= edges(i+1);
                    rosbylb = mean(edges(i:i+1));
                    if rosbylb > 2*dyb
                        disp(['i = ',num2str(i), ' rl/dx = ',num2str(rosbylb/dyb)])
                        tmpz_ft  = filt2(tmpz_ft,dyb,rosbylb,'lp');
                        dyb = rosbylb;
                        % get slope from filtered bathy for the segment only
                        [by,bx] = EarthGradient(tmpz_ft,dy,dx); % get slope in x and y directions
                        tempbs  = sqrt(bx.^2 + by.^2); % get overall slope
                    else
                        % otherwise just use the same tempbs from before
                    end
                    % put in the full one
                    bs(sel) = tempbs(sel);
                end
            else
                % get slope from (possibly filtered) bathy
                [by,bx] = EarthGradient(tmpz_f,dy,dx); % get slope in x and y directions
                bs      = sqrt(bx.^2 + by.^2); % get overall slope
            end
            clear bx by tmpz_f tmpz_ft

            % Allow user to specify depth ranges for slope parameter.
            obj.slpd = NaN([obj.nx,obj.ny]);
            for param = obj.slp'
                if numel(param)==1
                    % no bounds specified. valid in this range.
                    slpp = param(1);
                    z_min = -inf;
                    z_max = +inf;
                else
                    slpp = param(1);
                    z_min = param(2);
                    z_max = param(3);
                end
                % Calculating the slope function
                dp = max(1,-tmpz);
                tslpd = (2*pi/slpp)*dp./(bs+eps);
                % apply slope with mask
                limidx = (tmpz >= z_min & tmpz < z_max);
                obj.slpd(limidx) = tslpd(limidx);
                clearvars tslpd
            end
            clearvars tmpz xg yg
        end

        %% Channel edgefunction
        function obj = chfx(obj,feat)

            ang_of_reslope=obj.AngOfRe ;      % Estimate width of channel using tangent of this angle times depth.

            % STEP 1: Calculate the width of each channel using a v-shape approx to
            % channel's cross sectional area.
            for jj = 1:length(obj.Channels)
                if (isempty(obj.Channels{jj})); continue ;end
                pts{jj} = obj.Channels{jj};
                dp{jj} = feat.Fb(pts{jj});                               % depth at x,y channel locations
                radii{jj}=(tand(ang_of_reslope)*max(1,-dp{jj}));         % estimate of channel's width in degrees at x,y locations ASSUMING angle of reslope
                tempbb{jj} = feat.boubox;
            end

            [xg,yg] = CreateStructGrid(obj);

            % STEP 2: For each channel point, set the resolution around a
            % neighborhood of points as |h|/ch where ch is non-dim # between 0.5-1.5
            obj.chd = NaN([obj.nx,obj.ny]);
            % prune the potentially large channel network to only ones
            % partially enclosed in boubox
            isin = cellfun(@inpolysum,pts,tempbb);
            pts(isin==0) = []; radii(isin==0)= [];

            %figure; plot(xg,yg,'k.');
            tic
            tidx = [];
            for jj =1:length(pts)                                           % For each channel jj
                sel = pts{jj};
                in  = inpoly(sel,feat.boubox(1:end-1,:));
                sel(~in,:) = [];
                for jjj = 1 : size(sel,1)                                   % For each channel point jjj on channel jj.
                    % will the stencil be too large (nidx^2)?
                    nidx = ceil(radii{jj}(jjj)/obj.h0);                       % stencil of points around channel point.
                    % find linear index of channel point in 2-d matrix.
                    [lidx] = FindLinearIdx(sel(jjj,1),sel(jjj,2),xg,yg);
                    % convert to r,c or grid indices.
                    [r,c]     = ind2sub(size(xg),lidx);
                    [cid,rid] = ndgrid(c-nidx:c+nidx,r-nidx:r+nidx);    % grid of nearby points
                    % ensure that all these points are within the domain.
                    rid = min(max(rid(:),1),size(xg,1));
                    cid = min(max(cid(:),1),size(xg,2));
                    % convert back to linear indices.
                    temp = sub2ind(size(xg),rid,cid);
                    tidx = [tidx; temp];
                end
            end
            toc
            tmpz = feat.Fb(xg(tidx),yg(tidx));
            if ~isempty(feat.Fb2)
               tmpz2 = feat.Fb2(xg(tidx),yg(tidx));
               tmpz(isnan(tmpz)) = tmpz2(isnan(tmpz));
               clear tmpz2
            end
            dp = max(1,-tmpz);
            obj.chd(tidx) = dp/obj.ch;
            obj.chd(obj.chd < obj.min_el_ch) = obj.min_el_ch;
            clearvars Fb pts dp radii tempbb xg yg
        end

        %% General function to plot
        function plot(obj,type)
            % form grid here.
            % working out plottable size
            [xgrid,ygrid] = CreateStructGrid(obj);

            if nargin == 2
                switch type
                    case('dis')
                        val = obj.hhd;
                        tt = 'Distance EdgeLength Function';
                    case('fs')
                        val = obj.fsd;
                        tt = 'Feature Size EdgeLength Function';
                    case('wl')
                        val = obj.wld;
                        tt = 'Wavelength EdgeLength Function';
                    case('ch')
                        val = obj.chd;
                        tt = 'Channel EdgeLength Function';
                    case('slp')
                        val = obj.slpd;
                        tt = 'Slope EdgeLength Function';
                    otherwise
                        warning('Unexpected plot type. No plot created.')
                end
            else
                val = obj.F.Values;
                tt = 'Total EdgeLength Function';
            end

            % working out stride
            mem = inf; stride = obj.gridspace;
            while mem > 1
                xx = obj.x0y0(1):stride:obj.bbox(1,2);
                yy = obj.x0y0(2):stride:obj.bbox(2,2);
                xs = whos('xx'); ys = whos('yy');
                mem = xs.bytes*ys.bytes/1e9;
                stride = stride*2;
            end
            stride = ceil(stride/obj.gridspace);
            I = [1:stride:size(xgrid,1)];
            J = [1:stride:size(xgrid,2)];

            figure;
            m_proj('merc',...
                'long',[obj.bbox(1,1) obj.bbox(1,2)],...
                'lat',[obj.bbox(2,1) obj.bbox(2,2)])
            hold on; m_fastscatter(xgrid(I,J),ygrid(I,J),val(I,J));
            cb = colorbar; ylabel(cb,'edgelength in meters');
            colormap(lansey)
            m_grid() %'xtick',10,'tickdir','out',...
            % 'yaxislocation','left','fontsize',7);
            title(tt);
        end

        %% Finalize edge function
        % Add grading, cfl limiting and bound enforcement.
        function obj = finalize(obj,feat)
            % package the edge functions into a known order.
            counter = 0;
            hh = zeros([obj.nx,obj.ny]);
            for i = 1 : numel(obj.used)
                type = obj.used{i};
                switch type
                    case('dis')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.hhd;
                        obj.hhd = single(obj.hhd);
                    case('fs')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.fsd;
                        obj.fsd = single(obj.fsd);
                    case('wl')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.wld;
                        obj.wld = single(obj.wld);
                    case('slp')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.slpd;
                        obj.slpd = single(obj.slpd);
                    case('ch')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.chd;
                        obj.ch = single(obj.chd);
                    otherwise
                        error('FATAL:  Could not finalize edge function');
                end
            end

            [hh_m] = min(hh,[],3);
            clearvars hh

            [xg,yg] = CreateStructGrid(obj);

            % Before grading, ensure edgefx reflects the spacing in
            % proximity to constraints.
            if ~isempty(feat.weirs)
                % form temporary mesh size function interpolat
                % if spacing is negative -999 then it will use the mesh
                % size function to determine the spacing.
                %Ftmp = griddedInterpolant(xg,yg,hh_m,'linear','nearest');

                for i =1:length(feat.weirs)
                    if iscell(feat.weirs)
                        % get spacing in meters along the face of the weir
                        weir_spacing = feat.weirs{i}(1,4);
                        % get crestline points in wgs84
                        txy = feat.weirs{i}(:,1:2) ;
                    else
                        % get spacing in meters along the face of the weir
                        weir_spacing = feat.weirs(i).min_ele;
                        % get crestline points in wgs84
                        txy = [feat.weirs(i).X feat.weirs(i).Y];
                    end
                    xy = [];
                    [xy(:,2),xy(:,1)] = my_interpm(txy(:,2),txy(:,1),...
                        (obj.h0/2)/111e3);
                    % edit the mesh size function in proximity to the weir
                    for j = 1 : length(xy)
                        nidx = 10 ;
                        % return the indices into the mesh size function
                        [lidx] = FindLinearIdx(xy(j,1),xy(j,2),xg,yg);
                        [r,c]  = ind2sub(size(xg),lidx);
                        [cid,rid]=ndgrid((c-nidx:c+nidx)',(r-nidx:r+nidx)');    % grid of nearby points
                        rid = [rid(:);r]; cid=[cid(:);c];
                        hh_m(rid,cid) = weir_spacing ;
                    end
                end
            end

            % enforce all mesh resolution bounds,grade and enforce the CFL in planar metres
            if ~isinf(obj.max_el_ns) && ~isempty(obj.boudist)
                nearshore = abs(obj.boudist) < 2/3*obj.h0;
                hh_m(nearshore & hh_m > obj.max_el_ns) = obj.max_el_ns;
            end
            hh_m(hh_m < obj.h0 ) = obj.h0;

            % Compute tmpz if necessary
            if length(obj.max_el) > 1 || length(obj.g) > 1 || obj.dt(1) >= 0
                tmpz    = feat.Fb(xg,yg);
                if ~isempty(feat.Fb2)
                   tmpz2 = feat.Fb2(xg,yg);
                   tmpz(isnan(tmpz)) = tmpz2(isnan(tmpz));
                   clear tmpz2
                end
            end

            for param = obj.max_el'
                if numel(param)==1 && param~=0
                    mx   = obj.max_el(1);
                    limidx = hh_m > mx | isnan(hh_m);

                    hh_m( limidx ) = mx;
                else
                    mx  = param(1);
                    z_min = param(2);
                    z_max = param(3);

                    limidx = (tmpz >= z_min & tmpz < z_max) & ...
                             (hh_m > mx | isnan(hh_m));

                    hh_m( limidx ) = mx;
                end
            end

            % Make sure this is called before releasing memory...
            if obj.dt(1) == 0
                % Find min allowable dt based on dis or fs function
                if any(~cellfun('isempty',strfind(obj.used,'dis')))
                    hh_d = obj.hhd;
                elseif any(~cellfun('isempty',strfind(obj.used,'fs')))
                    hh_d = obj.fsd;
                else
                    error(['FATAL: cannot use automatic timestep ' ...
                           'limiter without specifying dis or fs option']);
                end
            end

            obj = release_memory(obj) ;

            disp('Relaxing the mesh size gradient');
            if all(abs(obj.bbox(1,:)) == 180)
                % for global mesh make it cyclical
                hh_m = [hh_m(end,:); hh_m; hh_m(1,:)];
            end
            hfun = reshape(hh_m',[numel(hh_m),1]);

            dx = obj.h0*cosd(min(yg(1,:),85)); % for gradient function
            dy = obj.h0;                       % for gradient function

            % make g a function of space
            dmy     = xg*0 ;
            for param = obj.g'
                if numel(param)==1 && param~=0
                    lim   = obj.g(1);
                    dmy  = dmy + lim ;
                else
                    lim  = param(1);
                    z_min  = param(2);
                    z_max  = param(3);

                    limidx = (tmpz >= z_min & tmpz < z_max) ;

                    dmy( limidx ) = lim;
                end
            end
            if all(abs(obj.bbox(1,:)) == 180)
                % for global mesh make it cyclical
                dmy = [dmy(end,:); dmy; dmy(1,:)];
            end
            fdfdx = reshape(dmy',[numel(dmy),1]);
            clearvars dmy;

            [hfun,flag] = limgradStruct(obj.ny,dx,dy,hfun,...
                fdfdx,sqrt(length(hfun)));
            if flag == 1
                disp('Gradient relaxing converged!');
            else
                error(['FATAL: Gradient relaxing did not converge, '
                    'please check your edge functions']);
            end
            % reshape it back
            hh_m = reshape(hfun,obj.ny,[])';
            clearvars hfun fdfdx
            if all(abs(obj.bbox(1,:)) == 180)
                % for global mesh make it cyclical
                hh_m = hh_m(2:end-1,:);
                hh_m(end,:) = hh_m(1,:);
            end

            % Limit CFL if dt >= 0, dt = 0 finds dt automatically.
            if obj.dt(1) >= 0
                if isempty(feat.Fb)
                    error('No DEM supplied Can''t CFL limit.')
                end
                % Estimate wavespeed in ocean (second term represents orbital
                % velocity at 0 degree phase for 1-m amp. wave).
                grav = 9.807;
                % Limit the minimum depth to 1 m (ignore overland)
                tmpz(tmpz > - 1) = -1;
                u = sqrt(grav*abs(tmpz)) + sqrt(grav./abs(tmpz));

                % Desired timestep to bound edgefx
                desDt = obj.dt(1);
                if isnan(desDt)
                    disp('No timestep enforcement due to no distance function')
                end

                % Determine Courant min. and max. bounds
                if length(obj.dt) < 2
                    % default behavior if no bounds are passed
                    % for explicit type schemes we ensure the maxCr is
                    % bounded
                    minCr = eps; % Cannot be zero!!!
                    maxCr = 0.5;
                elseif length(obj.dt) == 2
                    if obj.dt(1)==0.0
                        error('dt = 0 is only intended for restricting the upper bound of Cr')
                    end
                    % minimum Courant number bound
                    minCr = obj.dt(2);
                    maxCr = inf;
                elseif length(obj.dt) == 3
                    % minimum and maximum Courant bound
                    minCr = obj.dt(2);
                    maxCr = obj.dt(3);
                else
                    error('Length of dt name-value pair should be <=3')
                end

                if minCr > maxCr
                    error('MinCr is > MaxCr, please switch order in dt name value call')
                end

                % Automatic timestep selection option (for ADCIRC models)
                if desDt == 0
                    hh_d(hh_d < obj.h0) = obj.h0;
                    obj.dt = min(min(maxCr*hh_d./u));
                    desDt  = obj.dt;
                end

                % Enforcement of Courant bounds in mesh size function
                fprintf(1, [ ...
                    'Enforcing timestep of ',num2str(desDt),' seconds ', ...
                    'with Courant number bounds of ',num2str(minCr),' to ',num2str(maxCr),'\n', ...
                    ] ) ;

                Cr = (desDt*u)./hh_m; % Courant number for given desDt for mesh size variations

                % resolution that meets max. Cr. criteria for desDt
                dxn_min = u*desDt/maxCr;
                % resolution that meets min. Cr. criteria for desDt
                dxn_max = u*desDt/minCr;

                % resolve min. Cr violations
                hh_m( Cr <= minCr) = dxn_max( Cr <= minCr); %--in planar metres

                % resolve max. Cr violations
                hh_m( Cr > maxCr) = dxn_min( Cr > maxCr);   %--in planar metres

                clear dxn_min dxn_max Cr
                clear u hh_d tmpz
            end

            obj.F = griddedInterpolant(xg,yg,hh_m,'linear','nearest');

            clearvars xg yg
        end

        function [xg,yg] = CreateStructGrid(obj)
            [xg,yg] = ndgrid(obj.x0y0(1) + (0:obj.nx-1)'*obj.gridspace, ...
                obj.x0y0(2) + (0:obj.ny-1)'*obj.gridspace);
        end

        function obj = release_memory(obj)
            % releases heavy components from data structures
            if isa(obj,'edgefx')
                disp('--------------------------------------------------');
                disp('Releasing individual edge functions from memory...');
                disp('--------------------------------------------------');
                if ~isempty(obj.fsd)
                    obj.fsd = [];
                end

                if ~isempty(obj.wld)
                    obj.wld = [];
                end

                if ~isempty(obj.slpd)
                    obj.slpd = [];
                end

                if ~isempty(obj.chd)
                    obj.chd = [];
                end

                if ~isempty(obj.ch)
                    obj.ch = [];
                end

                if ~isempty(obj.hhd)
                    obj.hhd = [];
                end

                if ~isempty(obj.boudist)
                    obj.boudist = [];
                end

                if ~isempty(obj.Channels)
                    obj.Channels = [];
                end

                if ~isempty(obj.ch)
                   obj.ch = [];
                end

                if ~isempty(obj.boudist)
                   obj.boudist = [];
                end
            end
        end

    end

end


function calculateQualityMeasures(inputDir, datasetConfig, resultDir, binningFactors, numberOfFrames, numberOfFrames_val, sliding_val, sr_method_val, binning_val, scenes_val, compressions_val, measure_val, border)

    % Get configuration for this dataset.
    load([inputDir,'/', datasetConfig, '.mat']);

    % Iterate over compression settings.
    for compressIdx = compressions_val

        if isnan(compressIdx)
            compress_folder = 'Uncoded';
        else
            compress_affix = ['QP', num2str(compressIdx)];
            compress_folder = ['H265', compress_affix];
        end

        % Iterate over all scenes.
        for datasetIdx = scenes_val
            % Iterate over the different binning factors.
            for binningFactorIdx = binning_val
                % Iterate over different SR methods.
                for sr_method = sr_method_val

                    if sr_method == 0
                        continue;
                    end

                    % Iterate over different sequence lengths.
                    for numberOfFramesIdx = numberOfFrames_val
                        % Iterate over different sliding windows.
                        for slidingIdx = sliding_val

                            gtFilename = [resultDir, filesep, evalData.scenes{datasetIdx}, filesep, 'Uncoded', filesep, 'mat', filesep, evalData.motionTypes{datasetIdx}, '_bin', num2str(binningFactors(binningFactorIdx)), '_sr0', '_f', num2str(numberOfFrames(numberOfFramesIdx,binningFactorIdx)), '_win', num2str(slidingIdx,'%02d'), '.mat'];
                            srFilename = [resultDir ,filesep, evalData.scenes{datasetIdx}, filesep, compress_folder,filesep, 'mat', filesep, evalData.motionTypes{datasetIdx}, '_bin', num2str(binningFactors(binningFactorIdx)), '_sr', num2str(sr_method), '_f', num2str(numberOfFrames(numberOfFramesIdx,binningFactorIdx)), '_win', num2str(slidingIdx,'%02d'), '.mat'];
                            if ~exist(gtFilename, 'file') || ~exist(srFilename, 'file')
                                fprintf('Ground truth and result not available (%s, %s)\n', gtFilename, srFilename);
                            else
                                % Calculate the different quality measures.
                                groundTruth = [];
                                SR = [];
                                SR_aligned = [];
                                qualityMeasuresVec = qualityMeasures;
                                for measureIdx = measure_val
                                    % Prepare result directory for current quality metric
                                    if ~exist([resultDir, filesep,evalData.scenes{datasetIdx}, filesep,compress_folder, filesep,'quality_qm', num2str(measureIdx)], 'dir')
                                        mkdir([resultDir, filesep,evalData.scenes{datasetIdx}, filesep,compress_folder], ['quality_qm', num2str(measureIdx)]);
                                    end

                                    if exist([resultDir, filesep, evalData.scenes{datasetIdx}, filesep, compress_folder, filesep, 'quality_qm', num2str(measureIdx), filesep,evalData.motionTypes{datasetIdx}, '_bin', num2str(binningFactors(binningFactorIdx)), '_sr',num2str(sr_method), '_f', num2str(numberOfFrames(numberOfFramesIdx,binningFactorIdx)), '_win', num2str(slidingIdx,'%02d'), '_qm', num2str(measureIdx), '.mat'],'file')
                                        disp('Quality metric already calculated!');
                                    else
                                        if isempty(groundTruth)
                                            % Load the ground truth.
                                            load(gtFilename);
                                            groundTruth = srImages.groundTruth;
                                        end

                                        if isempty(SR)
                                            % Load the SR image.
                                            load(srFilename);
                                            srMethodName = fieldnames(srImages);
                                            srMethodName = srMethodName{1};
                                            SR = getfield(srImages, srMethodName);
                                        end

                                        % Align SR image to the ground truth if
                                        % desired (depends on the SR method).
                                        if isempty(SR_aligned)
                                            SR_aligned = alignToGroundTruth(SR, sr_method, binningFactors(binningFactorIdx));
                                        end

                                        % Calculate the quality measure.
                                        qualityMeasure = calculateQualityMeasureForImage(SR_aligned, groundTruth, qualityMeasuresVec{measureIdx}, border);
                                        save([resultDir, filesep, evalData.scenes{datasetIdx}, filesep, compress_folder, filesep, 'quality_qm', num2str(measureIdx), filesep, evalData.motionTypes{datasetIdx}, '_bin', num2str(binningFactors(binningFactorIdx)), '_sr',num2str(sr_method), '_f', num2str(numberOfFrames(numberOfFramesIdx,binningFactorIdx)), '_win', num2str(slidingIdx,'%02d'), '_qm', num2str(measureIdx), '.mat'], 'qualityMeasure');
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end

end

function sr = alignToGroundTruth(sr, srMethodIdx, binningFactor)

    srMethods = SRMethods;
    if ~ismatrix(sr)
        sr = sr(:,:,1);
    end
    sr = double(sr);

    if strcmp(srMethods(srMethodIdx).name, 'vsrnet')
        % Scale intensities to [0, 1].
        sr = sr / 255;
    end

    if strcmp(srMethods(srMethodIdx).pixelSampling, 'topLeft')
        % Geometric alignment of SR image to ground truth for algorithms
        % that use the top left corner for pixel sampling.
        [x_mesh, y_mesh] = meshgrid(1:size(sr, 2), 1:size(sr,1));
        x_mesh_shift = x_mesh - (binningFactor - 1) / 2;
        y_mesh_shift = y_mesh - (binningFactor - 1) / 2;
        sr = griddata(x_mesh, y_mesh, sr, x_mesh_shift, y_mesh_shift, 'cubic');
    end

end

function qualityMeasure = calculateQualityMeasureForImage(SR, groundTruth, qualityMeasureName, border)

    % Crop boundary of ground truth and super-resolved image.
    groundTruth = groundTruth( (border+1):(end-border), (border+1):(end-border) );
    SR = SR( (border+1):(end-border), (border+1):(end-border) );

    % Calculate the desired measure.
    qualityMeasure.(qualityMeasureName) = callQualityMeasure(SR, groundTruth, qualityMeasureName);

end


%
% [fh, varfh] = gppred(X, f, Covf, Xh, logtheta, covfunc, varargin)
%
function [fh, varfh] = gppred(X, f, Covf, Xh, logtheta, covfunc, ...
                              varargin)

warning('Deprecated. Use gp_predict or gp_predict_pseudo.');

% If you need more than just variances for PCA, you could give some sparse
% matrix to indicate which covariances should be evaluated. But again, this
% covariances should be zero for the latent function values fh a
% priori.. Things would be much easier if I just factored the principal
% components too.. :)

if ~iscell(covfunc)
  covfunc = {covfunc};
end

opts = struct('cholkx', [], 'khx', [], 'kh', []);

[ opts, errmsg, wrnmsg ] = argschk( opts, varargin{:} );
if ~isempty(errmsg), error( errmsg ), end
if ~isempty(wrnmsg), warning( wrnmsg ), end

nh = cols(Xh);

I = diag(sparse(ones(cols(X),1)));

if isempty(opts.cholkx)
  Kpp = feval(covfunc{:}, logtheta, X, X);

  % DEBUG: REGULARIZE
  %Kpp = regularize(Kpp);

  Kpp = regularize(Kpp);
  Lp = chol(Kpp, 'lower');
%  [Lp,Kpp] = safechol(Kpp, 1e-10, 'lower');
  % DEBUG:
  %Kpp = Lp*Lp';
else
  Lp = opts.cholkx;
end

if isempty(opts.khx)
  Kxp = feval(covfunc{:}, logtheta, Xh, X);
else
  Kxp = opts.khx;
end

if isempty(opts.kh)
  Kx = feval(covfunc{:}, logtheta, Xh, []);
else
  Kx = opts.kh;
end

fh = Kxp * solve_triu(Lp', solve_tril(Lp, f));

if nargout >= 2
  varfh = zeros(nh,1);
  for i=1:nh
    r = solve_tril(Lp, Kxp(i,:)');
    s = solve_triu(Lp', r);
    varfh(i) = Kx(i) - r'*r + s'*Covf*s;
  end
end

% $$$ if isequal(covfunc{1}, @gpcovPP)
% $$$ %if strcmpi(covfunc{1}, @gpcovPP)
% $$$   if ~issparse(Lp)
% $$$     error('Lp should be sparse');
% $$$   else
% $$$     density_in_gppred = nnz(Lp) / prod(size(Lp))
% $$$   end
% $$$ end


function [stack, stack_exposure ] = SortStack( stack, stack_exposure, sorting_type )
%
%       [lin_fun, pp] = MitsunagaNayarCRF(stack, stack_exposure, N, nSamples, sampling_strategy)
%
%       This function computes camera response function using Mitsunaga and
%       Nayar method.
%
%        Input:
%           -stack: a stack of LDR images. If the stack is a single or
%           double values are assumed to be in [0,1]
%           -stack_exposure: an array containg the exposure time of each
%           image. Time is expressed in second (s)
%           -sorting_type: 'ascend' or 'descend'
%
%        Output:
%           -stack: sorted stack
%           -stack_exposure: sorted stack_exposure
%
%     Copyright (C) 2016  Francesco Banterle
%
%     This program is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
%
%     This program 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 General Public License for more details.
%
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see <http://www.gnu.org/licenses/>.
%

if(~exist(sorting_type, 'var'))
    sorting_type =  'ascend';
end

[stack_exposure_sorted, ind] = sort(stack_exposure, sorting_type);

if(sum(abs(stack_exposure_sorted - stack_exposure)) > 0.0)
    stack_sorted = zeros(size(stack));
    for i=1:length(stack_exposure)
        stack_sorted(:,:,:,i) = stack(:,:,:,ind(i));
    end

    stack = stack_sorted;
    stack_exposure = stack_exposure_sorted;

    clear('stack_sorted');
    clear('stack_exposure_sorted');
end

end


function data = fixtrialdef(data)

% FIXTRIALDEF adds a trl matrix to the raw data configuration
% which is constructed on the fly, assuming that the trials are
% consecutive segments of a continuous recording

% Copyright (C) 2009-2010, Robert Oostenveld and Jan-Mathijs Schoffelen

if isfield(data, 'sampleinfo')
  return;
end

if ~isfield(data, 'cfg')
  % fieldtrip raw data structures are expected to have a cfg
  data.cfg = [];
end

hastrial   = isfield(data, 'trial');
hastime    = isfield(data, 'time');
hasfsample = isfield(data, 'fsample');

if ~hasfsample && isfield(data, 'time')
  data.fsample = median(1./diff(data.time{1}));
end

if hastrial,
  ntrial = length(data.trial);
else
  ntrial = dimlength(data, 'rpt');
  if ~isfinite(ntrial) && strcmp(data.dimord(1:6), 'rpttap') && isfield(data, 'cumtapcnt'),
    ntrial = numel(data.cumtapcnt);
  elseif ~isfinite(ntrial)
    ntrial = 1;
  end
end

trl = ft_findcfg(data.cfg, 'trl');

nsmp = zeros(ntrial,1);
if hastrial,
  for i=1:ntrial
    nsmp(i) = size(data.trial{i}, 2);
  end
elseif ~isempty(trl)
  nsmp = trl(:,2) - trl(:,1) + 1;
end

if size(trl,1)~=numel(nsmp)
  warning_once('the trial definition in the configuration is inconsistent with the actual data');
  trl = [];
elseif isempty(trl) || ~all(nsmp==trl(:,2)-trl(:,1)+1)
  warning_once('the data does not contain a trial definition, assuming that the trials are consecutive segments of a continuous recording');
  % construct a trial definition on the fly, assume that the trials are
  % consecutive segments of a continuous recording
  if ntrial==1,
    begsample = 1;
  else
    begsample = cat(1, 0, cumsum(nsmp(1:end-1))) + 1;
  end
  endsample = begsample + nsmp - 1;

  offset    = zeros(ntrial,1);
  if hastime,
    for i=1:ntrial
      offset(i) = time2offset(data.time{i}, data.fsample);
    end
  end
  trl = [begsample endsample offset];

elseif size(trl,1)~=ntrial
  warning_once('the trial definition in the configuration is inconsistent with the actual data');
  trl = [];
elseif nsmp~=(trl(:,2)-trl(:,1)+1)
  warning_once('the trial definition in the configuration is inconsistent with the actual data');
  trl = [];
end

if ~isfield(data, 'sampleinfo') && ~isempty(trl)
  data.sampleinfo = trl(:, 1:2);
elseif ~isfield(data, 'sampleinfo') && isempty(trl)
  warning_once('failed to create sampleinfo field');
end

if (~isfield(data, 'trialinfo') || isempty(data.trialinfo)) && ~isempty(trl) && size(trl, 2) > 3,
  data.trialinfo = trl(:, 4:end);
end

% if data is not raw then it does not make sense to keep the sampleinfo
if ~hastrial && isfield(data, 'sampleinfo')
  data = rmfield(data, 'sampleinfo');
end


function CPD = update_CPT(CPD)
% Compute the big CPT for an HHMM Q node (including F parents) given internal transprob and startprob
% function CPD = update_CPT(CPD)

Qsz = CPD.Qsz;
Qpsz = CPD.Qpsz;

if ~isempty(CPD.Fbelow_ndx)
  if ~isempty(CPD.Fself_ndx) % general case
    % Fb(t-1) Fself(t-1)  P(Q(t)=j| Q(t-1)=i, Qps(t)=k)
    % ------------------------------------------------------
    % 1        1         delta(i,j)
    % 2        1         transprob(i,k,j)
    % 1        2         impossible
    % 2        2         startprob(k,j)
    CPT = zeros(Qsz, 2, 2, Qpsz, Qsz);
    I = repmat(eye(Qsz), [1 1 Qpsz]); % i,j,k
    I = permute(I, [1 3 2]); % i,k,j
    CPT(:, 1, 1, :, :) = I;
    CPT(:, 2, 1, :, :) = CPD.transprob;
    CPT(:, 1, 2, :, :) = I;
    CPT(:, 2, 2, :, :) = repmat(reshape(CPD.startprob, [1 Qpsz Qsz]), [Qsz 1 1]); % replicate over i
  else % no F from self, hence no startprob
    % Fb(t-1) P(Q(t)=j| Q(t-1)=i, Qps(t)=k)
    % ------------------------------------------------------
    % 1       delta(i,j)
    % 2       transprob(i,k,j)

    nps = length(CPD.dom_sz)-1; % num parents
    CPT = 0*myones(CPD.dom_sz);
    %CPT = zeros(Qsz, 2, Qpsz, Qsz); % assumes CPT(Q(t-1), F(t-1), Qps, Q(t))
    % but a member of Qps may preceed Q(t-1) or F(t-1) in the ordering

    I = repmat(eye(Qsz), [1 1 Qpsz]); % i,j,k
    I = permute(I, [1 3 2]); % i,k,j

    % the following fails if there is a member of Qps with a lower
    % number than F
    %CPT(:, 1, :, :) = I;
    %CPT(:, 2, :, :) = CPD.transprob;

    ndx = mk_multi_index(nps+1, CPD.Fbelow_ndx, 1);
    CPT(ndx{:}) = I;
    ndx = mk_multi_index(nps+1, CPD.Fbelow_ndx, 2);
    CPT(ndx{:}) = CPD.transprob;
    keyboard
  end
else % no F signal from below
  if ~isempty(CPD.Fself_ndx)
    % Q(t-1), Fself(t-1), Qps, Q(t)

    % if condition start on previous concrete state (as in map learning),
    % CPT(:, 1, :, :, :) = CPD.transprob(Q(t-1), Qps, Q(t))
    % CPT(:, 2, :, :, :) = CPD.startprob(Q(t-1), Qps, Q(t))

    % Fself(t-1)  P(Q(t-1)=i, Qps(t)=k -> Q(t)=j)
    % ------------------------------------------------------
    % 1         transprob(i,k,j)
    % 2         startprob(k,j)
    CPT = zeros(Qsz, 2, Qpsz, Qsz);
    I = repmat(eye(Qsz), [1 1 Qpsz]); % i,j,k
    I = permute(I, [1 3 2]); % i,k,j
    CPT(:, 1, :, :) = CPD.transprob;
    if CPD.fullstartprob
      CPT(:, 2, :, :) = CPD.startprob;
    else
      CPT(:, 2, :, :) = repmat(reshape(CPD.startprob, [1 Qpsz Qsz]), [Qsz 1 1]); % replicate over i
    end
    else % no F from self
    error('An hhmmQ node without any F parents is just a tabular_CPD')
  end
end

CPD = set_fields(CPD, 'CPT', CPT);


function [Hhat_LMMSE,C_LMMSE,tau_p,R,H] = functionChannelEstimates_impairments(R,channelGaindB,nbrOfRealizations,M,K,L,p,f,kappatUE,kapparBS)
%Generate the channel realizations and estimates of these channels for all
%UEs in the entire network. The channels are assumed to be correlated
%Rayleigh fading. The LMMSE estimator that takes transceiver hardware
%impairments into account is used.
%
%INPUT:
%R                 = M x M x K x L x L matrix with spatial correlation
%                    matrices for all UEs in the network. R(:,:,k,j,l) is
%                    the correlation matrix for the channel between UE k
%                    in cell j and the BS in cell l. This such matrix can
%                    either include the average channel gain or can be
%                    normalized arbitrarily.
%channelGaindB     = K x L x L matrix containing the average channel gains
%                    in dB of all the channels, if these are not already
%                    included in the spatial channel correlation matrices.
%                    The product R(:,:,k,j,l)*10^(channelGaindB(k,j,l)/10)
%                    is the full spatial channel correlation matrix.
%nbrOfRealizations = Number of channel realizations
%M                 = Number of antennas per BS
%K                 = Number of UEs per cell
%L                 = Number of BSs and cells
%p                 = Uplink transmit power per UE (same for everyone)
%f                 = Pilot reuse factor
%kappatUE          = Hardware quality of the UEs' transmitters
%kapparBS          = Hardware quality of the BSs' receivers
%
%OUTPUT:
%Hhat_LMMSE   = M x nbrOfRealizations x K x L x L matrix with the LMMSE
%               channel estimates. The matrix Hhat_LMMSE(:,n,k,j,l) is the
%               n:th channel estimate of the channel between UE k in cell j
%               and the BS in cell l.
%C_LMMSE      = M x M x K x L x L matrix with estimation error correlation
%               matrices when using LMMSE estimation. The matrix is
%               organized in the same way as R.
%tau_p        = Length of pilot sequences
%R            = Scaled version of the input spatial correlation matrices R,
%               where the channel gains from channelGaindB are included
%H            = M x nbrOfRealizations x K x L x L matrix with the true
%               channel realizations. The matrix is organized in the same
%               way as Hhat_LMMSE.
%
%
%This Matlab function was developed to generate simulation results to:
%
%Emil Bjornson, Jakob Hoydis and Luca Sanguinetti (2017),
%"Massive MIMO Networks: Spectral, Energy, and Hardware Efficiency",
%Foundations and Trends in Signal Processing: Vol. 11, No. 3-4,
%pp. 154-655. DOI: 10.1561/2000000093.
%
%For further information, visit: https://www.massivemimobook.com
%
%This is version 1.0 (Last edited: 2017-11-04)
%
%License: This code is licensed under the GPLv2 license. If you in any way
%use this code for research that results in publications, please cite our
%monograph as described above.

%% Generate channel realizations

%Generate uncorrelated Rayleigh fading channel realizations
H = (randn(M,nbrOfRealizations,K,L,L)+1i*randn(M,nbrOfRealizations,K,L,L));

%Prepare a matrix to save the channel gains per UE
betas = zeros(K,L,L);

%Go through all channels and apply the channel gains to the spatial
%correlation matrices
for j = 1:L

    for l = 1:L

        for k = 1:K

            %Extract channel gain in linear scale
            betas(k,j,l) = 10^(channelGaindB(k,j,l)/10);

            %Apply channel gain to correlation matrix
            R(:,:,k,j,l) = betas(k,j,l) * R(:,:,k,j,l);

            %Apply correlation to the uncorrelated channel realizations
            Rsqrt = sqrtm(R(:,:,k,j,l));
            H(:,:,k,j,l) = sqrt(0.5)*Rsqrt*H(:,:,k,j,l);

        end

    end

end

%% Perform channel estimation

%Length of pilot sequences
tau_p = f*K;

%Generate pilot pattern
if f == 1

    pilotPattern = ones(L,1);

elseif f == 2 %Only works in the running example with its 16 BSs

    pilotPattern = kron(ones(2,1),[1; 2; 1; 2; 2; 1; 2; 1]);

elseif f == 4 %Only works in the running example with its 16 BSs

    pilotPattern = kron(ones(2,1),[1; 2; 1; 2; 3; 4; 3; 4]);

elseif f == 16 %Only works in the running example with its 16 BSs

    pilotPattern = (1:L)';

end

%Store identity matrix of size M x M
eyeM = eye(M);

%Generate realizations of normalized noise
Np = sqrt(0.5)*(randn(M,nbrOfRealizations,K,L,f) + 1i*randn(M,nbrOfRealizations,K,L,f));

%Prepare to store LMMSE channel estimates
Hhat_LMMSE = zeros(M,nbrOfRealizations,K,L,L);

%Prepare to store estimation error correlation matrices
C_LMMSE = zeros(M,M,K,L,L);

%% Go through all f pilot groups
for g = 1:f

    %Create transmit distortion term
    etaUE = sqrt(0.5*p*tau_p*kapparBS*(1-kappatUE))*(randn(1,nbrOfRealizations,K,L,K) + 1i*randn(1,nbrOfRealizations,K,L,K));

    %Go through all cells
    for j = 1:L

        %Extract the cells that belong to pilot group g
        groupMembers = find(g==pilotPattern)';

        %Go through all UEs
        for k = 1:K

            %Create receive distortion term
            etabarBS = sqrt(0.5*p*tau_p*(1-kapparBS))*(randn(M,nbrOfRealizations,K,L) + 1i*randn(M,nbrOfRealizations,K,L));

            %Sum up transmit and receive distortion terms
            etaSum = repmat(etaUE(:,:,:,:,k),[M 1 1 1]) + etabarBS;

            %Compute processed pilot signal for all UEs that use these pilots, according to (6.22)
            ypk = sqrt(p*kappatUE*kapparBS)*tau_p*sum(H(:,:,k,g==pilotPattern,j),4) + squeeze(sum(sum(H(:,:,:,:,j).*etaSum,4),3)) + sqrt(tau_p)*Np(:,:,k,j,g);

            %Compute sum of all UEs' correlation matrices to BS j
            RjAll = sum(sum(R(:,:,:,:,j),3),4);

            %Compute the matrix that is inverted in the LMMSE estimator
            PsiInv = (p*kappatUE*kapparBS*tau_p*sum(R(:,:,k,g==pilotPattern,j),4) + p*(1-kappatUE)*kapparBS*RjAll + p*(1-kapparBS)*diag(diag(RjAll)) + eyeM);

            %Go through the cells in pilot group g
            for l = groupMembers

                %Compute LMMSE estimate of channel between BS l and UE k in
                %cell j using (6.23) in Theorem 6.1
                RPsi = R(:,:,k,l,j) / PsiInv;
                Hhat_LMMSE(:,:,k,l,j) = sqrt(p*kappatUE*kapparBS)*RPsi*ypk;

                %Compute corresponding estimation error correlation matrix, using (6.26)
                C_LMMSE(:,:,k,l,j) = R(:,:,k,l,j) - p*kappatUE*kapparBS*tau_p*RPsi*R(:,:,k,l,j);

            end

        end

    end

end


function signal = interpolateChannels(signal, targetChannels, sourceChannels)
% Interpolate the targetChannels rows of signal.data using spherical splines
%
% signal = interpolateChannels(signal, targetChannels)
% [signal, W] = interpolateChannels(signal, targetChannels, sourceChannels)
%
% Input:
%   signal      Structure with data and chanlocs fields compatible with
%               an EEGLAB EEG structure (requires .data and .chanlocs fields)
%   targetChannels  Vector of channel numbers for channels to interpolate (required)
%   sourceChannels  Vector of channel numbers to use in the interpolation
%               (default all channels except destination channels)
%
% Output:
%   signal      Input signal structure with the dest_chans rows of
%               signal.data replaced with their interpolated values
%
%
% Written by Kay Robbins 8/24/2014 UTSA
%

%% Check the parameters
if nargin < 1 || ~isstruct(signal) || ~isfield(signal, 'data') || ...
   ~isfield(signal, 'chanlocs')
    error('interpolateChannels:NotEnoughArguments', ...
          'first argument must be a structure with data and chanlocs fields');
elseif ~exist('targetChannels', 'var') || isempty(targetChannels) || ...
    min(targetChannels) < 0 || max(targetChannels) > size(signal.data, 1)
    error('interpolateChannels:InterpolatedChannelsOutOfRange', ...
          'interpolated (dest) channels must be in dim 1 of signal.data');
elseif ~exist('sourceChannels', 'var') || isempty(sourceChannels)
    sourceChannels = 1:size(signal.data, 1);
end
sourceChannels = setdiff(sourceChannels, targetChannels);  % Src and dest not same
if isempty(sourceChannels) || min(sourceChannels) < 0 || max(sourceChannels) > size(signal.data, 1)
    error('interpolateChannels:SourceChannelsOutOfRange', ...
          'source channels must be in dim 1 of signal.data');
end

%% Perform the interpolation -- currently using EEGLAB eeg_interp
sourceChannels = sort(sourceChannels);
targetChannels = sort(targetChannels);
channelsConsidered = union(sourceChannels, targetChannels);
[~, reindexedTargetChannels] = intersect(channelsConsidered, targetChannels);
signaltemp = signal;
signaltemp.data = signal.data(channelsConsidered, :);
signaltemp.chanlocs = signal.chanlocs(channelsConsidered);
signaltemp.nbchan = length(signaltemp.chanlocs);
signaltemp = eeg_interp(signaltemp, reindexedTargetChannels, 'spherical');
reindex = false(1, length(signal.chanlocs));
reindex(targetChannels) = true;
reindex = reindex(channelsConsidered);
signal.data(targetChannels, :) = signaltemp.data(reindex, :);


function z = min( x, y, dim )

%   Disciplined convex/geometric programming information:
%       MAX is convex, log-log-concave, and nondecreasing in its first
%       two arguments. Thus when used in disciplined convex programs,
%       both arguments must be concave (or affine). In disciplined
%       geometric programs, both arguments must be log-concave/affine.

persistent remap remap_1 remap_2 remap_3
error( nargchk( 1, 3, nargin ) );
if nargin == 2,

    %
    % min( X, Y )
    %

    sx = size( x );
    sy = size( y );
    xs = all( sx == 1 );
    ys = all( sy == 1 );
    if xs,
        sz = sy;
    elseif ys || isequal( sx, sy ),
        sz = sx;
    else
        error( 'Array dimensions must match.' );
    end

    %
    % Determine the computation methods
    %

    if isempty( remap ),
        remap1 = cvx_remap( 'real' );
        remap1 = remap1' * remap1;
        remap2 = cvx_remap( 'log-concave' )' * cvx_remap( 'nonpositive' );
        remap3 = remap2';
        remap4 = cvx_remap( 'log-concave', 'real' );
        remap4 = remap4' * remap4;
        remap5 = cvx_remap( 'concave' );
        remap5 = remap5' * remap5;
        remap   = remap1 + ~remap1 .* ...
            ( 2 * remap2 + 3 * remap3 + ~( remap2 | remap3 ) .* ...
            ( 4 * remap4 + ~remap4 .* ( 5 * remap5 ) ) );
    end
    vx = cvx_classify( x );
    vy = cvx_classify( y );
    vr = remap( vx + size( remap, 1 ) * ( vy - 1 ) );
    vu = sort( vr(:) );
    vu = vu([true;diff(vu)~=0]);
    nv = length( vu );

    %
    % The cvx multi-objective problem
    %

    xt = x;
    yt = y;
    if nv ~= 1,
        z = cvx( sz, [] );
    end
    for k = 1 : nv,

        %
        % Select the category of expression to compute
        %

        if nv ~= 1,
            t = vr == vu( k );
            if ~xs,
                xt = cvx_subsref( x, t );
                sz = size( xt ); %#ok
            end
            if ~ys,
                yt = cvx_subsref( y, t );
                sz = size( yt ); %#ok
            end
        end

        %
        % Apply the appropriate computation
        %

        switch vu( k ),
        case 0,
            % Invalid
            error( 'Disciplined convex programming error:\n    Cannot perform the operation min( {%s}, {%s} )', cvx_class( xt, false, true ), cvx_class( yt, false, true ) );
        case 1,
            % constant
            cvx_optval = cvx( min( cvx_constant( xt ), cvx_constant( yt ) ) );
        case 2,
            % min( log-concave, nonpositive ) (no-op)
            cvx_optval = yt;
        case 3,
            % min( nonpositive, log-concave ) (no-op)
            cvx_optval = xt;
        case 4,
            % posy
            cvx_begin gp
                 hypograph variable zt( sz )
                xt >= zt; %#ok
                yt >= zt; %#ok
            cvx_end
        case 5,
            % non-posy
            cvx_begin
                hypograph variable zt( sz )
                xt >= zt; %#ok
                yt >= zt; %#ok
            cvx_end
        otherwise,
            error( 'Shouldn''t be here.' );
        end

        %
        % Store the results
        %

        if nv == 1,
            z = cvx_optval;
        else
            z = cvx_subsasgn( z, t, cvx_optval );
        end

    end

else

    %
    % min( X, [], dim )
    %

    if nargin > 1 && ~isempty( y ),
        error( 'min with two matrices to compare and a working dimension is not supported.' );
    end
    sx = size( x );
    if nargin < 2,
        dim = cvx_default_dimension( sx );
    elseif ~cvx_check_dimension( dim ),
        error( 'Third argument must be a positive integer.' );
    end

    %
    % Determine sizes, quick exit for empty arrays
    %

    sx = [ sx, ones( 1, dim - length( sx ) ) ];
    nx = sx( dim );
    if any( sx == 0 ),
        sx( dim ) = min( sx( dim ), 1 );
        z = zeros( sx );
        return
    end
    sy = sx;
    sy( dim ) = 1;

    %
    % Type check
    %

    if isempty( remap_3 ),
        remap_1 = cvx_remap( 'real' );
        remap_2 = cvx_remap( 'log-concave', 'real' );
        remap_3 = cvx_remap( 'concave' );
    end
    vx = cvx_reshape( cvx_classify( x ), sx );
    t1 = all( reshape( remap_1( vx ), sx ), dim );
    t2 = all( reshape( remap_2( vx ), sx ), dim );
    t3 = all( reshape( remap_3( vx ), sx ), dim );
    t3 = t3 & ~( t1 | t2 );
    t2 = t2 & ~t1;
    ta = t1 + ( 2 * t2 + 3 * t3 ) .* ~t1;
    nu = sort( ta(:) );
    nu = nu([true;diff(nu)~=0]);
    nk = length( nu );

    %
    % Quick exit for size 1
    %

    if nx == 1 && all( nu ),
        z = x;
        return
    end

    %
    % Permute and reshape, if needed
    %

    if dim > 1 && any( sx( 1 : dim - 1 ) > 1 ),
        perm = [ dim, 1 : dim - 1, dim + 1 : length( sx ) ];
        x   = permute( x,  perm );
        ta  = permute( ta, perm );
        sx  = sx(perm);
        sy  = sy(perm);
        dim = 1;
    else
        perm = [];
    end
    nv = prod( sx ) / nx;
    x  = reshape( x, nx, nv );

    %
    % Perform the computations
    %

    if nk ~= 1,
        z = cvx( [ 1, nv ], [] );
    end
    for k = 1 : nk,

        if nk == 1,
            xt = x;
        else
            tt = ta == nu( k );
            xt = cvx_subsref( x, ':', tt );
            nv = nnz( tt ); %#ok
        end

        switch nu( k ),
            case 0,
                error( 'Disciplined convex programming error:\n   Invalid computation: min( {%s} )', cvx_class( xt, false, true ) );
            case 1,
                cvx_optval = min( cvx_constant( xt ), [], dim );
            case 2,
                cvx_begin gp
                    hypograph variable zt( 1, nv )
                    xt >= ones(nx,1) * zt; %#ok
                cvx_end
            case 3,
                cvx_begin
                    hypograph variable zt( 1, nv )
                    xt >= ones(nx,1) * zt; %#ok
                cvx_end
            otherwise,
                error( 'Shouldn''t be here.' );
        end

        if nk == 1,
            z = cvx_optval;
        else
            z = cvx_subsasgn( z, tt, cvx_optval );
        end

    end

    %
    % Reverse the reshaping and permutation steps
    %

    z = reshape( z, sy );
    if ~isempty( perm ),
        z = ipermute( z, perm );
    end

end

% Copyright 2010 Michael C. Grant and Stephen P. Boyd.
% See the file COPYING.txt for full copyright information.
% The command 'cvx_where' will show where this file is located.