function []=ps_sb_merge()
%PS_SB_MERGE merge PS and SB pixels
%
%   Andy Hooper May 2007
%
logit;

ps_wd=pwd;
i=strfind(ps_wd,'INSAR_');
if isempty(i)
    error('INSAR_* does not appear in current directory path')
end

sb_wd1=[ps_wd(1:i+13),'/SMALL_BASELINES'];
sb_wd=[sb_wd1,ps_wd(i+14:end)];
merged_wd1=[ps_wd(1:i+13),'/MERGED'];
merged_wd=[merged_wd1,ps_wd(i+14:end)];

if ~exist(merged_wd)
    mkdir(merged_wd)
end
if ~exist([merged_wd,'/patch_noover.in']) & exist([sb_wd,'/patch_noover.in'])
    aa=['!cp ',sb_wd,'/patch_noover.in ',merged_wd];
    eval(aa)
end
if ~exist([merged_wd1,'/parms.mat'])
    aa=['!cp ',sb_wd1,'/parms.mat ',merged_wd1,'/parms.mat'];
    eval(aa)
end

psver=2;
save([merged_wd,'/psver'],'psver')

psps=load([ps_wd,'/ps2.mat']);
pssb=load([sb_wd,'/ps2.mat']);

pssb_ix=zeros(psps.n_ps,1); % points from ps to sb

imax=max([psps.ij(:,2);pssb.ij(:,2)])+1;
jmax=max([psps.ij(:,3);pssb.ij(:,3)])+1;
grid_ix=zeros(imax,jmax);
grid_ix(pssb.ij(:,2)+1+pssb.ij(:,3)*imax)=[1:pssb.n_ps];
pssb_ix=grid_ix(psps.ij(:,2)+1+psps.ij(:,3)*imax);

psu_ix=find(pssb_ix==0); % ps only index
psnu_ix=find(pssb_ix);  % ps non-unique index
sbnu_ix=pssb_ix(psnu_ix); % sb non-unique index

%%%%%%%%% Some non-adjacent pixels are allocated the same lon/lat by DORIS. %%%%%%%%%
[dups,I]=setdiff(psps.lonlat(psu_ix,:),pssb.lonlat,'rows');
fprintf('%d pixels with duplicate lon/lat dropped\n',length(psu_ix)-length(dups))
psu_ix=psu_ix(I);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

G=zeros(pssb.n_ifg,pssb.n_image);
for i=1:pssb.n_ifg
     G(i,pssb.ifgday_ix(i,1))=-1;
     G(i,pssb.ifgday_ix(i,2))=1;
end

pmps=load([ps_wd,'/pm2.mat']);
pmsb=load([sb_wd,'/pm2.mat']);

pmps.ph_res_all=[pmps.ph_res(:,1:psps.master_ix-1),pmps.C_ps,pmps.ph_res(:,psps.master_ix:end)]; % add master residual
pmps.ph_res3=exp(j*(G*pmps.ph_res_all')'); % would-be residuals of sb ifgs
pmps.coh_ps2=abs(sum(pmps.ph_res3,2))/pssb.n_ifg; % would-be coherence of sb ifgs

psnu_coh=pmps.coh_ps2(psnu_ix);
psnu_snr=1./(1./psnu_coh.^2-1);
sbnu_coh=pmsb.coh_ps(sbnu_ix);
sbnu_snr=1./(1./sbnu_coh.^2-1);
ps_high_coh_ix=pmps.coh_ps2(psu_ix)>min(pmsb.coh_ps);
psu_ix=psu_ix(ps_high_coh_ix);
psu_coh=pmps.coh_ps2(psu_ix);

save([merged_wd,'/merge2'],'psu_ix','psu_coh','psnu_ix','sbnu_ix','psnu_coh','sbnu_coh','G')

ps=pssb;
ps.ij=[psps.ij(psu_ix,:);pssb.ij];
ps.lonlat=[psps.lonlat(psu_ix,:);pssb.lonlat];
ll0=(max(ps.lonlat)+min(ps.lonlat))/2;
xy=llh2local(ps.lonlat',ll0)*1000;
xy=xy';
sort_x=sortrows(xy,1);
sort_y=sortrows(xy,2);
n_pc=round(size(xy,1)*0.001);
bl=mean(sort_x(1:n_pc,:)); % bottom left corner
tr=mean(sort_x(end-n_pc:end,:)); % top right corner
br=mean(sort_y(1:n_pc,:)); % bottom right  corner
tl=mean(sort_y(end-n_pc:end,:)); % top left corner

heading=getparm('heading');
theta=(180-heading)*pi/180;
if theta>pi
    theta=theta-2*pi;
end

rotm=[cos(theta),sin(theta); -sin(theta),cos(theta)];
xy=xy';
xynew=rotm*xy; % rotate so that scene axes approx align with x=0 and y=0
if max(xynew(1,:))-min(xynew(1,:))<max(xy(1,:))-min(xy(1,:)) &...
   max(xynew(2,:))-min(xynew(2,:))<max(xy(2,:))-min(xy(2,:))
    xy=xynew; % check that rotation is an improvement
    disp(['Rotating by ',num2str(theta*180/pi),' degrees']);
end

xy=single(xy');
[dummy,sort_ix]=sortrows(xy,[2,1]); % sort in ascending y order
xy=xy(sort_ix,:);
xy=[[1:size(xy,1)]',xy];
xy(:,2:3)=round(xy(:,2:3)*1000)/1000; % round to mm
ps.xy=xy;
ps.lonlat=ps.lonlat(sort_ix,:);
ps.ij=ps.ij(sort_ix,:);
ps.n_ps=size(ps.xy,1);
save([merged_wd,'/ps2'],'-struct','ps')

pmps.ph_patch2=exp(j*G*angle([pmps.ph_patch(:,1:psps.master_ix-1),ones(psps.n_ps,1),pmps.ph_patch(:,psps.master_ix:end)])').';
pm.ph_patch=pmsb.ph_patch;
pm.ph_patch(sbnu_ix,:)=pmsb.ph_patch(sbnu_ix,:).*repmat(sbnu_snr,1,pssb.n_ifg)+pmps.ph_patch2(psnu_ix,:).*repmat(psnu_snr,1,pssb.n_ifg);
pm.ph_patch(sbnu_ix,:)=pm.ph_patch(sbnu_ix,:)./abs(pm.ph_patch(sbnu_ix,:));
pm.ph_patch=[pmps.ph_patch2(psu_ix,:);pm.ph_patch];
pm.ph_patch=pm.ph_patch(sort_ix,:);

save([merged_wd,'/pm2'],'-struct','pm')
clear pm pmps pmsb

rcps=load([ps_wd,'/rc2.mat']);
rcsb=load([sb_wd,'/rc2.mat']);
rcps.ph_rc2=exp(j*G*angle(rcps.ph_rc)').'; % convert phase to sb ifgs
rc=rcsb;
rc.ph_rc(sbnu_ix,:)=rcsb.ph_rc(sbnu_ix,:).*repmat(sbnu_snr,1,pssb.n_ifg)+rcps.ph_rc2(psnu_ix,:).*repmat(psnu_snr,1,pssb.n_ifg);
rc.ph_rc(sbnu_ix,:)=rc.ph_rc(sbnu_ix,:)./abs(rc.ph_rc(sbnu_ix,:));
%rc.ph_rc(sbnu_ix,:)=rc.ph_rc(sbnu_ix,:)+rcps.ph_rc2(psnu_ix,:);
rc.ph_rc=[rcps.ph_rc2(psu_ix,:);rc.ph_rc];
rc.ph_rc=rc.ph_rc(sort_ix,:);

save([merged_wd,'/rc2'],'-struct','rc')
clear rc rcps rcsb

bpps=load([ps_wd,'/bp2.mat']);
bpsb=load([sb_wd,'/bp2.mat']);
bpps.bperp_mat2=(G*[bpps.bperp_mat(:,1:psps.master_ix-1),zeros(psps.n_ps,1),bpps.bperp_mat(:,psps.master_ix:end)]')';
bp=bpsb;
bp.bperp_mat=[bpps.bperp_mat2(psu_ix,:);bp.bperp_mat];
bp.bperp_mat=bp.bperp_mat(sort_ix,:);
save([merged_wd,'/bp2'],'-struct','bp')
clear bp bpps bpsb

hgtps=load([ps_wd,'/hgt2.mat']);
hgtsb=load([sb_wd,'/hgt2.mat']);
hgt=hgtsb;
hgt.hgt=[hgtps.hgt(psu_ix,:);hgt.hgt];
hgt.hgt=hgt.hgt(sort_ix,:);
save([merged_wd,'/hgt2'],'-struct','hgt')
clear hgt hgtps hgtsb

laps=load([ps_wd,'/la2.mat']);
lasb=load([sb_wd,'/la2.mat']);
la=lasb;
la.la=[laps.la(psu_ix,:);la.la];
la.la=la.la(sort_ix,:);
save([merged_wd,'/la2'],'-struct','la')
clear la laps lasb
logit(1);


function [growthOnMinimalMedium,essentialExchanges,modelPruned] = predictGrowthRequirements(model)
% Predicts growth requirements for a genome-scale reconstruction.
% The list of predicted essential exchanges as well as a model
% constrained with these exchanges are returned.
%
% INPUT
% model                  COBRA model structure
% biomassReaction        String listing the biomass reaction
%
% OUTPUT
% growthOnMinimalMedium  Bool if growth on defined medium yes or no
% essentialExchanges     Exchanges that need to be open to enable growth
% constrainedModel       Model constrained with predicted essential
%                        exchanges
%
% Almut Heinken, November 2020

tol=1e-6;

% set "unlimited" constraints
model = changeRxnBounds(model, model.rxns(strncmp('EX_', model.rxns, 3)), -1000, 'l');
model = changeRxnBounds(model, model.rxns(strncmp('EX_', model.rxns, 3)), 1000, 'u');

% remove positive values on lower bounds
model.lb(find(model.lb>0))=0;
%
% % remove compounds that should not be essential for any organism
% unlikelyRequirements={'EX_alaasp(e)';'EX_alagln(e)';'EX_alaglu(e)';'EX_alagly(e)';'EX_alahis(e)';'EX_alaleu(e)';'EX_alathr(e)';'EX_glygln(e)';'EX_glyglu(e)';'EX_glyleu(e)';'EX_glymet(e)';'EX_glyphe(e)';'EX_glypro(e)';'EX_glytyr(e)';'EX_metala(e)';'EX_glyasn(e)';'EX_glyasp(e)';'EX_dpcoa(e)';'EX_coa(e)';'EX_cmp(e)';'EX_dtmp(e)';'EX_ump(e)';'EX_gmp(e)';'EX_dgtp(e)';'EX_nadp(e)';'EX_nad(e)';'EX_thmmp(e)';'EX_malt(e)';'EX_stys(e)';'EX_raffin(e)';'EX_melib(e)';'EX_lcts(e)';'EX_gtp(e)';'EX_datp(e)';'EX_amp(e)';'EX_pppi(e)';'EX_r5p(e)';'EX_g1p(e)';'EX_glu_D(e)';'EX_agm(e)';'EX_glycys(e)';'EX_1hibup_S(e)';'EX_1hibupglu_S(e)';'EX_1hmdgluc(e)';'EX_1ohmdz(e)';'EX_2hatvacid(e)';'EX_2hatvacidgluc(e)';'EX_2hatvlac(e)';'EX_2hatvlacgluc(e)';'EX_2hibup_S(e)';'EX_2hibupglu_S(e)';'EX_2oh_cbz_glc(e)';'EX_2oh_cbz(e)';'EX_2oh_mtz_glc(e)';'EX_2oh_mtz(e)';'EX_34dhphe(e)';'EX_3hibup_S(e)';'EX_3hibupglu_S(e)';'EX_3meacmp(e)';'EX_3oh_cbz_glc(e)';'EX_3oh_cbz(e)';'EX_3oh_dlor_glc(e)';'EX_3oh_dlor(e)';'EX_3oh_mdea_glc(e)';'EX_3oh_mdea(e)';'EX_3oh_mxn_glc(e)';'EX_3oh_mxn(e)';'EX_4dh_tpno_1glc(e)';'EX_4dh_tpno(e)';'EX_4hmdgluc(e)';'EX_4hphac(e)';'EX_4oh_dcf_glc(e)';'EX_4oh_dcf(e)';'EX_4oh_kp_glc(e)';'EX_4oh_kp(e)';'EX_4oh_levole_glc(e)';'EX_4oh_levole(e)';'EX_4oh_meth_glc(e)';'EX_4oh_meth(e)';'EX_4oh_propl_glc(e)';'EX_4oh_propl(e)';'EX_4oh_trz_glc(e)';'EX_4oh_trz(e)';'EX_4oh_vcz_glc(e)';'EX_4oh_vcz(e)';'EX_4ohmdz(e)';'EX_5asa(e)';'EX_5fura(e)';'EX_5oh_sulfp_glc(e)';'EX_5oh_sulfp(e)';'EX_5ohfvs(e)';'EX_5ohfvsglu(e)';'EX_6bhglz(e)';'EX_6bhglzglc(e)';'EX_6ohfvs(e)';'EX_6ohfvsglu(e)';'EX_7a_czp(e)';'EX_7bhglz(e)';'EX_7bhglzglc(e)';'EX_7oh_efv_glc(e)';'EX_7oh_efv(e)';'EX_814dioh_efv_glc(e)';'EX_814dioh_efv(e)';'EX_8oh_efv_glc(e)';'EX_8oh_efv(e)';'EX_abz_ala_b(e)';'EX_ac_amn_b_gly_glc(e)';'EX_ac_amn_b_gly(e)';'EX_ac5asa(e)';'EX_acisnzd(e)';'EX_acmp_glc(e)';'EX_acmp(e)';'EX_acmpglu(e)';'EX_alpz_4oh_glc(e)';'EX_alpz_4oh(e)';'EX_alpz_aoh_glc(e)';'EX_alpz_aoh(e)';'EX_am14_glc(e)';'EX_am14(e)';'EX_am1ccs(e)';'EX_am1cglc(e)';'EX_am5_glc(e)';'EX_am5(e)';'EX_am6_glc(e)';'EX_am6(e)';'EX_amio_c_glc(e)';'EX_amio_c(e)';'EX_amio_glc(e)';'EX_amio(e)';'EX_amn_b_gly_glc(e)';'EX_amn_b_gly(e)';'EX_amntd_m6_glc(e)';'EX_amntd_m6(e)';'EX_anzp(e)';'EX_atvacid(e)';'EX_atvacylgluc(e)';'EX_atvethgluc(e)';'EX_atvlac(e)';'EX_atvlacgluc(e)';'EX_bhpm_glc(e)';'EX_bhpm(e)';'EX_bilr_355(e)';'EX_bilr_M10(e)';'EX_bilr_M12(e)';'EX_bilr_M14(e)';'EX_bilr_M15(e)';'EX_bilr_M16(e)';'EX_bilr_M7(e)';'EX_brv(e)';'EX_bsn_glc(e)';'EX_bsn(e)';'EX_bvu(e)';'EX_bz_glc(e)';'EX_bz(e)';'EX_bzd(e)';'EX_C02528(e)';'EX_caribup_s(e)';'EX_caribupglu_S(e)';'EX_cbz_glc(e)';'EX_cbz(e)';'EX_cd6168_glc(e)';'EX_cd6168(e)';'EX_chlphncl_glc(e)';'EX_chlphncl(e)';'EX_cholate(e)';'EX_clcxb_c_glc(e)';'EX_clcxb_c(e)';'EX_clcxb_glc(e)';'EX_clcxb(e)';'EX_clobi_c(e)';'EX_clobi_glc(e)';'EX_crvsm1(e)';'EX_crvsm23(e)';'EX_cvm1gluc(e)';'EX_cvm23gluc(e)';'EX_czp(e)';'EX_daa_glc(e)';'EX_daa(e)';'EX_dcf_glc(e)';'EX_dcf(e)';'EX_dchac(e)';'EX_ddea_glc(e)';'EX_ddea(e)';'EX_des_astzl_glc(e)';'EX_des_astzl(e)';'EX_dfdcytd(e)';'EX_dfduri(e)';'EX_dgchol(e)';'EX_dh5fura(e)';'EX_digitoxin(e)';'EX_digoxin_glc(e)';'EX_digoxin(e)';'EX_dihydro_digitoxin(e)';'EX_dihydro_digoxin(e)';'EX_dlb_glc(e)';'EX_dlb(e)';'EX_dnpz_5des(e)';'EX_dnpz_6des(e)';'EX_dnpz_m11(e)';'EX_dnpz_m12(e)';'EX_dnpz_m13(e)';'EX_dnpz_m14(e)';'EX_dnpz_m9(e)';'EX_doh_etr_glc(e)';'EX_doh_etr(e)';'EX_doh_vcz_glc(e)';'EX_doh_vcz(e)';'EX_dopa(e)';'EX_dxo_glc(e)';'EX_dxo(e)';'EX_efv_glc(e)';'EX_efv(e)';'EX_eltr_glc(e)';'EX_eltr_m3(e)';'EX_eltr_m4(e)';'EX_eltr(e)';'EX_estradiol(e)';'EX_estradiolglc(e)';'EX_estriol(e)';'EX_estriolglc(e)';'EX_estrone(e)';'EX_estroneglc(e)';'EX_eztmb_glc(e)';'EX_eztmb(e)';'EX_fcsn(e)';'EX_fvs(e)';'EX_fvsgluc(e)';'EX_fvstet(e)';'EX_fvstetglu(e)';'EX_gchola(e)';'EX_glc3meacp(e)';'EX_gltmn_glc(e)';'EX_gltmn(e)';'EX_gmfl_glc(e)';'EX_gmfl_mI_glc(e)';'EX_gmfl_mI(e)';'EX_gmfl_mII_glc(e)';'EX_gmfl_mII(e)';'EX_gmfl_mIII_glc(e)';'EX_gmfl_mIII(e)';'EX_gmfl(e)';'EX_gtacmp(e)';'EX_HC02191(e)';'EX_hst_3_glc(e)';'EX_hst_3_s(e)';'EX_hst_37_diglc(e)';'EX_hst_3glc_7s(e)';'EX_hst_7_glc(e)';'EX_hst_7_s(e)';'EX_hst_7glc_3s(e)';'EX_hst(e)';'EX_ibup_S(e)';'EX_ibupgluc(e)';'EX_imn_glc(e)';'EX_imn(e)';'EX_inv_m1(e)';'EX_inv(e)';'EX_isnzd(e)';'EX_isosorbide_5mn_glc(e)';'EX_isosorbide_5mn(e)';'EX_kprofen_glc(e)';'EX_kprofen(e)';'EX_lactl(e)';'EX_lst4exp(e)';'EX_lstn(e)';'EX_lstn1gluc(e)';'EX_lstnm4(e)';'EX_lstnm7(e)';'EX_mdz_glc(e)';'EX_mdz(e)';'EX_mdzglc(e)';'EX_miso_glc(e)';'EX_miso(e)';'EX_mrphn_3glc(e)';'EX_mrphn_6glc(e)';'EX_mrphn(e)';'EX_mtym(e)';'EX_mtz_glc(e)';'EX_mtz(e)';'EX_N_oh_phtn_glc(e)';'EX_N_oh_phtn(e)';'EX_nchlphncl(e)';'EX_neopront(e)';'EX_nsldp_m5_glc(e)';'EX_nsldp_m5(e)';'EX_nverp_glc(e)';'EX_nverp(e)';'EX_nzp(e)';'EX_odsm_egltmn_glc(e)';'EX_odsm_egltmn(e)';'EX_odsm_gltmn_glc(e)';'EX_odsm_gltmn(e)';'EX_oh_etr_glc(e)';'EX_oh_etr(e)';'EX_oh_pbl_glc(e)';'EX_oh_pbl(e)';'EX_olsa(e)';'EX_pcresol(e)';'EX_phppa_glc(e)';'EX_phppa(e)';'EX_phtn_glc(e)';'EX_prob_glc(e)';'EX_prob(e)';'EX_pront_glc(e)';'EX_pront(e)';'EX_propl_glc(e)';'EX_propl(e)';'EX_prx_mI_glc(e)';'EX_prx_mI(e)';'EX_prx_mII_glc(e)';'EX_prx_mII(e)';'EX_ptvst(e)';'EX_ptvstgluc(e)';'EX_pvs(e)';'EX_pvsgluc(e)';'EX_R_6oh_warf_glc(e)';'EX_R_6oh_warf(e)';'EX_R_7oh_warf_glc(e)';'EX_R_7oh_warf(e)';'EX_R_8oh_warf_glc(e)';'EX_R_8oh_warf(e)';'EX_r406_glc(e)';'EX_r406(e)';'EX_r529_glc(e)';'EX_r529(e)';'EX_r788(e)';'EX_regfnb_glc(e)';'EX_regfnb(e)';'EX_rep_glc(e)';'EX_rep(e)';'EX_rpn_104557_cb_glc(e)';'EX_rpn_104557(e)';'EX_rpn_96990_glc(e)';'EX_rpn_96990(e)';'EX_rpn_oh_glc(e)';'EX_rpn_oh(e)';'EX_rsv(e)';'EX_rsvgluc(e)';'EX_S_4oh_warf_glc(e)';'EX_S_4oh_warf(e)';'EX_S_6oh_warf_glc(e)';'EX_S_6oh_warf(e)';'EX_sanilamide(e)';'EX_sb_611855(e)';'EX_sb_y(e)';'EX_sch_488128(e)';'EX_sch_57871_glc(e)';'EX_sch_57871(e)';'EX_sfnd_1689_glc(e)';'EX_sfnd_1689(e)';'EX_sftz_glc(e)';'EX_sftz(e)';'EX_simvgluc(e)';'EX_smap_glc(e)';'EX_smap(e)';'EX_smvacid(e)';'EX_sn38(e)';'EX_sn38g(e)';'EX_spz_glc(e)';'EX_spz_sfn_glc(e)';'EX_spz_sfn(e)';'EX_spz(e)';'EX_srv(e)';'EX_ssz(e)';'EX_stg_m3(e)';'EX_stg_m4(e)';'EX_stg(e)';'EX_sulfp(e)';'EX_tab(e)';'EX_tat(e)';'EX_taur(e)';'EX_tchola(e)';'EX_tdchola(e)';'EX_tgz_glc(e)';'EX_tgz(e)';'EX_thsacmp(e)';'EX_tlf_a_1a(e)';'EX_tlf_a_1b(e)';'EX_tlf_a_1x(e)';'EX_tlf_a_2(e)';'EX_tlf_a_2a(e)';'EX_tlf_a_3(e)';'EX_tlf_a_4(e)';'EX_tlf_a_m1(e)';'EX_tlf_a_m2(e)';'EX_tlf_a_m3(e)';'EX_tlf_a_m4(e)';'EX_tlf_a_m4a(e)';'EX_tlf_a_m5(e)';'EX_tlf_a_m5a(e)';'EX_tlf_a_m5b(e)';'EX_tlf_a_m6(e)';'EX_tlf_a_m9(e)';'EX_tlf_a(e)';'EX_tlms_glc(e)';'EX_tlms(e)';'EX_tmacmp(e)';'EX_tolcp_ac_glc(e)';'EX_tolcp_ac(e)';'EX_tolcp_am_glc(e)';'EX_tolcp_am(e)';'EX_tolcp_glc(e)';'EX_tolcp(e)';'EX_tpno_1glc_4g(e)';'EX_tpno_4g(e)';'EX_tpno_4glc(e)';'EX_tpnoh(e)';'EX_tsacmgluc(e)';'EX_tym(e)'};
% model=changeRxnBounds(model,unlikelyRequirements,0,'l');

% set objective
biomassReaction=model.rxns{find(strncmp(model.rxns,'bio',3)),1};
model = changeObjective(model, biomassReaction);

[modelMin, modelPruned, essentialExchanges] = generateCompactExchModel(model,tol,biomassReaction,1,1);
modelMin = changeObjective(modelMin, biomassReaction);
FBA=optimizeCbModel(modelMin,'max');
growthOnMinimalMedium=FBA.f;

end


function output = callfiltersdsp(model)

options = model.options.filtersd;

model = yalmip2nonlinearsolver(model);

if ~model.derivative_available
    disp('Derivate-free call to filterSD not yet implemented')
    error('Derivate-free call to filterSD not yet implemented')
end
if model.options.savedebug
    save filtersddebug model
end
showprogress('Calling filterSDSP',model.options.showprogress);

cu = [ repmat(0,length(model.bnonlinineq),1);
    repmat(0,length(model.bnonlineq),1);
    repmat(0,length(model.b),1);
    repmat(0,length(model.beq),1)];

cl = [ repmat(-inf,length(model.bnonlinineq),1);
    repmat(0,length(model.bnonlineq),1);
    repmat(-inf,length(model.b),1);
    repmat(0,length(model.beq),1)];

if isempty(cu)
    Flow = [];
    Fupp = [];
end

% These are needed to avoid recomputation due to ipopts double call to get
% f and df, and g and dg
global latest_x_f
global latest_x_g
global latest_df
global latest_f
global latest_G
global latest_g
global latest_xevaled
global latest_x_xevaled
latest_G = [];
latest_g = [];
latest_x_f = [];
latest_x_g = [];
latest_xevaled = [];
latest_x_xevaled = [];

funcs.objective = @(x)ipopt_callback_f(x,model);
funcs.gradient = @(x)ipopt_callback_df(x,model);
if ~isempty(cu)
    funcs.constraints = @(x)ipopt_callback_g(x,model);
    funcs.jacobian  = @(x)ipopt_callback_dg(x,model);
else
    funcs.constraints = [];
    funcs.jacobian = [];
end

lb  = model.lb(:)';
ub = model.ub(:)';

if ~isempty(cu)
   nljacstr = jacobiansparsityfromnonlinear(model);
else
   nljacstr = [];
end

if ~model.options.usex0
    model.x0 = (lb+ub)/2;
    model.x0(isinf(ub)) = lb(isinf(ub))+1;
    model.x0(isinf(lb)) = ub(isinf(lb))-1;
    model.x0(isinf(model.x0)) = 0;
end

options.display = model.options.verbose;
solvertime = tic;
[xout,fval,exitflag,stats,lambda] = filtersdsp(funcs.objective, funcs.gradient, model.x0, lb, ub, funcs.constraints, funcs.jacobian,nljacstr, cl, cu, options);
solvertime = toc(solvertime);

% Duals currently not supported
lambda = [];

x = RecoverNonlinearSolverSolution(model,xout);

switch exitflag
    case {0}
        problem = 0;
    case {1}
        problem = 2;
    case {2,3}
        problem = 1;
    case {5}
        problem = 3;
    case {4,9}
        problem = 4;
    case 105
        problem = 16;
    otherwise
        problem = -1;
end

% Internal format for duals
D_struc = [];

% Save all data sent to solver?
if model.options.savesolverinput
    solverinput.model = model;
else
    solverinput = [];
end

% Save all data from the solver?
if model.options.savesolveroutput
    solveroutput.x = xout;
    solveroutput.exitflag = exitflag;
    solveroutput.stats = stats;
else
    solveroutput = [];
end

% Standard interface
output = createoutput(x,D_struc,[],problem,'filterSD',solverinput,solveroutput,solvertime);


function allen_ccf_2pi(tv,av,st)
% allen_ccf_2pi(tv,av,st)
% written by Samuel Picard (samuel.picard@ucl.ac.uk)
% based on original allen_ccf_npx tool written by Andy Peters (peters.andrew.j@gmail.com)
%
% GUI for planning 2pi chronic window implant with the Allen CCF
% Part of repository: https://github.com/cortex-lab/allenCCF
% - directions for installing atlas in repository readme
% - some dependent functions from that repository
%
% (optional inputs - if CCF path written in Line 22, loaded automatically)
% tv, av, st = CCF template volume, annotated volume, structure tree
% tv = readNPY('template_volume_10um.npy');
% av = readNPY('annotation_volume_10um_by_index.npy');
% st = loadStructureTree('structure_tree_safe_2017.csv');

% Initialize gui_data structure
gui_data = struct;

% Allen CCF-bregma transform (estimated from eyeballing Paxinos->CCF)
% [AP,DV,ML]
bregma = [540,0,570];

% If not already loaded in, load in atlas
if nargin < 3
    allen_atlas_path = 'C:\Users\Samuel\Documents\GitHub\allenCCF'; %put path to atlas files here
    if isempty(allen_atlas_path)
        error('Enter path where Allen CCF is stored at Line 26');
    end
    tv = readNPY([allen_atlas_path filesep 'template_volume_10um.npy']); % grey-scale "background signal intensity"
    av = readNPY([allen_atlas_path filesep 'annotation_volume_10um_by_index.npy']); % the number at each pixel labels the area, see note below
    st = loadStructureTree([allen_atlas_path filesep 'structure_tree_safe_2017.csv']); % a table of what all the labels mean
end

% Load the colormap (located in the repository, find by associated fcn)
allenCCF_path = fileparts(which('allenCCFbregma'));
cmap_filename = [allenCCF_path filesep 'allen_ccf_colormap_2017.mat'];
load(cmap_filename);

% Set up the gui
window_atlas_gui = figure('Toolbar','none','Menubar','none','color','w', ...
    'Name','Atlas-window viewer','Units','normalized','Position',[0.2,0.2,0.7,0.7]);

% Set up the atlas axes
axes_atlas = subplot(2,2,[1 3]);
[~, brain_outline] = plotBrainGrid([],axes_atlas);
hold(axes_atlas,'on');
axis vis3d equal off manual
view([-30,25]);
caxis([0 300]);
[ap_max,dv_max,ml_max] = size(tv);
xlim([-10,ap_max+10])
ylim([-10,ml_max+10])
zlim([-10,dv_max+10])

% Set up the window area axes
%top of window
axes_window_areas = subplot(2,2,2);
axes_window_areas.ActivePositionProperty = 'position';
window_areas_plot = image(nan(166,166)); %initialized for 5mm window at 30um resolution
axis off auto
window_areas_text = text(axes_window_areas,zeros(1,100),zeros(1,100),repmat({''}, 1, 100));
set(axes_window_areas,'FontSize',11,'XLimSpec', 'Tight','YLimSpec', 'Tight');
colormap(axes_window_areas,cmap);
caxis([1,size(cmap,1)])
%bottom of window
axes_window_areas_bottom = subplot(2,2,4);
axes_window_areas_bottom.ActivePositionProperty = 'position';
window_areas_bottom_plot = image(nan(166,166)); %initialized for 5mm window at 30um resolution
axis off auto
window_areas_bottom_text = text(axes_window_areas_bottom,zeros(1,100),zeros(1,100),repmat({''}, 1, 100));
set(axes_window_areas_bottom,'FontSize',11,'XLimSpec', 'Tight','YLimSpec', 'Tight');
colormap(axes_window_areas_bottom,cmap);
caxis([1,size(cmap,1)])

% Position the axes
set(axes_atlas,'Position',[-0.15,-0.1,1,1.2]);
set(axes_window_areas,'Position',[0.7,0.55,0.2,0.34]);
set(axes_window_areas_bottom,'Position',[0.7,0.1,0.2,0.34]);

% Set the current axes to the atlas (dirty, but some gca requirements)
axes(axes_atlas);

% Set up the window reference/actual
window_centre = [bregma(1),bregma(3),0];
window_depth = 60; % effective z-range of 2p microscope (in 10 ums)
window_diameter = 500; % diameter in 10 ums
window_vector = [window_centre',[window_centre(1),window_centre(2),window_depth]'];
window_vector_line = line(window_vector(1,:),window_vector(2,:),window_vector(3,:),'linewidth',3,'color','b','linestyle','-');
window_rads = linspace(-pi,pi,100);
window_circle = line(0.5*window_diameter*cos(window_rads)+window_centre(1),...
    0.5*window_diameter*sin(window_rads)+window_centre(2),...
    zeros(size(window_rads))+window_centre(3),...
    'linewidth',1.5,'color','r','linestyle','--');

% add titles for window plots
title(axes_window_areas,'Top of window');
title(axes_window_areas_bottom,[sprintf('Bottom (%i ',10*window_depth),'\mu','m)']);

% Set up the text to display coordinates
window_coordinates_text = uicontrol('Style','text','String','', ...
    'Units','normalized','Position',[0,0.95,1,0.05], ...
    'BackgroundColor','w','HorizontalAlignment','left','FontSize',12);

% Store data
gui_data.tv = tv; % Intensity atlas
gui_data.av = av; % Annotated atlas
gui_data.st = st; % Labels table
gui_data.cmap = cmap; % Atlas colormap
gui_data.bregma = bregma; % Bregma for external referencing
gui_data.window_depth = window_depth; % Effective z-depth under window
gui_data.structure_plot_idx = []; % Plotted structures
gui_data.window_angle = [0;0]; % window angles in ML/DV
gui_data.window_centre = window_centre; % Window reference centre on 3D atlas
gui_data.window_diameter = window_diameter;
gui_data.ref_centre = window_centre;

%Store handles
gui_data.handles.cortex_outline = brain_outline;
gui_data.handles.structure_patch = []; % Plotted structures
gui_data.handles.axes_atlas = axes_atlas; % Axes with 3D atlas
gui_data.handles.axes_window_areas = axes_window_areas; % Axes with window areas
gui_data.handles.axes_window_areas_bottom = axes_window_areas_bottom; % Axes with window areas
gui_data.handles.slice_plot = surface('EdgeColor','none'); % Slice on 3D atlas
gui_data.handles.slice_volume = 'tv'; % The volume shown in the slice
gui_data.handles.window_vector = window_vector_line; % Window centre vector on 3D atlas
gui_data.handles.window_circle = window_circle; % Window circle on 3D atlas
gui_data.handles.window_areas_plot = window_areas_plot; % Color-coded window regions
gui_data.handles.window_areas_bottom_plot = window_areas_bottom_plot; % Color-coded window regions
gui_data.handles.window_areas_text = window_areas_text; % Labels for window regions
gui_data.handles.window_areas_bottom_text = window_areas_bottom_text; % Labels for window regions
gui_data.window_coordinates_text = window_coordinates_text; % Window coordinates text

% Make 3D rotation the default state (toggle on/off with 'r')
h = rotate3d(axes_atlas);
h.Enable = 'on';
% Update the slice whenever a rotation is completed
%h.ActionPostCallback = @update_slice;

% Set functions for key presses
hManager = uigetmodemanager(window_atlas_gui);
[hManager.WindowListenerHandles.Enabled] = deal(false);
set(window_atlas_gui,'KeyPressFcn',@key_press);
set(window_atlas_gui,'KeyReleaseFcn',@key_release);

% Upload gui_data
guidata(window_atlas_gui, gui_data);

% Display the first slice and update the window position
update_slice(window_atlas_gui);
update_window_coordinates(window_atlas_gui);

% Display controls
display_controls;

end

function key_press(window_atlas_gui,eventdata)

% Get guidata
gui_data = guidata(window_atlas_gui);

switch eventdata.Key

    case 'uparrow'
        if isempty(eventdata.Modifier)
            % Up: move window anterior
            ap_offset = -10;
            set(gui_data.handles.window_circle,'XData',get(gui_data.handles.window_circle,'XData') + ap_offset);
            set(gui_data.handles.window_vector,'XData',get(gui_data.handles.window_vector,'XData') + ap_offset);
            gui_data.window_centre = gui_data.window_centre + [ap_offset,0,0];
        elseif any(strcmp(eventdata.Modifier,'shift'))
            % Ctrl-up: increase DV angle
            angle_change = [1;0];
            gui_data = update_window_angle(window_atlas_gui,angle_change);
        elseif any(strcmp(eventdata.Modifier,'alt'))
            % Alt-up: raise window
            dv_offset = -10;
            old_window_vector = cell2mat(get(gui_data.handles.window_vector,{'XData','YData','ZData'})');
            old_window_circle = cell2mat(get(gui_data.handles.window_circle,{'XData','YData','ZData'})');
            move_vector = diff(old_window_vector,[],2)./ ...
                norm(diff(old_window_vector,[],2))*dv_offset;
            new_window_vector = bsxfun(@plus,old_window_vector,move_vector);
            new_window_circle = bsxfun(@plus,old_window_circle,move_vector);
            set(gui_data.handles.window_vector,'XData',new_window_vector(1,:), ...
                'YData',new_window_vector(2,:),'ZData',new_window_vector(3,:));
            set(gui_data.handles.window_circle,'XData',new_window_circle(1,:), ...
                'YData',new_window_circle(2,:),'ZData',new_window_circle(3,:));
            gui_data.window_centre = gui_data.window_centre + move_vector';

        end

    case 'downarrow'
        if isempty(eventdata.Modifier)
            % Down: move window posterior
            ap_offset = 10;
            set(gui_data.handles.window_circle,'XData',get(gui_data.handles.window_circle,'XData') + ap_offset);
            set(gui_data.handles.window_vector,'XData',get(gui_data.handles.window_vector,'XData') + ap_offset);
            gui_data.window_centre = gui_data.window_centre + [ap_offset,0,0];
        elseif any(strcmp(eventdata.Modifier,'shift'))
            % Ctrl-down: decrease DV angle
            angle_change = [-1;0];
            gui_data = update_window_angle(window_atlas_gui,angle_change);
        elseif any(strcmp(eventdata.Modifier,'alt'))
            % Alt-down: lower window
            dv_offset = 10;
            old_window_vector = cell2mat(get(gui_data.handles.window_vector,{'XData','YData','ZData'})');
            old_window_circle = cell2mat(get(gui_data.handles.window_circle,{'XData','YData','ZData'})');
            move_vector = diff(old_window_vector,[],2)./ ...
                norm(diff(old_window_vector,[],2))*dv_offset;
            new_window_vector = bsxfun(@plus,old_window_vector,move_vector);
            new_window_circle = bsxfun(@plus,old_window_circle,move_vector);
            set(gui_data.handles.window_vector,'XData',new_window_vector(1,:), ...
                'YData',new_window_vector(2,:),'ZData',new_window_vector(3,:));
            set(gui_data.handles.window_circle,'XData',new_window_circle(1,:), ...
                'YData',new_window_circle(2,:),'ZData',new_window_circle(3,:));
            gui_data.window_centre = gui_data.window_centre + move_vector';

        end

    case 'rightarrow'
        if isempty(eventdata.Modifier)
            % Right: move window right
            ml_offset = 10;
            set(gui_data.handles.window_circle,'YData',get(gui_data.handles.window_circle,'YData') + ml_offset);
            set(gui_data.handles.window_vector,'YData',get(gui_data.handles.window_vector,'YData') + ml_offset);
            gui_data.window_centre = gui_data.window_centre + [0,ml_offset,0];
        elseif any(strcmp(eventdata.Modifier,'shift'))
            % Ctrl-right: increase vertical angle
            angle_change = [0;1];
            gui_data = update_window_angle(window_atlas_gui,angle_change);
        end

    case 'leftarrow'
        if isempty(eventdata.Modifier)
            % Left: move window left
            ml_offset = -10;
            set(gui_data.handles.window_circle,'YData',get(gui_data.handles.window_circle,'YData') + ml_offset);
            set(gui_data.handles.window_vector,'YData',get(gui_data.handles.window_vector,'YData') + ml_offset);
            gui_data.window_centre = gui_data.window_centre + [0,ml_offset,0];
        elseif any(strcmp(eventdata.Modifier,'shift'))
            % Ctrl-left: decrease vertical angle
            angle_change = [0;-1];
            gui_data = update_window_angle(window_atlas_gui,angle_change);
        end

    case 'c'
        % Bring up controls again
        display_controls;

    case 'b'
        % Toggle brain outline visibility
        current_visibility = gui_data.handles.cortex_outline.Visible;
        switch current_visibility; case 'on'; new_visibility = 'off'; case 'off'; new_visibility = 'on'; end;
        set(gui_data.handles.cortex_outline,'Visible',new_visibility);

    case 'a'
        % Toggle plotted structure visibility
        if ~isempty(gui_data.structure_plot_idx)
            current_alpha = get(gui_data.handles.structure_patch(1),'FaceAlpha');
            switch current_alpha
                case 0
                    new_alpha = 0.2;
                case 0.2
                    new_alpha = 1;
                case 1
                    new_alpha = 0;
            end
            set(gui_data.handles.structure_patch,'FaceAlpha',new_alpha);
        end

    case 's'
        % Toggle slice volume/visibility
        slice_volumes = {'tv','av','none'};
        new_slice_volume = slice_volumes{circshift( ...
            strcmp(gui_data.handles.slice_volume,slice_volumes),[0,1])};

        if strcmp(new_slice_volume,'none')
            set(gui_data.handles.slice_plot,'Visible','off');
        else
            set(gui_data.handles.slice_plot,'Visible','on');
        end

        gui_data.handles.slice_volume = new_slice_volume;
        guidata(window_atlas_gui, gui_data);

        update_slice(window_atlas_gui);

    case 'w'
        % Toggle window visibility
        current_visibility = gui_data.handles.window_circle.Visible;
        switch current_visibility; case 'on'; new_visibility = 'off'; case 'off'; new_visibility = 'on'; end;
        set(gui_data.handles.window_circle,'Visible',new_visibility);

    case 'm'
        % Set window position manually and find window angle automatically
        set_window_position(window_atlas_gui);
        % Get updated guidata
        gui_data = guidata(window_atlas_gui);

    case {'equal','add'}
        % Add structure(s) to display
        slice_spacing = 10;

        % Prompt for which structures to show (only structures which are
        % labelled in the slice-spacing downsampled annotated volume)

        if ~any(strcmp(eventdata.Modifier,'shift'))
            % (no shift: list in native CCF order)

            parsed_structures = unique(reshape(gui_data.av(1:slice_spacing:end, ...
                1:slice_spacing:end,1:slice_spacing:end),[],1));
            if ~any(strcmp(eventdata.Modifier,'alt'))
                % (no alt: list all)
                plot_structures_parsed = listdlg('PromptString','Select a structure to plot:', ...
                    'ListString',gui_data.st.safe_name(parsed_structures),'ListSize',[520,500]);
                plot_structures = parsed_structures(plot_structures_parsed);
            else
                % (alt: search list)
                structure_search = lower(inputdlg('Search structures'));
                structure_match = find(contains(lower(gui_data.st.safe_name),structure_search));
                list_structures = intersect(parsed_structures,structure_match);
                if isempty(list_structures)
                    error('No structure search results')
                end

                plot_structures_parsed = listdlg('PromptString','Select a structure to plot:', ...
                    'ListString',gui_data.st.safe_name(list_structures),'ListSize',[520,500]);
                plot_structures = list_structures(plot_structures_parsed);
            end

            if ~isempty(plot_structures)
                for curr_plot_structure = reshape(plot_structures,1,[])
                    % If this label isn't used, don't plot
                    if ~any(reshape(gui_data.av( ...
                            1:slice_spacing:end,1:slice_spacing:end,1:slice_spacing:end),[],1) == curr_plot_structure)
                        disp(['"' gui_data.st.safe_name{curr_plot_structure} '" is not parsed in the atlas'])
                        continue
                    end

                    gui_data.structure_plot_idx(end+1) = curr_plot_structure;

                    plot_structure_color = hex2dec(reshape(gui_data.st.color_hex_triplet{curr_plot_structure},2,[])')./255;
                    structure_3d = isosurface(permute(gui_data.av(1:slice_spacing:end, ...
                        1:slice_spacing:end,1:slice_spacing:end) == curr_plot_structure,[3,1,2]),0);

                    if isempty(gui_data.handles.structure_patch)
                        structure_alpha = 0.2;
                    else
                        structure_alpha = get(gui_data.handles.structure_patch(1),'FaceAlpha');
                    end
                    gui_data.handles.structure_patch(end+1) = patch('Vertices',structure_3d.vertices*slice_spacing, ...
                        'Faces',structure_3d.faces, ...
                        'FaceColor',plot_structure_color,'EdgeColor','none','FaceAlpha',structure_alpha);
                end
            end

        elseif any(strcmp(eventdata.Modifier,'shift'))
            % (shift: use hierarchy search)
            if ~any(strcmp(eventdata.Modifier,'control'))
                % (no ctrl, choose between all structures)
                plot_structures = hierarchicalSelect(gui_data.st);
            else
                % (ctrl: take pre-defined regions: isocortex, cerebellum, SC & IC)
                plot_structures = [7,13,31,108,115,122,158,221,239,246,253,279,298,340,361,368,374,381,809,813,1016,1017];
            end

            if ~isempty(plot_structures) % will be empty if dialog was cancelled
                % get all children of this one
                for plot_structure = plot_structures
                    thisID = gui_data.st.id(plot_structure);
                    idStr = sprintf('/%d/', thisID);
                    theseCh = find(cellfun(@(x)contains(x,idStr), gui_data.st.structure_id_path));

                    % plot the structure
                    slice_spacing = 5;
                    plot_structure_color = hex2dec(reshape(gui_data.st.color_hex_triplet{plot_structure},3,[]))./255;
                    structure_3d = isosurface(permute(ismember(gui_data.av(1:slice_spacing:end, ...
                        1:slice_spacing:end,1:slice_spacing:end),theseCh),[3,1,2]),0);

                    if isempty(gui_data.handles.structure_patch)
                        structure_alpha = 0.2;
                    else
                        structure_alpha = get(gui_data.handles.structure_patch(1),'FaceAlpha');
                    end
                    gui_data.structure_plot_idx(end+1) = plot_structure;
                    gui_data.handles.structure_patch(end+1) = patch('Vertices',structure_3d.vertices*slice_spacing, ...
                        'Faces',structure_3d.faces, ...
                        'FaceColor',plot_structure_color,'EdgeColor','none','FaceAlpha',structure_alpha);

                end

            end

        end

    case {'hyphen','subtract'}
        % Remove structure(s) already plotted
        if ~isempty(gui_data.structure_plot_idx)
            remove_structures = listdlg('PromptString','Select a structure to remove:', ...
                'ListString',gui_data.st.safe_name(gui_data.structure_plot_idx));
            delete(gui_data.handles.structure_patch(remove_structures))
            gui_data.structure_plot_idx(remove_structures) = [];
            gui_data.handles.structure_patch(remove_structures) = [];
        end

    case 'x'
        % Export the window vector coordinates in Allen CCF to the workspace
        window_vector = cell2mat(get(gui_data.handles.window_vector_line,{'XData','YData','ZData'})');
        window_vector_ccf = round(window_vector([1,3,2],:))';
        assignin('base','window_vector_ccf',window_vector_ccf)
        disp('Copied window vector coordinates to workspace');

end

% Upload gui_data
guidata(window_atlas_gui, gui_data);

end

function key_release(window_atlas_gui,eventdata)

% Get guidata
gui_data = guidata(window_atlas_gui);

switch eventdata.Key
    case {'rightarrow','leftarrow','uparrow','downarrow'}
        % Update the window info/slice on arrow release
        update_window_coordinates(window_atlas_gui);
        update_slice(window_atlas_gui);
end

% Upload gui_data
guidata(window_atlas_gui, gui_data);

end

function update_slice(window_atlas_gui,varargin)

% Get guidata
gui_data = guidata(window_atlas_gui);

% Only update the slice if it's visible
if strcmp(gui_data.handles.slice_plot(1).Visible,'on')

    offsets = [0,gui_data.window_depth];
    plot_handles = {'window_areas','window_areas_bottom'};

    for islice = 1:length(offsets)

        % Get current window outline coordinate
        curr_window_circle = cell2mat(get(gui_data.handles.window_circle,{'XData','YData','ZData'})');
        window_vector = cell2mat(get(gui_data.handles.window_vector,{'XData','YData','ZData'})');
        move_vector = diff(window_vector,[],2)./ ...
            norm(diff(window_vector,[],2))*offsets(islice);
        window_circle = bsxfun(@plus,curr_window_circle,move_vector);
        window_centre = gui_data.window_centre + move_vector';

        % Get two vectors on the window plane
        vlength = size(window_circle,2);
        window_vector_1 = window_circle(:,1)' - window_circle(:,round(0.4*vlength))';
        window_vector_2 = window_circle(:,1)' - window_circle(:,round(0.7*vlength))';

        %get the normal vector of the plane
        normal_vector = cross(window_vector_1,window_vector_2);

        % Get the plane offset through the window centre
        window_top = window_centre;
        plane_offset = -(normal_vector*window_top');

        % Define a plane of points to index
        % (the plane grid is defined based on the which cardinal plan is most
        % orthogonal to the plotted plane. this is janky but it works)
        slice_px_space = 3;
        [~,cam_plane] = max(abs(normal_vector./norm(normal_vector)));

        switch cam_plane

            case 1
                [plane_y,plane_z] = meshgrid(1:slice_px_space:size(gui_data.tv,3),1:slice_px_space:size(gui_data.tv,2));
                plane_x = ...
                    (normal_vector(2)*plane_y+normal_vector(3)*plane_z + plane_offset)/ ...
                    -normal_vector(1);

            case 2
                [plane_x,plane_z] = meshgrid(1:slice_px_space:size(gui_data.tv,1),1:slice_px_space:size(gui_data.tv,2));
                plane_y = ...
                    (normal_vector(1)*plane_x+normal_vector(3)*plane_z + plane_offset)/ ...
                    -normal_vector(2);

            case 3
                [plane_x,plane_y] = meshgrid(1:slice_px_space:size(gui_data.tv,1),1:slice_px_space:size(gui_data.tv,3));
                plane_z = ...
                    (normal_vector(1)*plane_x+normal_vector(2)*plane_y + plane_offset)/ ...
                    -normal_vector(3);

        end

        % Get the coordinates on the plane
        x_idx = round(plane_x);
        y_idx = round(plane_y);
        z_idx = round(plane_z);

        % make a mask for the window (in the horizontal plane)
        window_mask = uint8(((plane_x-window_centre(1)).^2 + (plane_y-window_centre(2)).^2 + (plane_z-window_centre(3)).^2)<(gui_data.window_diameter/2)^2);

        % Find plane coordinates in bounds with the volume and the window
        use_xd = x_idx > 0 & x_idx < size(gui_data.tv,1);
        use_yd = y_idx > 0 & y_idx < size(gui_data.tv,3);
        use_zd = z_idx > 0 & z_idx < size(gui_data.tv,2);
        use_idx = use_xd & use_yd & use_zd & window_mask;

        curr_slice_idx = sub2ind(size(gui_data.tv),x_idx(use_idx),z_idx(use_idx),y_idx(use_idx));

        % Find plane coordinates that contain brain
        curr_slice_isbrain = false(size(use_idx));
        curr_slice_isbrain(use_idx) = gui_data.av(curr_slice_idx) > 1;

        % Index coordinates in bounds + with brain
        grab_pix_idx = sub2ind(size(gui_data.tv),x_idx(curr_slice_isbrain),z_idx(curr_slice_isbrain),y_idx(curr_slice_isbrain));

        % Grab pixels from (selected) volume
        curr_slice = nan(size(use_idx));
        switch gui_data.handles.slice_volume
            case 'tv'
                curr_slice(curr_slice_isbrain) = gui_data.tv(grab_pix_idx);
                colormap(gui_data.handles.axes_atlas,'gray');
                caxis([0,255]);
            case 'av'
                curr_slice(curr_slice_isbrain) = gui_data.av(grab_pix_idx);
                colormap(gui_data.handles.axes_atlas,gui_data.cmap);
                caxis([1,size(gui_data.cmap,1)]);
        end

        % Update the slice display
        if islice==2 %1 = top, 2 = bottom
            set(gui_data.handles.slice_plot,'XData',plane_x,'YData',plane_y,'ZData',plane_z,'CData',curr_slice);
        end

        % Update the title with new depth
        if islice==2
            set(eval(['gui_data.handles.axes_',plot_handles{islice},'.Title']),'String',[sprintf('Bottom (%i ',10*offsets(islice)),'\mu','m)']);
        end

        % Update the window overview
        curr_slice_av = nan(size(use_idx));
        curr_slice_av(curr_slice_isbrain) = gui_data.av(grab_pix_idx);
        x_edges = [find(sum(window_mask,1),1,'first'),find(sum(window_mask,1),1,'last')];
        y_edges = [find(sum(window_mask,2),1,'first'),find(sum(window_mask,2),1,'last')];
        cropped_window = curr_slice_av(y_edges(1):y_edges(2),x_edges(1):x_edges(2))';
        set(eval(['gui_data.handles.',plot_handles{islice},'_plot']),'CData',cropped_window);

        %update area labels based on which areas are contained in window
        window_areas = cropped_window;
        unique_areas = unique(window_areas(~isnan(window_areas(:))));
        unique_parent_struct = unique(gui_data.st.parent_structure_id(unique_areas));
        for i_struct = 1:100
            if i_struct<=length(unique_parent_struct)
                areas_in_parent_struct = find(gui_data.st.parent_structure_id==unique_parent_struct(i_struct));
                area_bm = ismember(window_areas,areas_in_parent_struct);
                area_bm_largest = zeros(size(area_bm));
                cc = bwconncomp(area_bm);
                [~,largest_blob] = max(cellfun('length',cc.PixelIdxList));
                area_bm_largest(cc.PixelIdxList{largest_blob}) = 1;
                [area_x,area_y] = find(area_bm_largest==1);
                area_centre = [mean(area_y),mean(area_x)];
                area_string = gui_data.st.acronym{find(gui_data.st.id==unique_parent_struct(i_struct))};
            else
                area_centre = [0,0];
                area_string = '';
            end
            set(eval(['gui_data.handles.',plot_handles{islice},'_text(',num2str(i_struct),')']),...
                'Position',area_centre,'String',area_string,...
                'VerticalAlignment','middle','HorizontalAlignment','center');
        end
    end

    % Upload gui_data
    guidata(window_atlas_gui, gui_data);

end

end

function set_window_position(window_atlas_gui,varargin) %this function should place window at tangent plane

% Get guidata
gui_data = guidata(window_atlas_gui);

prompt_text = { ...
    'AP position (mm from bregma)', ...
    'ML position (mm from bregma)', ...
    'window diameter (mm)', ...
    'maximum depth (microns)'};

new_window_position = cellfun(@str2num,inputdlg(prompt_text,'Set window position',1));

% Convert centre position: mm->CCF
window_centre_ccf_coords = round(gui_data.bregma([1,3])' - 100*[new_window_position(1);-new_window_position(2)]);%not sure why we need this sign change...
window_diameter = new_window_position(3)*100;
window_depth = new_window_position(4)/10;

%load surface data or generate it from scratch
if ~exist('surf_coords','var')
    try
        load('dorsalsurface_nomidline_ccf_coords.mat')
    catch
        %CODE TO GENERATE SURFACE MAP
        surf_coords = nan(size(gui_data.av,1),size(gui_data.av,3));
        for i=1:size(gui_data.av,1)
            for j=1:size(gui_data.av,3)
                idxs = find(squeeze(gui_data.av(i,:,j))>1,1,'first');
                if ~isempty(idxs)
                    surf_coords(i,j) = idxs;
                end
            end
            idx_c = round(size(gui_data.av,3)/2);
            [m,idx_m] = min(surf_coords(i,:));
            idx_m_c = idx_m-idx_c;
            surf_coords(i,idx_c-abs(idx_m_c):idx_c+abs(idx_m_c)) = m;
        end
        save('dorsalsurface_nomidline_ccf_coords.mat',surf_coords);
    end
end

%figure out angle of tangent plane
surf_depth_coords = surf_ccf_coords(window_centre_ccf_coords(1),window_centre_ccf_coords(2));
centre_surf_ccf_coords = [window_centre_ccf_coords(1); surf_depth_coords; window_centre_ccf_coords(2)];
centre_patch = surf_ccf_coords(window_centre_ccf_coords(1)-50:window_centre_ccf_coords(1)+50,window_centre_ccf_coords(2)-50:window_centre_ccf_coords(2)+50);
centre_patch_smooth = imgaussfilt(centre_patch,'FilterSize',9);
[fx,fy] = gradient(centre_patch_smooth,1);
avg_gradient = [-nanmean(fy(:));nanmean(fx(:))];
window_angle_rad = atan(avg_gradient);
window_angle_deg = (window_angle_rad/(2*pi))*360;

%compute new window vector and circle (horizontal)
%window_diameter = gui_data.window_diameter;
window_rads = linspace(-pi,pi,100);
window_centre = [window_centre_ccf_coords', surf_depth_coords-gui_data.ref_centre(3)];
window_circle = [0.5*window_diameter*cos(window_rads)+window_centre(1);...
    0.5*window_diameter*sin(window_rads)+window_centre(2);...
    zeros(size(window_rads))+window_centre(3)];
window_vector = [window_centre',(window_centre+[0,0,window_depth])'];

%rotate these by the tangent angle
eul = [0;window_angle_rad]; %[yaw; pitch; roll]
R = eul2rotm(eul'); %find rotation matrix
window_circle_centred = window_circle - window_centre'; %translate to origin by subtracting its centre coords
window_vector_centred = window_vector - window_centre';
new_window_circle_centred = R*window_circle_centred; %rotate around origin
new_window_vector_centred = R*window_vector_centred;
new_window_circle = new_window_circle_centred + window_centre'; %translate back to its position
new_window_vector = new_window_vector_centred + window_centre';

% update gui_data
set(gui_data.handles.window_circle,'XData',new_window_circle(1,:), ...
    'YData',new_window_circle(2,:), ...
    'ZData',new_window_circle(3,:));
set(gui_data.handles.window_vector,'XData',new_window_vector(1,:), ...
    'YData',new_window_vector(2,:), ...
    'ZData',new_window_vector(3,:));
gui_data.window_angle = window_angle_deg;
gui_data.window_centre = [window_centre_ccf_coords', surf_depth_coords];
gui_data.window_diameter = window_diameter;
gui_data.window_depth = window_depth;

% Upload gui_data
guidata(window_atlas_gui, gui_data);

% Update the slice and window coordinates
update_slice(window_atlas_gui);
update_window_coordinates(window_atlas_gui);

end

function gui_data = update_window_angle(window_atlas_gui,angle_change)

% Get guidata
gui_data = guidata(window_atlas_gui);

% Set new angle
new_angle = gui_data.window_angle + angle_change;
gui_data.window_angle = new_angle;

% Get the positions of the window circle and vector
window_circle = cell2mat(get(gui_data.handles.window_circle,{'XData','YData','ZData'})');
window_vector = cell2mat(get(gui_data.handles.window_vector,{'XData','YData','ZData'})');

% Compute the rotation matrix
eul = [0;(angle_change/360)*2*pi];
R = eul2rotm(eul');

% temporarily translate window position to origin
window_circle_centred = window_circle - gui_data.window_centre';
window_vector_centred = window_vector - gui_data.window_centre';

% rotate the vector around the origin using rotation matrix
new_window_circle_centred = R*window_circle_centred;
new_window_vector_centred = R*window_vector_centred;

% translate rotated window back to the original centre point
new_window_circle = new_window_circle_centred + gui_data.window_centre';
new_window_vector = new_window_vector_centred + gui_data.window_centre';

% update gui_data
set(gui_data.handles.window_circle,'XData',new_window_circle(1,:), ...
    'YData',new_window_circle(2,:), ...
    'ZData',new_window_circle(3,:));
set(gui_data.handles.window_vector,'XData',new_window_vector(1,:), ...
    'YData',new_window_vector(2,:), ...
    'ZData',new_window_vector(3,:));

% Upload gui_data
guidata(window_atlas_gui, gui_data);

end

function update_window_coordinates(window_atlas_gui,varargin)

% Get guidata
gui_data = guidata(window_atlas_gui);

window_bregma_coordinate = round((gui_data.bregma([1,3,2]) - gui_data.window_centre)*10);

% Update the text
window_text = ['Window position: ' ....
    num2str(window_bregma_coordinate(1)) ' AP, ', ...
    num2str(-window_bregma_coordinate(2)) ' ML, ', ...
    num2str(-window_bregma_coordinate(3)) ' DV, ', ...
    num2str(round(gui_data.window_angle(1))) char(176) ' pitch, ' ...
    num2str(round(gui_data.window_angle(2))) char(176) ' roll'];
set(gui_data.window_coordinates_text,'String',window_text);

% Upload gui_data
guidata(window_atlas_gui, gui_data);

end

function display_controls

% Print controls
CreateStruct.Interpreter = 'tex';
CreateStruct.WindowStyle = 'non-modal';
msgbox( ...
    {'\fontsize{12}' ...
    '\bf Window: \rm' ...
    'Arrow keys : translate window' ...
    'Alt/Option up/down : raise/lower window' ...
    'Shift arrow keys : change window angle' ...
    'm : set location manually (tangent to surface)', ...
    '\bf 3D brain areas: \rm' ...
    ' =/+ : add (list selector)' ...
    ' Alt/Option =/+ : add (search)' ...
    ' Shift =/+ : add (hierarchy selector)' ...
    ' Ctrl Shift =/+ : add all regions on surface' ...
    ' - : remove', ...
    '\bf Visibility: \rm' ...
    's : atlas slice (toggle tv/av/off)' ...
    'b : brain outline' ...
    'w : window outline' ...
    'a : 3D brain areas (toggle transparency)' ...
    '\bf Other: \rm' ...
    'x : export window coordinates to workspace' ...
    'c : bring up controls box'}, ...
    'Controls',CreateStruct);

end


%% Example
% real time control of the KUKA iiwa 7 R 800
% Moving first joint of the robot, using a sinisoidal function
% control is executed at joint velocity level

% An example script, it is used to show how to use the different
% functions of the KUKA Sunrise matlab toolbox

% First run the following script in Matlab
% Then start the client on the KUKA iiwa controller

% Mohammad SAFEEA, 8th-August-2018

close all,clear all;clc;

warning('off')

ip='172.31.1.147'; % The IP of the controller
% start a connection with the server
global t_Kuka;
t_Kuka=net_establishConnection( ip );

if ~exist('t_Kuka','var') || isempty(t_Kuka) || strcmp(t_Kuka.Status,'closed')
  disp('Connection could not be establised, script aborted');
  return;
end

%% Go to initial position

jPos={0,0,0,0,0,0,0};

setBlueOn(t_Kuka); % turn on blue light

relVel=0.25;
movePTPJointSpace( t_Kuka , jPos, relVel); % move to initial configuration

%% Start direct servo in joint space
realTime_startVelControlJoints( t_Kuka );

w=2; % motion constants, frequency rad/sec
A=pi/6; % motion constants, amplitude of motion
counter=0;
jvel={0,0,0,0,0,0,0};
%% Initiate timing variables
dt=0;
tic;
t0=toc; % calculate initial time
%% Control loop
try
    while(dt<(8*pi/w))
        %% Perform velocity command calculation here
        time=toc;
        dt=time-t0;
        jvel{1}=A*w*sin(w*dt);
        counter=counter+1;
        %% Send joint velocties to robot
        sendJointsVelocities( t_Kuka ,jvel);
    end
    tstart=t0;
    tend=time;
    rate=counter/(tend-tstart);
    %% Stop the direct servo motion
    realTime_stopVelControlJoints( t_Kuka );
    fprintf('\nTotal execution time is %f: \n',tend-t0 );
    fprintf('\nThe rate of command update per second is: \n');
    disp(rate);
    fprintf('\n')
    pause(2);
    %% turn off light
    setBlueOff(t_Kuka);
    net_turnOffServer( t_Kuka )
    disp('Direct servo motion completed successfully')
    warning('on')
    return;
catch
    % in case of error turn off the server
    net_turnOffServer( t_Kuka );
    disp('Error during execution the direct servo motion')
    fclose(t_Kuka);
    warning('on')
end


function header = read_spike6mat_header(filename)

% read_spike6mat_header() - read Matlab files exported from Spike 6
%
% Usage:
%   >> header = read_spike6mat_header(filename);
%
% Inputs:
%   filename - [string] file name
%
% Outputs:
%   header   - FILEIO toolbox type structure
% _______________________________________________________________________
% Copyright (C) 2008 Institute of Neurology, UCL
% Vladimir Litvak

if nargin < 1
  help read_spike6mat_header;
  return;
end;

try
    vars = struct2cell(load(filename));
catch
    ft_error('File not found or wrong format.');
end

header = [];
header.nChans      = length(vars);
header.label = {};
header.orig = {};

fsample = [];
onsets = [];
lengths = [];
for i = 1:numel(vars)
    fsample(i) = round(1./vars{i}.interval);
    onsets(i)  = 1e-3*round(1e3*vars{i}.times(1));
    lengths(i) = vars{i}.length;
    header.label{i} = vars{i}.title;
    header.orig{i} = rmfield(vars{i}, {'values', 'times'});
end

if length(unique(fsample))>1 || length(unique(onsets))>1 || length(unique(lengths))>1
    ft_error('Only files with identical channel parameters are supported');
end

header.Fs          = unique(fsample);

header.nSamples    = unique(lengths);

header.nSamplesPre = -round(unique(onsets)*header.Fs);

header.nTrials     = 1;

header.label       = header.label(:);


%%*****************************************************************
%% validate: validate data
%%*****************************************************************
%% SDPT3: version 4.0
%% Copyright (c) 1997 by
%% Kim-Chuan Toh, Michael J. Todd, Reha H. Tutuncu
%% Last Modified: 16 Sep 2004
%%*****************************************************************

   function  [blk,At,C,b,dim,nnblk,parbarrier] = ...
              validate(blk,At,C,b,par,parbarrier);

   if (nargin >= 5)
      spdensity = par.spdensity;
   else
      spdensity = 0.4;
   end
%%
   if ~iscell(blk);
      error('validate: blk must be a cell array'); end;
   if (size(blk,2) < 2)
      error('validate: blk must be a cell array with at least 2 columns');
   end
   if ~iscell(At) | ~iscell(C);
      error('validate: At, C must be cell arrays');
   end
   if (size(At,1) ~= size(blk,1))
      if (size(At,2) == size(blk,1));
         At = At';
      else
         error('validate: size of At is not compatible with blk');
      end
   end
   if (size(C,1) ~= size(blk,1))
      if (size(C,2) == size(blk,1))
         C = C';
      else
         error('validate: size of C is not compatible with blk');
      end
   end
   if (min(size(b)) > 1); error('validate: b must be a vector'); end
   if (size(b,1) < size(b,2)); b = b'; end
   m = length(b);
%%
%%-----------------------------------------
%% validate blk, At, C
%%-----------------------------------------
%%
   for p = 1:size(blk,1)
      if (size(blk{p,2},1) > size(blk{p,2},2))
         blk{p,2} = blk{p,2}';
      end
      pblk = blk(p,:);
      n = sum(pblk{2});
      numblk = length(pblk{2});
      if strcmp(pblk{1},'s');
         m1 = size(At{p,1},2);
         n2 = sum(pblk{2}.*pblk{2});  n22 = sum(pblk{2}.*(pblk{2}+1))/2;
         ntotal(p) = n22;
         if ~all(size(C{p}) == n)
            error('validate: blk and C are not compatible');
         end
         if (norm(C{p}-C{p}',inf) > 1e-13*norm(C{p},inf));
            error('validate: C is not symmetric');
         end
         if (size(At{p,1}) == [m1, n22] & m1~=n22);
            At{p,1} = At{p,1}';
         end
         if (~isempty(At{p,1})) & (size(At{p,1},1) ~= n22)
            error('validate: blk and At not compatible');
         end
         if (nnz(At{p,1}) < spdensity*n22*m1)
            if ~issparse(At{p,1}); At{p,1} = sparse(At{p,1}); end
         end
         if (length(pblk) > 2) %% for low rank constraints
            len = sum(pblk{3});
            if (size(pblk{1,3},2) < size(pblk{1,3},1))
                blk{p,3} = blk{p,3}';
            end
            if any(size(At{p,2})- [n,len])
               error(' low rank structure specified in blk and At not compatible')
            end
            if (length(At(p,:)) > 2)  & ~isempty(At{p,3})
               if all(size(At{p,3},2)-[1,4])
                  error(' low rank structure in At{p,3} not specified correctly')
               end
               if (size(At{p,3},2) == 1)
                  if (size(At{p,3},1) < size(At{p,3},2)); At{p,3} = At{p,3}'; end
                  lenn = length(At{p,3});
                  constrnum = mexexpand(pblk{3},[1:length(pblk{3})]');
                  At{p,3} = [constrnum, [1:lenn]', [1:lenn]', At{p,3}];
               elseif (size(At{p,3},2) == 4)
                  dd = At{p,3};
                  [dummy,idxsort] = sort(dd(:,1));
                  dd = dd(idxsort,:);
                  lenn = size(dd,1);
                  idxD = [0; find(diff(dd(:,1))); lenn];
                  ii = zeros(lenn,1); jj = zeros(lenn,1);
                  ss = [0,cumsum(pblk{3})];
                  for k = 1:length(pblk{3})
                     idx = [idxD(k)+1 : idxD(k+1)];
                     ii(idx) = dd(idx,2)+ss(k); %% convert to cumulative indexing
                     jj(idx) = dd(idx,3)+ss(k);
                  end
                  At{p,3} = [dd(:,1),ii,jj,dd(:,4)];
               end
            else
               constrnum = mexexpand(pblk{3},[1:length(pblk{3})]');
               At{p,3} = [constrnum,  [1:len]', [1:len]', ones(len,1)];
            end
         end
         if (nnz(C{p}) < spdensity*n2) | (numblk > 1);
            if ~issparse(C{p}); C{p} = sparse(C{p}); end;
         else
            if issparse(C{p}); C{p} = full(C{p}); end;
         end
      elseif strcmp(pblk{1},'q') | strcmp(pblk{1},'l') | strcmp(pblk{1},'u');
         ntotal(p) = n;
         if (min(size(C{p})) ~= 1 | max(size(C{p})) ~= n);
            error(['validate: blk and C are not compatible']);
         end;
         if (size(C{p},1) < size(C{p},2)); C{p} = C{p}'; end
         if (size(At{p,1}) == [m, n] & m~=n);
            At{p,1} = At{p,1}';
         end
         if ~all(size(At{p,1}) == [n,m]);
            error('validate: blk and At not compatible');
         end
         if ~issparse(At{p,1});
            At{p,1} = sparse(At{p,1});
         end
         if (nnz(C{p}) < spdensity*n);
            if ~issparse(C{p}); C{p} = sparse(C{p}); end;
         else
            if issparse(C{p}); C{p} = full(C{p}); end;
         end;
      else
         error(' blk: some fields are not specified correctly');
      end
   end
   if (sum(ntotal) < m)
      error(' total dimension of C should be > length(b)');
   end
%%
%%-----------------------------------------
%% problem dimension
%%-----------------------------------------
%%
   dim = zeros(1,4);  nnblk = zeros(1,2);
   for p = 1:size(blk,1)
      pblk = blk(p,:);
      if strcmp(pblk{1},'s')
         dim(1) = dim(1) + sum(pblk{2});
         nnblk(1) = nnblk(1) + length(pblk{2});
         nn(p) = sum(pblk{2});
      elseif strcmp(pblk{1},'q')
         dim(2) = dim(2) + sum(pblk{2});
         nnblk(2) = nnblk(2) + length(pblk{2});
         nn(p) = length(pblk{2});
      elseif strcmp(pblk{1},'l')
         dim(3) = dim(3) + sum(pblk{2});
         nn(p) = sum(pblk{2});
      elseif strcmp(pblk{1},'u')
         dim(4) = dim(4) + sum(pblk{2});
         nn(p) = sum(pblk{2});
      end
   end
%%
%%-----------------------------------------
%% validate parbarrier
%%-----------------------------------------
%%
    if (nargin == 6)
       if ~iscell(parbarrier);
	  if (length(parbarrier) == size(blk,1))
	     tmp = parbarrier;
             clear parbarrier;
	     parbarrier = cell(size(blk,1),1);
	     for p = 1:size(blk,1)
		parbarrier{p} = tmp(p);
             end
          end
       end
       if (size(parbarrier,2) > size(parbarrier,1))
          parbarrier = parbarrier';
       end
       for p = 1:size(blk,1)
          pblk = blk(p,:);
          if (size(parbarrier{p},1) > size(parbarrier{p},2))
             parbarrier{p} = parbarrier{p}';
          end
          len = length(parbarrier{p});
          if strcmp(pblk{1},'s') | strcmp(pblk{1},'q')
             if (len == 1)
                parbarrier{p} = parbarrier{p}*ones(1,length(pblk{2}));
	     elseif (len == 0)
                parbarrier{p} = zeros(1,length(pblk{2}));
	     elseif (len ~= length(pblk{2}))
                error('blk and parbarrier not compatible');
             end
          elseif strcmp(pblk{1},'l')
             if (len == 1)
                parbarrier{p} = parbarrier{p}*ones(1,sum(pblk{2}));
	     elseif (len == 0)
		parbarrier{p} = zeros(1,sum(pblk{2}));
             elseif (len ~= sum(pblk{2}))
                error('blk and parbarrier not compatible');
             end
          elseif strcmp(pblk{1},'u')
             parbarrier{p}= zeros(1,sum(pblk{2}));
          end
       end
    end
%%***********************************************************************


function res = bf_output_image_mv(BF, S)
% Computes multivariate test on a number of frequency bands
% Copyright (C) 2012 Wellcome Trust Centre for Neuroimaging

% Gareth Barnes, modified from Vladimir Litvak's example code
% $Id: bf_output_image_mv.m 7703 2019-11-22 12:06:29Z guillaume $

%--------------------------------------------------------------------------

%% no covariance matrix creation -so need to check bands are within this window

if nargin == 0
    all = cfg_const;
    all.tag = 'all';
    all.name = 'All';
    all.val  = {1};

    condlabel = cfg_entry;
    condlabel.tag = 'condlabel';
    condlabel.name = 'Condition label';
    condlabel.strtype = 's';
    condlabel.val = {''};

    conditions = cfg_repeat;
    conditions.tag = 'conditions';
    conditions.name = 'Conditions';
    conditions.help = {'Specify the labels of the conditions to be included in the inversion'};
    conditions.num  = [1 Inf];
    conditions.values  = {condlabel};
    conditions.val = {condlabel};

    whatconditions = cfg_choice;
    whatconditions.tag = 'whatconditions';
    whatconditions.name = 'What conditions to include?';
    whatconditions.values = {all, conditions};
    whatconditions.val = {all};

    %     design = cfg_const;
    %     design.tag = 'design';
    %     design.name = 'design';
    %     design.help = {'Use default settings for the inversion'};
    %     design.val  = {1};

    design = cfg_files;
    design.tag = 'design';
    design.name = 'design matrix';
    design.filter = 'mat';
    design.num = [1 Inf];
    design.help = {'Select the design matrix'};

    woi = cfg_entry;
    woi.tag = 'woi';
    woi.name = 'Time windows of interest';
    woi.strtype = 'r';
    woi.num = [Inf 2];
    woi.help = {'Time windows (in ms)'};

    foi = cfg_entry;
    foi.tag = 'foi';
    foi.name = 'Frequency bands of interest';
    foi.strtype = 'r';
    foi.num = [Inf 2];
    foi.help = {'Freq bands (in Hz)'};

    datafeatures = cfg_menu;
    datafeatures.tag = 'datafeatures';
    datafeatures.name = 'Features';
    datafeatures.labels =get_data_features;
    datafeatures.values =get_data_features;

    datafeatures.help = {'Data features of interest'};
    datafeatures.val =datafeatures.values(end);

    contrast = cfg_entry;
    contrast.tag = 'contrast';
    contrast.name = 'contrast';
    contrast.strtype = 'i';
    contrast.num = [1 Inf];
    contrast.val = {1};

    result         = cfg_menu;
    result.tag     = 'result';
    result.name    = 'What to output';
    result.help    = {'Specify output type'};
    result.labels  = {
        'chi square'
        'BIC'
        'r square'
        }';
    result.values  = result.labels;
    result.val = {'chi square'};

    modality         = cfg_menu;
    modality.tag     = 'modality';
    modality.name    = 'Modality';
    modality.help    = {'Specify modality'};
    modality.labels  = {
        'MEG'
        'MEGPLANAR'
        'EEG'
        }';
    modality.values  = {
        'MEG'
        'MEGPLANAR'
        'EEG'
        }';
    modality.val = {'MEG'};

    custom = cfg_branch;
    custom.tag = 'custom';
    custom.name = 'Custom';
    custom.help = {'Define custom settings for the inversion'};
    custom.val  = {whatconditions, contrast, woi};

    isdesign = cfg_choice;
    isdesign.tag = 'isdesign';
    isdesign.name = 'Design matrix parameters';
    isdesign.help = {'Choose whether to load custom design'};
    isdesign.values = {design, custom};
    isdesign.val = {design};

    sametrials = cfg_menu;
    sametrials.tag = 'sametrials';
    sametrials.name = 'Trials same as for filters';
    sametrials.labels = {'yes', 'no'};
    sametrials.values = {true, false};
    sametrials.val = {false};
    sametrials.help = {'Take the same trials as used for filter computation',...
        'This is useful for bootstrap.'};

    image_mv      = cfg_branch;
    image_mv.tag  = 'image_mv';
    image_mv.name = 'Mv image';
    image_mv.val  = { isdesign, datafeatures, foi,  result, sametrials, modality};

    res = image_mv;

    return
elseif nargin < 2
    error('Two input arguments are required');
end

D = BF.data.D;

if isfield(S.isdesign,'custom'),
    %% gui specified conditions and contrast

    woitmp = S.isdesign.custom.woi;

    % DP - The indsample function does not work for matrices, so I have
    % looped through. Otherwise, we are left with only one windows of
    % interest.
    for wi = 1:size(woitmp,1)
        woiind(wi,:)=D.indsample(woitmp(wi,:)/1000);
        woi(wi,:)=D.time(woiind(wi,:)); %% in seconds
    end

    duration=unique(woiind(:,2)-woiind(:,1))./D.fsample; %% in sec
    if numel(duration)>1,
        error('both windows need to be the same length');
    end;

    % DP - this can result in a tiny bit of residual, presumably due to
    % numerical imprecision. Not sure. Doesn't seem necessary anyway since
    % the number of samples is equal.
%     duration=unique(woi(:,2)-woi(:,1));
%     if numel(duration)>1,
%         error('both windows need to be the same length');
%     end;
%     woi = S.isdesign.custom.woi;
%     woiind=D.indsample(woi/1000);
%     woi=D.time(woiind); %% in seconds

%     duration=unique(woiind(:,2)-woiind(:,1))./D.fsample; %% in sec
%     if numel(duration)>1,
%         error('both windows need to be the same length');
%     end;

    duration=unique(woi(:,2)-woi(:,1));
    if numel(duration)>1,
        error('both windows need to be the same length');
    end;

    %
    whatconditions=S.isdesign.custom.whatconditions;

    if isfield(whatconditions, 'all')
        if S.sametrials
            trials{1} = BF.features.trials;
        else
            trials{1} = 1:D.ntrials;
        end
        clabel{1}='all';
    else
        for i = 1:numel(whatconditions.condlabel)
            if isempty(D.indtrial(whatconditions.condlabel{i}, 'GOOD'))
                error('No trials matched the selection.');
            end

            clabel{i}=whatconditions.condlabel{i};

            if S.sametrials
                trials{i} = BF.features.trials(strmatch(clabel{i}, D.conditions(BF.features.trials)));
            else
                trials{i} = D.indtrial(whatconditions.condlabel{i}, 'GOOD');
            end

        end
        if isempty(trials)
            error('No trials matched the selection, check the specified condition labels');
        end
    end

    %%check for number of trials //// added by ANNA 30/04/2013
    for i=1:numel(trials)
        num_trials(i) = length(trials{i});
    end

    if min(num_trials)~= max(num_trials)
        warning ('Number of trials are not the same accross conditions- throwing away');
        num_trials
    end
    nt = min(num_trials); % ANNA -throw away the extra trials

    col=0;
    X=[];
    Xtrials=[];
    Xstartlatencies=[];
    xlabel=[];
    for j=1:size(woi, 1),
        for i=1:numel(trials)
            col=col+1;
            %nt=numel(trials{i}); % ANNA look above for the warning
            Xtmp=[zeros(size(X,1),1); ones(nt,1)];
            if col>1,
                X=[X;zeros(nt,size(X,2))]; %1)]; ANNA
            end;
            X=[X Xtmp];
            tlist = spm_vec(trials{i}); % ANNA list of trials
            Xtrials=[Xtrials ; tlist(1:nt)]; % ANNA list of trials - this must be a vector
            Xstartlatencies=[Xstartlatencies; ones(nt,1).*woi(j,1)];%% again this has to be a vector
            xlabel=strvcat(xlabel,[clabel{i} ',' num2str(woi(j,1))]);
        end;
    end;
%     % ORIG
%     col=0;
%     X=[];
%     Xtrials=[];
%     Xstartlatencies=[];
%     xlabel=[];
%     for j=1:size(woi, 1),
%         for i=1:numel(trials)
%             col=col+1;
%             nt=numel(trials{i});
%             Xtmp=[zeros(size(X,1),1); ones(nt,1)];
%             if col>1,
%                 X=[X;zeros(nt,1)];
%             end;
%             X=[X Xtmp];
%             Xtrials=[Xtrials ;[trials{i}]];
%             Xstartlatencies=[Xstartlatencies; ones(nt,1).*woi(j,1)];
%             xlabel=strvcat(xlabel,[clabel{i} ',' num2str(woi(j,1))]);
%         end;
%     end;

    allsamples=[D.indsample(Xstartlatencies); D.indsample(Xstartlatencies+ones(size(Xstartlatencies)).*duration)]';
    contrast=S.isdesign.custom.contrast';

else %%  conditions and contrast  specified in a file

    if ~exist(cell2mat(S.isdesign.design)),
        error('Cannot load design matrix');
    end;
    disp('loading design matrix');
    a=load(cell2mat(S.isdesign.design));
    X=a.design.X; %% design matrix
    contrast=a.design.contrast;
    ntrials=size(X,1);
    if (size(a.design.Xstartlatencies,1)~=ntrials)||(size(a.design.Xtrials,1)~=ntrials)
        error('start latencies and Xtrials and X should have a value per row of the design');
    end;

    Xtrials=a.design.Xtrials; %% indices of trials to use
    for j=1:ntrials,
        allsamples(j,1)=D.indsample(a.design.Xstartlatencies(j));
        allsamples(j,2)=D.indsample(a.design.Xstartlatencies(j)+a.design.Xwindowduration);
    end;

end;

if size(Xtrials)~=size(Xstartlatencies)
    error('Xtrials and start latencies must be the same length');
end;
if size(Xtrials,1)~=size(X,1)
    error('X must have same number of rows as trials and start latencies');
end;
if size(contrast,1)~=size(X,2),
    error('contrast needs to match number of columns in design');
end;

Fgraph  = spm_figure('GetWin','Graphics'); spm_figure('Clear',Fgraph);

subplot(4,1,1);
imagesc(X);
title('X');
set(gca,'Xtick',1:col);
set(gca,'Xticklabel',xlabel);
subplot(4,1,2);

imagesc(contrast);
title('c');
subplot(4,1,3);

imagesc(Xtrials);
title('trials');
subplot(4,1,4);

imagesc(Xstartlatencies);
title('start latency');

chanind = BF.features.(S.modality).chanind;
U       = BF.features.(S.modality).U;

Nchans=size(U,2); %% effective number of channels

nsamples = unique(allsamples(:,2)-allsamples(:,1));

if length(nsamples) > 1
    error('All time windows should be equal lentgh')
end

alltrials = spm_vec(Xtrials);
ntrials   = length(alltrials);

%% now identify frequency bands of interest

Nbands=size(S.foi,1);
windowduration=(nsamples/D.fsample);
dctfreq = (0:nsamples-1)/2/windowduration;           % DCT frequencies (Hz)
dctT      = spm_dctmtx(nsamples,nsamples);

freqstr=[];
allfreqind=[];
for fband=1:Nbands, %% allows one to break up spectrum and ignore some frequencies
    freqrange=S.foi(fband,:);
    j      = find( (dctfreq >= freqrange(1)) & (dctfreq<=freqrange(2)) );
    featureind{fband}=j;
    allfreqind=sort(unique([allfreqind j]));
    freqstr=[freqstr sprintf('%3.1f-%3.1f,',dctfreq(min(j)),dctfreq(max(j)))];
end; % for fband=1:Nbands

if isempty(allfreqind),
    error('No valid frequency range found');
end;
% Tfull      = dctT(:,allfreqind); %% A filter for all bands (not necessarily continuous)
%% end of freq band section

spm('Pointer', 'Watch');drawnow;
spm_progress_bar('Init', ntrials , 'Computing covariance'); drawnow;
if ntrials  > 100, Ibar = floor(linspace(1, ntrials ,100));
else Ibar = 1:ntrials; end

%% load in data and make up simple design matrix
%ncond=numel(samples); %% number of conditions= columns in design matrix
%Nt=ntrials*ncond; %% total number of time windows under consideration
%X=zeros(Nt,ncond);

flatdata=zeros(ntrials*nsamples,Nchans);
%% want flatdata in form Nchans,Nt*Nsamples

%count=0;
for i = 1:ntrials
    %for j = 1:numel(samples)
    %   count=count+1;
    %   X(count,j)=1;
    Y  = U'*squeeze(D(chanind, allsamples(i,1):allsamples(i,2)-1, alltrials(i)));
    Y  = detrend(Y'); %% detrend and throw away low freq drift
    flatdata((i-1)*nsamples+1:i*nsamples,:) =Y;

    %end
    if ismember(i, Ibar)
        spm_progress_bar('Set', i); drawnow;
    end
end

spm_progress_bar('Clear');

W = BF.inverse.(S.modality).W;
nvert = numel(W);
S.regressout=[]; %% turn off for now
regressout=S.regressout;

%% set up the data features
weights=-1; %% set up flag
Yfull=get_data_features(flatdata,nsamples,ntrials,weights,dctT,S.datafeatures,featureind,regressout); %% set up data structures

spm('Pointer', 'Watch');drawnow;
spm_progress_bar('Init', nvert, 'Scanning grid points'); drawnow;
if nvert > 100, Ibar = floor(linspace(1, nvert,100));
else Ibar = 1:nvert; end

outval = nan(1, nvert);

for i = 1:nvert

    if ~isnan(W{i})

        w    = W{i};

        %% returns columns of a matrix with rows as different observations
        [Yfull,vedata]=get_data_features(flatdata,nsamples,ntrials,w,dctT,S.datafeatures,featureind,regressout); %% extract the data features

        Yfull=Yfull-repmat(mean(Yfull),size(Yfull,1),1); %% remove dc level from each column/feature
        Yfull=Yfull./repmat(std(Yfull),size(Yfull,1),1); %% normalize features to have unit variance by default
        [chival,BIC,cva] = output_image_mv_cva(X,Yfull,contrast); %% run the multivariate test

        switch S.result
            case 'chi square'
                resultstr='chisq';
                outval(i) = chival(1);
            case 'r square'
                outval(i) = cva.ccorr.^2;
                resultstr='rsq';
            case 'BIC'
                outval(i)=BIC(1);
                resultstr='BIC';
        end;

    end

    if ismember(i, Ibar)
        spm_progress_bar('Set', i); drawnow;
    end
end

spm_progress_bar('Clear');

image(1).val   = outval;

image(1).label = ['mv' resultstr S.datafeatures freqstr  spm_file(D.fname, 'basename')];

res = image;


function sFileOut = out_fopen_bst(OutputFile, sFileIn, ChannelMat, EpochSize)
% OUT_FOPEN_BST: Saves the header of a new empty Brainstorm binary file.

% @=============================================================================
% 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, 2014-2019; Martin Cousineau, 2018

% Get file comment
[fPath, fBase, fExt] = bst_fileparts(OutputFile);

% Create a new header structure
sFileOut = sFileIn;
sFileOut.filename  = OutputFile;
sFileOut.condition = '';
sFileOut.format    = 'BST-BIN';
sFileOut.byteorder = 'l';
sFileOut.comment   = fBase;
sFileOut.header    = struct();
sFileOut.header.device    = sFileOut.device;
sFileOut.header.sfreq     = sFileOut.prop.sfreq;
sFileOut.header.starttime = sFileOut.prop.times(1);
sFileOut.header.navg      = sFileOut.prop.nAvg;
% sFileOut.header.version   = 51;   % April 2019
sFileOut.header.version   = 52;   % March 2023
sFileOut.header.nsamples  = round((sFileOut.prop.times(2) - sFileOut.prop.times(1)) .* sFileOut.prop.sfreq) + 1;
sFileOut.header.epochsize = EpochSize;
sFileOut.header.nchannels = length(ChannelMat.Channel);
% Force the destination compensation level
sFileOut.prop.currCtfComp = sFileOut.prop.destCtfComp;
sFileOut.header.ctfcomp   = sFileOut.prop.destCtfComp;
% Open file
fid = fopen(OutputFile, 'w+', sFileOut.byteorder);
if (fid == -1)
    error(['Could not open output file: "' OutputFile '".']);
end

% ===== FORMAT HEADER =====
fwrite(fid, 'BSTBIN', 'char');                               % CHAR(6)    : Format
fwrite(fid, sFileOut.header.version, 'uint8');               % UINT8(1)   : Version of the format
fwrite(fid, str_zeros(sFileOut.header.device, 40), 'char');  % CHAR(40)   : Device used for recording
fwrite(fid, sFileOut.header.sfreq, 'float32');               % UINT32(1)  : Sampling frequency
fwrite(fid, sFileOut.header.starttime, 'float32');           % FLOAT32(1) : Start time
fwrite(fid, sFileOut.header.navg, 'uint32');                 % UINT32(1)  : Number of files averaged
if ~isempty(sFileOut.header.ctfcomp)
    fwrite(fid, sFileOut.header.ctfcomp, 'uint8');           % UINT8(1)   : CTF compensation status (0,1,2,3)
else
    fwrite(fid, 0, 'uint8');
end
fwrite(fid, sFileOut.header.nsamples, 'uint32');             % UINT32(1)  : Total number of samples
fwrite(fid, sFileOut.header.epochsize, 'uint32');            % UINT32(1)  : Number of samples per epoch
fwrite(fid, sFileOut.header.nchannels, 'uint32');            % UINT32(1)  : Number of channels

% ===== CHANNEL LOCATIONS =====
fwrite(fid, str_zeros(ChannelMat.Comment, 40), 'char');                 % CHAR(40)   : Channel file comment
for i = 1:length(ChannelMat.Channel)
    fwrite(fid, str_zeros(ChannelMat.Channel(i).Name, 20), 'char');     % CHAR(20)   : Channel name
    fwrite(fid, str_zeros(ChannelMat.Channel(i).Type, 20), 'char');     % CHAR(20)   : Channel type
    fwrite(fid, str_zeros(ChannelMat.Channel(i).Comment, 40), 'char');  % CHAR(40)   : Channel comment
    % Loc
    fwrite(fid, size(ChannelMat.Channel(i).Loc, 2), 'uint8');           % UINT8(1)   : Number of location points
    if ~isempty(ChannelMat.Channel(i).Loc)
        fwrite(fid, ChannelMat.Channel(i).Loc, 'float32');              % FLOAT32(N) : Locations [3xN]
    end
    % Orient
    fwrite(fid, size(ChannelMat.Channel(i).Orient, 2), 'uint8');        % UINT8(1)   : Number of orientations points
    if ~isempty(ChannelMat.Channel(i).Orient)
        fwrite(fid, ChannelMat.Channel(i).Orient, 'float32');           % FLOAT32(N) : Orientations [3xN]
    end
    % Weight
    fwrite(fid, length(ChannelMat.Channel(i).Weight), 'uint8');         % UINT8(1)   : Number of weights
    if ~isempty(ChannelMat.Channel(i).Weight)
        fwrite(fid, ChannelMat.Channel(i).Weight, 'float32');           % FLOAT32(N) : Weights [1xN]
    end
    % Channel flag
    fwrite(fid, sFileIn.channelflag(i), 'int8');                        % INT8(1)    : Channel flag
end

% ===== CTF =====
% CTF compensation matrix
fwrite(fid, size(ChannelMat.MegRefCoef), 'uint32');                     % UINT32(2)  : Dimensions of the MegRefCoef matrix
fwrite(fid, ChannelMat.MegRefCoef, 'float32');                          % FLOAT32(N) : MegRefCoef matrix

% ===== SSP PROJECTORS =====
fwrite(fid, length(ChannelMat.Projector), 'uint32');                      % UINT32(1)  : Number of projectors
for i = 1:length(ChannelMat.Projector)
    fwrite(fid, str_zeros(ChannelMat.Projector(i).Comment, 40), 'char');  % CHAR(40)   : Projector comment
    fwrite(fid, size(ChannelMat.Projector(i).Components), 'uint32');      % UINT32(2)  : Dimensions of the Components matrix
    fwrite(fid, ChannelMat.Projector(i).Components, 'float32');           % FLOAT32(N) : Components matrix
    fwrite(fid, size(ChannelMat.Projector(i).CompMask), 'uint32');        % UINT32(2)  : Dimensions of the CompMask matrix
    fwrite(fid, ChannelMat.Projector(i).CompMask, 'float32');             % FLOAT32(N) : CompMask matrix
    fwrite(fid, size(ChannelMat.Projector(i).SingVal), 'uint32');         % UINT32(2)  : Dimensions of the SingVal matrix
    if ~isempty(ChannelMat.Projector(i).SingVal)
        fwrite(fid, ChannelMat.Projector(i).SingVal, 'float32');          % FLOAT32(N) : SingVal matrix
    end
    fwrite(fid, ChannelMat.Projector(i).Status, 'int8');                % INT8(1)    : Status
end

% ===== HEAD POINTS =====
if ~isempty(ChannelMat.HeadPoints)
    fwrite(fid, length(ChannelMat.HeadPoints.Label), 'uint32');              % UINT32(1)  : Number of head points
    for i = 1:length(ChannelMat.HeadPoints.Label)
        fwrite(fid, ChannelMat.HeadPoints.Loc(:,i), 'float32');              % FLOAT32(3) : (X,Y,Z) positions in meters
        fwrite(fid, str_zeros(ChannelMat.HeadPoints.Label{i}, 10), 'char');  % CHAR(10)   : Point label
        fwrite(fid, str_zeros(ChannelMat.HeadPoints.Type{i}, 10), 'char');   % CHAR(10)   : Point type
    end
else
    fwrite(fid, 0, 'uint32');
end

% ===== FIDUCIALS =====
% Write fiducials positions (SCS)
if isfield(ChannelMat, 'SCS') && ~isempty(ChannelMat.SCS) ...
&& isfield(ChannelMat.SCS, 'NAS') && (length(ChannelMat.SCS.NAS) == 3) ...
&& isfield(ChannelMat.SCS, 'LPA') && (length(ChannelMat.SCS.LPA) == 3) ...
&& isfield(ChannelMat.SCS, 'RPA') && (length(ChannelMat.SCS.RPA) == 3) ...
&& isfield(ChannelMat.SCS, 'R')   && isequal(size(ChannelMat.SCS.R), [3,3]) ...
&& isfield(ChannelMat.SCS, 'T')   && (length(ChannelMat.SCS.T) == 3) ...
&& isfield(ChannelMat.SCS, 'Origin') && (length(ChannelMat.SCS.Origin) == 3)
    fwrite(fid, 1, 'int8');                                                  % INT8(1)    : Are the fiducials saved in the file?
    fwrite(fid, ChannelMat.SCS.NAS, 'float32');                              % FLOAT32(3) : NAS (X,Y,Z) positions in meters
    fwrite(fid, ChannelMat.SCS.LPA, 'float32');                              % FLOAT32(3) : LPA (X,Y,Z) positions in meters
    fwrite(fid, ChannelMat.SCS.RPA, 'float32');                              % FLOAT32(3) : RPA (X,Y,Z) positions in meters
    fwrite(fid, ChannelMat.SCS.R, 'float32');                                % FLOAT32(9) : Rotation matrix [3x3]
    fwrite(fid, ChannelMat.SCS.T, 'float32');                                % FLOAT32(3) : Translation vector [3x1]
    fwrite(fid, ChannelMat.SCS.Origin, 'float32');                           % FLOAT32(3) : Origin (X,Y,Z) positions in meters
else
    fwrite(fid, 0, 'int8');                                                  % INT8(1)    : Are the fiducials saved in the file?
end

% ===== EVENTS =====
fwrite(fid, length(sFileOut.events), 'uint32');                              % UINT32(1)  : Number of event categories
for iEvt = 1:length(sFileOut.events)
    isExtended = (size(sFileOut.events(iEvt).times,1) == 2);
    evtLabel = sFileOut.events(iEvt).label;
    labelLength = length(evtLabel);
    nOcc = size(sFileOut.events(iEvt).times,2);
    fwrite(fid, labelLength, 'uint8');                                       % UINT8(1)   : Length of event name (1 to 255)
    fwrite(fid, str_zeros(evtLabel, labelLength), 'char');                   % CHAR(??)   : Event name
    fwrite(fid, sFileOut.events(iEvt).color, 'float32');                     % FLOAT32(3) : Event color
    fwrite(fid, isExtended, 'int8');                                         % INT8(1)    : Event type (0=regular, 1=extended)
    fwrite(fid, nOcc, 'uint32');                                             % UINT32(1)  : Number of occurrences
    % If there are event occurrences
    if ~isempty(sFileOut.events(iEvt).times)
        % Write latencies
        fwrite(fid, sFileOut.events(iEvt).times, 'float32');                 % FLOAT32(2*N) : Time in seconds
        % Write list of channels associated to each event
        isChannels = ~isempty(sFileOut.events(iEvt).channels);
        fwrite(fid, uint8(isChannels), 'uint8');                             % UINT8(1) : 1 if channels list is present, 0 otherwise
        if isChannels
            for iOcc = 1:nOcc
                nChannels = length(sFileOut.events(iEvt).channels{iOcc});
                fwrite(fid, nChannels, 'uint16');                            % UINT16(1) : Number of channels associated to this event
                for iChan = 1:nChannels
                    chLabel = sFileOut.events(iEvt).channels{iOcc}{iChan};
                    labelLength = length(chLabel);
                    fwrite(fid, labelLength, 'uint8');                       % UINT8(1) : Length of channel name (1 to 255)
                    fwrite(fid, str_zeros(chLabel, labelLength), 'char');    % CHAR(??) : Channel name
                end
            end
        end
        % Read list of notes associated to each event
        isNotes = ~isempty(sFileOut.events(iEvt).notes);
        fwrite(fid, uint8(isNotes), 'uint8');                                % UINT8(1) : 1 if notes list is present, 0 otherwise
        if isNotes
            for iOcc = 1:nOcc
                noteLabel = sFileOut.events(iEvt).notes{iOcc};
                labelLength = length(noteLabel);
                fwrite(fid, labelLength, 'uint16');                          % UINT16(1) : Length of note text
                fwrite(fid, str_zeros(noteLabel, labelLength), 'char');      % CHAR(??) : Note text
            end
        end
    end
end

% Save total header size (to allow fast skipping when reading)
sFileOut.header.hdrsize = ftell(fid);
% Close file
fclose(fid);

end

%% ===== HELPER FUNCTIONS =====
function sout = str_zeros(sin, N)
    sout = char(zeros(1,N));
    if (length(sin) <= N)
        sout(1:length(sin)) = sin;
    else
        sout = sin(1:N);
    end
end


function undersamplingRINEX(filenameIN, filenameOUT, base, step, delta, wait_dlg)

% SYNTAX:
%   undersamplingRINEX(filenameIN, filenameOUT, base, step, delta, wait_dlg);
%
% INPUT:
%   filenameIN  = input RINEX observation file
%   filenameOUT = output RINEX observation file
%   base  = base timing (e.g. if first epoch should be 13  4  5 11 35  1, then base = 1) [sec]
%   step  = new sampling rate [sec]
%   delta = original sampling rate [sec]
%   wait_dlg = optional handler to waitbar figure (optional)
%
% OUTPUT:
%
%
% DESCRIPTION:
%   Undersamples a RINEX observation file.

%--- * --. --- --. .--. ... * ---------------------------------------------
%               ___ ___ ___
%     __ _ ___ / __| _ | __|
%    / _` / _ \ (_ |  _|__ \
%    \__, \___/\___|_| |___/
%    |___/                    v 1.0RC1
%
%--------------------------------------------------------------------------
%  Copyright (C) 2021 Geomatics Research & Development srl (GReD)
%  Written by:
%  Contributors:     ...
%  A list of all the historical goGPS contributors is in CREDITS.nfo
%--------------------------------------------------------------------------
%
%   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/>.
%
%--------------------------------------------------------------------------
% 01100111 01101111 01000111 01010000 01010011
%--------------------------------------------------------------------------

%default values
if (nargin < 3)
    base  = 0;
end

if (nargin < 4)
    step  = 30;
end

if (nargin < 5)
    delta = 1;
end

% ------------------------------------------
% header copy and paste
% ------------------------------------------

fidIN  = fopen(filenameIN,'rt');
fidOUT = fopen(filenameOUT,'wt');

line = fgets(fidIN);
while (isempty(strfind(line,'END OF HEADER')))
   fprintf(fidOUT,'%s',line);
   line = fgets(fidIN);
   if (~isempty(strfind(line,'INTERVAL')))
       line = sprintf('%10.3f                                                  INTERVAL            \n', step);
   end
end
fprintf(fidOUT,line);

% fclose(fidIN);
% fclose(fidOUT);

% ------------------------------------------
% undersampling
% ------------------------------------------

while (feof(fidIN) == 0)

    % time (seconds) extraction
	sec = roundmod(str2num(line(17:27)),delta);
	%fprintf('%d\n',sec);           % debugging

	if (mod(sec-base,step) == 0)    % if it is a header message
		fprintf(fidOUT,'%s',line);
		line = fgets(fidIN);
		%fprintf('%s',line);        % debugging
		while (feof(fidIN) == 0) & ...
		      ((line(1) ~= ' ') | (line(2) == ' ') | ...
		       (line(3) == ' ') | (line(4) ~= ' '))
			fprintf(fidOUT,'%s',line);
			line = fgets(fidIN);
			%fprintf('%s',line);    % debugging
        end
        if (feof(fidIN) == 1) && ~isempty(line)
            fprintf(fidOUT,'%s',line);
        end
	else                            % if it is a data message
		line = fgets(fidIN);
		%fprintf('%s',line);        % debugging
		while (feof(fidIN) == 0) & ...
		      ((line(1) ~= ' ') | (line(2) == ' ') | ...
		       (line(3) == ' ') | (line(4) ~= ' '))
			line = fgets(fidIN);
			%fprintf('%s',line);    % debugging
		end
	end
end

fclose(fidIN);      % input file closure
fclose(fidOUT);     % output file closure