% Analyse laminin orientations in projected data of zebrafish optical cup
%  in data by Karen Soans, Caren Norden group, MPI-CBG
% Note that the input to this script are derotated and max-projected ROIs 
% from the original image stack, obtained within Fiji by the script
% extractDerotatedSurface.py by Noreen Walker, MPI-CBG.
% Here, orientations are inferred from small image patches, and the
% alignment between orientations (or their coherency) is calculated locally
% and globally across each region of interest.
%
% Karl Hoffmann, Max Planck Institute of Molecular Cell Biology and
% Genetics, Dresden, Germany
% last edit: 2022_09_05 
%
% developed using MATLAB R2019, R2020, and R2021
%
%%% usage %%%
% It is assumed that regions of interest (ROIs) of the original image stack
% were extracted, derotated by extractDerotatedSurface.py, and each saved 
% into a folder structure 
%   head_folder/
%     <Stage>/
%       <Embryo>/
%         <RegionOfTheEye>/
%           <DerotationConditions>/
%             Roi<nn>_ZMAX_of_Resliced_of_Derotated_with2ndCrop.tif
% [the pilot data had only <head_folder>/<Stage>/<Embryo>/<EyeAndChannel>/<ROI>/ ]
% Then per-ROI observables are collected in the array  output_collected 
% together with their relevant settings and conditions.
%
% Orientations are expressed as vector with coordinates (u,v) where
% anti-podal vectors (u,v) and (-u,-v) represent the same orientation,
% or as magnitude magn and angle theta (from the interval [0,pi]) with
% (u,v) = +- magn * (cos theta, sin theta).


% CHANGELOG
% 2022_08_23 clean-up for publication
% 2021_05_25 adapt plotting to downsampling
% 2021_05_18 include downsampling (i) of the input grayscale image and 
%     (ii) on the level of orientions.
% 2021_03_26 smoothing now discouraged - replaced by downsampling (on
%     grayscale or on orientations)
% 2021_03_16 curate computation of theta_for_hists
% 2021_03_12 switch to orientation estimation by the gradient method (with 
%     gradient estimate by a Scharr operator), 
%     introduced kernelSize as opposed to boxRadius
% 2021_01_20 adapted to the structure of 2021 data 



%% settings for orientation analysis
% Scharr operator with kernel size 5 is default and highly recommended for
% extremely low error in orientation estimation, see Tabelle B.11 in
%
%   Hanno Scharr (2000): Optimale Operatoren in der digitalen
%   Bildverarbeitung, PhD Thesis at University Heidelberg, Germany,
%   DOI=10.11588/heidok.00000962, http://www.ub.uni-heidelberg.de/archiv/962 
%   
% Alternative orientation estimation based on the tensor of inertia (tensor
% of second moments of image intensities) and on the Fourier transform are
% included for completeness
method = 'gradient'; %one of "fourier", "inertia", "gradient"
kernelSize = 5;            %only relevant for method="gradient" - allowed values are 5 (recommended), 3 and 2
maxImageIntensity = 256;   %only relevant for method="gradient" - necessary for scaling of the gradient
boxRadius = 10;            %only relevant for method="inertia" and method="fourier" - orientation estimation is performed in square boxes of side length 2*boxRadius+1 
forCenterOfMass = true;    %only relevant for method="inertia" - compute second moments for center of the image patch or center of mass (image intensity)
subtractBackgroud = true;  %only relevant for method="inertia"
minWaveLength = 1;         %only relevant for method="fourier" - wavelength to consider for radial integration in the Fourier domain
maxWaveLength = 10;        %only relevant for method="fourier"

%% settings for optional downsampling of the grayscale image and/or the extracted orientation field
downSamplFactorGrayscale = 1; %needs to be integer, 1=no downsampling
downSamplFactorOrient = 1;    %needs to be integer, 1=no downsampling
downSamplTotal = downSamplFactorGrayscale * downSamplFactorOrient;

%% collect prepocessing settings in a "methodInfo"
switch method
    case 'fourier'
        methodInfo = [ method '_radius' num2str(boxRadius) '_WaveLengths' num2str(minWaveLength) 'to' num2str(maxWaveLength) ];
    case 'inertia'
        methodInfo = [ method '_radius' num2str(boxRadius) '_' repmat('NOT', 1-forCenterOfMass) 'forCenterOfMass' '_' repmat('NOT', 1-subtractBackgroud) 'subtractBackgroud' ];
    case 'gradient'
        methodInfo = [ method '_kernel' num2str(kernelSize) ];
        boxRadius = kernelSize; % use boxRadius as means to store the value of kernelSize, p.e. for usage in image erosion
end
methodInfo = [methodInfo '_downsample' num2str(downSamplFactorGrayscale) 'x' num2str(downSamplFactorOrient) ];


%% settings for analysis of coherency, that is degree of local alignment betweem orientations in a box
boxRadius_LocalAlignment = 10;  %this quantity counts in ORIGINAL pixels, defining a box of side length 1+2*boxRadius_LocalAlignment
boxRadius_LocalAlignment_downSampled = floor( boxRadius_LocalAlignment / downSamplTotal );
powerOfMagn_LocalAlignment = 1;

myStrengthExponent = 1.0; % for smoothing
mySigmaForDirectySmoothing = [1.0 1.0];  %kernel radius for smoothing

requiredFracOfOrientInfo = 0.9; %fraction of orientations that must lay inside the region 
  % of interest (or its foreground) for a box to be included into the statistics



%% setting data folder and preparing output collection
head_folder = '/Users/khoffman/Documents/DirectionalityDefects/processedData/2021_08_06_KarenSoans/';

genericFilename = 'ZMAX_of_Resliced_of_Derotated_with2ndCrop.tif'; % according to the output of extractDerotatedSurface.py, 
  % specify WITHOUT leading individual filename portions like 'ROI<number>_'

filesToAnalyse = dir([head_folder '/**/Roi*_' genericFilename ]);
% the levels below  head_folder  ougth to be  <Stage>/<Embryo>/<RegionOfTheEye>/<DerotationConditions>/
% whereby <RegionOfTheEye> is one of the  ROInames = ["hollow", "inBetween", "ridge", "rim",  "back"];

% prepare matrix/array for collection of statistics
output_folder_stats = [head_folder 'collectedResults' '_' methodInfo ...
    '_coherencyRadius' num2str(boxRadius_LocalAlignment) 'Power' num2str(powerOfMagn_LocalAlignment) '/'];
temp = strsplit(output_folder_stats, '/');
% within the folder, specify file to hold the statistics (its name = name of the deepest subfolder)
output_collected = [output_folder_stats temp{end-1} '.csv'];

%folder for the statistics across ROIs and plots
if ~exist(output_folder_stats, 'dir')
    mkdir( output_folder_stats )
end


%% analysis file by file (ROI by ROI)
all_vals_mat = [];
folders_arr = {};
for numFile = 1:numel(filesToAnalyse)
  input_folder = [filesToAnalyse(numFile).folder '/' ] ;
  output_folder= input_folder;
  
  %% load the grayscale image
  input_fullPath = [input_folder filesToAnalyse(numFile).name] ;
  
  [~, input_soleFilename, ~] = fileparts(input_fullPath);
  leading_ROIinfo_inFilename = input_soleFilename( 1 : strfind(input_soleFilename, genericFilename(1:end-4))-1  ); %find name part that is individual to this file

  [~, GrayImageStack] = loadGrayStack(input_fullPath);
  assert(numel(size(GrayImageStack)) ==2) %2D data
  NumSlices = 1;
  
  % scale image intensities to a fixed range for comparable results by the gradient 
  if max(GrayImageStack(:))>255
    warning('image intensities are being scaled down to <=255')
    GrayImageStack = GrayImageStack / max(GrayImageStack(:)) ;
  end
  
  % optional: show loaded data
  %  imshow(GrayImageStack_orig / 255); title('original data')
  
  GrayImageStack_orig = GrayImageStack;
  
  
  
  %% optional downsampling of the grayscale image (downSamplFactorGrayscale = 1 means no downsampling)
  % NOTE: Identification of the foreground still happens in the original image,
  % but orientation analysis is performed in the downsampled grayscale data
  size_y_orig = size(GrayImageStack_orig,1);
  size_x_orig = size(GrayImageStack_orig,2);
  assert( int8(downSamplFactorGrayscale) == downSamplFactorGrayscale, "must be integer")
  
  GrayImageStack_blocks = 1/(downSamplFactorGrayscale^2) ...
    * conv2(GrayImageStack, ones(downSamplFactorGrayscale), 'valid') ;
  
  GrayImageStack_downSampl = GrayImageStack_blocks(1:downSamplFactorGrayscale:end, 1:downSamplFactorGrayscale:end);
  
  GrayImageStack = GrayImageStack_downSampl ;
  size_y_grayBlocks = size(GrayImageStack_downSampl,1);
  size_x_grayBlocks = size(GrayImageStack_downSampl,2);


  %% load info on preprocessing 
  % in particular rotations performed to check for anisotropy along the z-axis in the original imaging system 
  % that might result from the form of the point-spread function 
  input_preprocessingInfoPath = [input_folder leading_ROIinfo_inFilename 'logFileParameters.txt'];
  
  fileID = fopen(input_preprocessingInfoPath);
  preprocessingInfo_raw = fread(fileID,'*char')';
  fclose(fileID);

  % find text location where normal vector is represented
  pattern = 'Smallest eigen vector (V0) = normal vector of plane: (nx,ny,nz)= ';
  textlocation = strfind(preprocessingInfo_raw, pattern);
  if isempty(textlocation)
    warning('no info on normal vector found')
    NormalVecOrig = [NaN, NaN, NaN];
  else
    NormalVecOrig_raw = strsplit( preprocessingInfo_raw(textlocation+length(pattern) : end)); %split at whitespace and newline
    NormalVecOrig_raw = NormalVecOrig_raw(1:3);
    NormalVecOrig = str2double(NormalVecOrig_raw);
    assert( abs( 1- sum(NormalVecOrig.^2) ) < 1e-5, 'Unit length violated by normal vector of the plane that was read') 
  end
  %NormalVecOrig is the normal vector of the plane in the original data,
  % where the z-axis is the axis of anisotropy
  
  Rotation_theta_x = atan2(NormalVecOrig(2) , NormalVecOrig(3)); % equals atan(NormalVecOrig(2) / NormalVecOrig(3))
  Rotation_theta_y = - atan2(NormalVecOrig(1) , sqrt(1-NormalVecOrig(1)^2) ); % named 'theta_yprime' in the preprocessingInfo_raw
  
  AnisotropyAxis = [  sin(Rotation_theta_y), ...
                     -cos(Rotation_theta_y) * sin(Rotation_theta_x), ...
                      cos(Rotation_theta_y) * cos(Rotation_theta_x) ] ;
  %AnisotropyAxis is the axis of anisotropy in the rotated data, 
  % where the z-axis is the normal axis of the plane (and this dimension is omitted by projection)
  
  %AnisotropyAxis is in the left-handed coordinate systems of Fiji (and MATLAB)
  % therefore distinctino between "MATLABcoords" and righthanded "NORMALcoords"
  AnisotropyAxis_inThePlane_MATLABcoords  = AnisotropyAxis(1:2) / sqrt(sum( AnisotropyAxis(1:2).^2)) ;
  
  %for the angle, take the same approach as for orientations theta: use the standard right-handed coordinate layout
  AngleAnisotropy_inThePlane_NORMALcoords = atan2( -AnisotropyAxis(2), AnisotropyAxis(1) );  
  
  AngleAnisotropyProjection = acos(NormalVecOrig(3)) ; % or = acos(AnisotropyAxis(3));
  % in 3D: angle between axis of anisotropy and direction of projection
  
  
  %% orientation analysis
  %% Prepare output arrays
  theta = zeros( size(GrayImageStack) );
  magn  = zeros( size(GrayImageStack) );
  
  %% Orientation analysis
  time_needed_total=0;
  for useSlice = 1:NumSlices
    tic
    switch method
        case 'inertia'
            [directy, ~] = ComputeDirecty(GrayImageStack(:,:,useSlice), boxRadius, forCenterOfMass, subtractBackgroud) ;
        case 'fourier'
            [directy, ~] = ComputeDirecty_FourierAndQ(GrayImageStack(:,:,useSlice), boxRadius, minWaveLength, maxWaveLength);
        case 'gradient'
            directy      = ComputeDirectyGradient(GrayImageStack(:,:,useSlice), kernelSize );
        otherwise
            warning(['method "' method '" not known'])
    end
    
    theta(:,:,useSlice) = atan2( directy(:,:,2), directy(:,:,1) );
    if strcmp(method,'gradient')
        magn (:,:,useSlice) = directy(:,:,3) / maxImageIntensity;
    else %'inertia', 'fourier'
        magn (:,:,useSlice) = directy(:,:,3);
    end
    
    time_needed=toc;
    time_needed_total = time_needed_total + time_needed;
    disp(['... done in ', num2str(time_needed), ' seconds.'])
  end
  disp(['All slices done in ', num2str(time_needed_total), ' seconds.'])
  
  
  %% save result arrays (together with their settings) to file
  % as checkpoint, because orientation estimation with method='inertia' and 'fourier' can take up to hours
  filename_intermediate_output = [input_fullPath(1:end-4) '_' methodInfo '.mat'] ;
  
  save( filename_intermediate_output, ... 
    'GrayImageStack_orig', ...  %the original data
    'input_fullPath', ...  % its source
    'NormalVecOrig', ... % the normal vector of the plane in original data, from which the applied rotation follows
    'method', 'boxRadius', 'forCenterOfMass', 'subtractBackgroud', ...
    'minWaveLength', 'maxWaveLength', ...  %the settings for orientation analysis
    'theta', 'magn', ...  %the results
    'downSamplFactorGrayscale', 'downSamplFactorOrient'  )  %information on downsampling
  
  %load(filename_intermediate_output)
  
  
  %% find foreground = image parts sufficiently off the boundary 
  % (determined without downsampling in the original image data of highest resolution)
  % note that the actual data is surrounded by a dim region of polygonal
  % shape due to the repeated cutting and projecting in Fiji 
  % fg_closed = approximate the foreground region (after morphological closing for smoother shape and convex hull operation
  %   as the shapes du to cutting/projecting are known to be convex polygons)
  % fg_inner  = all those pixels, for which the complete box used for orientation estimation was on the foreground fg_closed
  % fg_innermost = all those pixels, for which all those pixels taken into
  %   account for local alignment had their respective box used for orientation estimation on the foreground fg_closed
  
  foreground_orig = imgaussfilt(GrayImageStack_orig/255, 5, 'FilterDomain','spatial', 'Padding', 0.0 );
  foreground_orig = imbinarize(foreground_orig/max(foreground_orig(:)), 'global');  %imbinarize expects input normalised in [0,1]
  %  imshow(foreground)
  structureElement = strel('diamond',5);
  fg_closed_orig = imclose(foreground_orig, structureElement);
  %fg_closed_orig = imfill(fg_closed_orig, 'holes');
  fg_closed_orig = bwconvhull(fg_closed_orig);
  % compare
  %   figure(1); imshow(fg_closed_orig)
  %   figure(2); imshow(GrayImageStack_orig/255+0.5)
  
  fg_inner_orig = imerode(fg_closed_orig, strel('square', 2*boxRadius+1) );
  %   figure(3); imshow(fg_inner_orig)
  
  fg_innermost_orig = imerode(fg_inner_orig, strel('square', 2*boxRadius_LocalAlignment+1) );
  %   figure(4); imshow(fg_innermost_orig)
  
  %% downsample the foreground information according to the downsampling of grayscale and orientation blocks
  % two variants: count a block in the downsampled image as forground, when
  % (A) any of the contained pixels is foreground, or when
  % (B) all of the contained pixels are foreground (" .."strict")
  %% foreground downsampled to the resoution of grayscale downsampling
  fg_closed_blocks = 1/(downSamplFactorGrayscale^2) ...
    * conv2(fg_closed_orig, ones(downSamplFactorGrayscale), 'valid') ;
  fg_closed        = (0 <  fg_closed_blocks(1:downSamplFactorGrayscale:end, 1:downSamplFactorGrayscale:end) );
  fg_closed_strict = (1 == fg_closed_blocks(1:downSamplFactorGrayscale:end, 1:downSamplFactorGrayscale:end) );
  
  fg_inner_blocks = 1/(downSamplFactorGrayscale^2) ...
    * conv2(fg_inner_orig, ones(downSamplFactorGrayscale), 'valid') ;
  fg_inner        = (0 <  fg_inner_blocks(1:downSamplFactorGrayscale:end, 1:downSamplFactorGrayscale:end) );
  fg_inner_strict = (1 == fg_inner_blocks(1:downSamplFactorGrayscale:end, 1:downSamplFactorGrayscale:end) );
  
  fg_innermost_blocks = 1/(downSamplFactorGrayscale^2) ...
    * conv2(fg_innermost_orig, ones(downSamplFactorGrayscale), 'valid') ;
  fg_innermost        = (0 <  fg_inner_blocks(1:downSamplFactorGrayscale:end, 1:downSamplFactorGrayscale:end) );
  fg_innermost_strict = (1 == fg_innermost_blocks(1:downSamplFactorGrayscale:end, 1:downSamplFactorGrayscale:end) );
  
  %% foreground downsampled to the (coarser) resolution which the orientation downsampling will attain
  % downsample the full-resolution foreground information like "fg_closed_orig" 
  % by the factor   downSamplTotal = downSamplFactorGrayscale * downSamplFactorOrient;
  fg_closed_blocks = 1/(downSamplTotal^2) ...
    * conv2(fg_closed_orig, ones(downSamplTotal), 'valid') ;
  fg_closed_downSampl        = (0 <  fg_closed_blocks(1:downSamplTotal:end, 1:downSamplTotal:end) );
  fg_closed_downSampl_strict = (1 == fg_closed_blocks(1:downSamplTotal:end, 1:downSamplTotal:end) );
  
  fg_inner_blocks = 1/(downSamplTotal^2) ...
    * conv2(fg_inner_orig, ones(downSamplTotal), 'valid') ;
  fg_inner_downSampl        = (0 <  fg_inner_blocks(1:downSamplTotal:end, 1:downSamplTotal:end) );
  fg_inner_downSampl_strict = (1 == fg_inner_blocks(1:downSamplTotal:end, 1:downSamplTotal:end) );
  
  fg_innermost_blocks = 1/(downSamplTotal^2) ...
    * conv2(fg_inner_orig, ones(downSamplTotal), 'valid') ;
  fg_innermost_downSampl        = (0 <  fg_innermost_blocks(1:downSamplTotal:end, 1:downSamplTotal:end) );
  fg_innermost_downSampl_strict = (1 == fg_innermost_blocks(1:downSamplTotal:end, 1:downSamplTotal:end) );
  
  
  %% smooth orientations with Gaussian kernel, and find versions restricted to the foreground
  [theta_smoothed, magn_smoothed] = smoothDirecty_ThetaStrength(theta, magn, mySigmaForDirectySmoothing, myStrengthExponent);
  
  % alternative versions restrict to points in the foreground (_fg) 
  % and those for which the image patch used for orientation estimation was fully within the foreground (_inner) 
  % and those for which all pixels considered for local alignment had their respective box for orientation estimation fully in the foreground (_innermost)
  %   (the last is NOT the same as "all pixels considered in the smoothing step")
  magn_smoothed_fg = NaN (size(magn_smoothed));
  magn_smoothed_fg(fg_closed) = magn_smoothed(fg_closed);
  
  magn_smoothed_fg_inner = NaN (size(magn_smoothed));
  magn_smoothed_fg_inner(fg_inner) = magn_smoothed(fg_inner);
  
  magn_smoothed_fg_innermost = NaN (size(magn_smoothed));
  magn_smoothed_fg_innermost(fg_innermost) = magn_smoothed(fg_innermost);
  
  %% optional: use the smoothed orientation field for further analysis
  % basically set  theta = theta_smoothed  but assert values in the interval [0, pi]
  % NOTE: smoothing might interfere with the subsequent analysis of alignment
  %{
  theta = mod(theta_smoothed, pi) ;
  magn  = magn_smoothed;
  %}
  
  
  
  %% optional: downsampling of the orientation field (downSamplFactorOrient = 1 means no downsampling)
  assert( int8(downSamplFactorOrient) == downSamplFactorOrient, "must be integer")
  
  [theta_downSampl, magn_downSampl] = downSamplDirecty_ThetaStrength(theta, magn, downSamplFactorOrient);  % skipRows=0 and skipCols=0 fits to the grayscale downsampling in this file, strength_exponent)
  
  size_y_grayAndOrientBlocks = size(theta_downSampl,1);
  size_x_grayAndOrientBlocks = size(theta_downSampl,2);
  
  % also downsample the grayscale intensities (again) as they will be used as weights in the check for anisotropy
  GrayImageStack_blocksCoarse = 1/(downSamplFactorOrient^2) ...
    * conv2(GrayImageStack_downSampl, ones(downSamplFactorOrient), 'valid') ;
  
  GrayImageStack_downSamplTwice = GrayImageStack_blocksCoarse(1:downSamplFactorOrient:end, 1:downSamplFactorOrient:end);
  
  assert( all([size_y_grayAndOrientBlocks, size_x_grayAndOrientBlocks] == size(GrayImageStack_downSamplTwice) ) )
  
  % use downsampled orientation field for further analysis
  theta_orig = theta;
  magn_orig  = magn ;
  % basically set  theta = theta_downSampl  but assert values in the interval [0, pi]
  theta = mod(theta_downSampl, pi) ;
  magn  = magn_downSampl;
  
  
  %% alignment as a function of space
  %% compute local alignment in fixed box - potentially time consuming
  % use boxRadius_LocalAlignment_downSampled that is adjusted to the downsampling performed beforehand
  [localOrient, localCoherency] = ComputeLocalAlignment(theta,magn,boxRadius_LocalAlignment_downSampled, powerOfMagn_LocalAlignment);
  
  
  %% save result arrays of local alignment (together with their settings) to file
  % as checkpoint
  filename_intermediate_output1 = [filename_intermediate_output(1:end-4) ...
    '_coherencyRadius' num2str(boxRadius_LocalAlignment) 'Power' num2str(powerOfMagn_LocalAlignment) '.mat'];
  
  save( filename_intermediate_output1, ... 
    'GrayImageStack_orig', ...  %the original data
    'input_fullPath', ...  % its source
    'NormalVecOrig', ... % the normal vector of the plane in original data, from which the applied rotation follows
    'method', 'boxRadius', 'forCenterOfMass', 'subtractBackgroud', ...
    'minWaveLength', 'maxWaveLength', ...  %the settings for orientation analysis
    'theta', 'magn', ...   %the results
    'downSamplFactorGrayscale', 'downSamplFactorOrient', ...   %information on downsampling
    'fg_closed', 'fg_inner', 'fg_innermost', ... % different definitions of foreground
    'boxRadius_LocalAlignment', 'boxRadius_LocalAlignment_downSampled', 'powerOfMagn_LocalAlignment', ... %the settings for local alignment analysis
    'localOrient', 'localCoherency' )    %the results 
  
  %load(filename_intermediate_output1)
  
  
  
  %% local alignment restricted to foreground versions
  % recall: this restricts to points in the foreground (_fg) 
  % and those for which the image patch used for orientation estimation was fully within the foreground (_inner) 
  % and those for which all pixels considered for local alignment had their respective box for orientation estimation fully in the foreground (_innermost)
  localCoherency_fg = NaN (size(localCoherency));
  localCoherency_fg(fg_closed_downSampl) = localCoherency(fg_closed_downSampl);
  
  localCoherency_fg_inner = NaN (size(localCoherency));
  localCoherency_fg_inner(fg_inner_downSampl) = localCoherency(fg_inner_downSampl);
  
  localCoherency_fg_innermost = NaN (size(localCoherency));
  localCoherency_fg_innermost(fg_innermost_downSampl) = localCoherency(fg_innermost_downSampl);
  
  %% compute global alignment of ALL orientations present
  % in the whole image, in the foreground (_fg), and of those for which the
  % image patch used for orientation estimation was fully within the foreground (_inner) 
  [overallOrient,          overallCoherency]          = ComputeQTensor(theta, magn, powerOfMagn_LocalAlignment);
  [overallOrient_fg,       overallCoherency_fg]       = ComputeQTensor(theta, magn.*fg_closed_downSampl, powerOfMagn_LocalAlignment);
  [overallOrient_fg_inner, overallCoherency_fg_inner] = ComputeQTensor(theta, magn.*fg_inner_downSampl , powerOfMagn_LocalAlignment);
  
  
  %% Alternatively, limit further analysis of local orientation to points 
  % for which the neighbourhood considered contains not too much pixels without orientation information
  OrientInfoPresent = (~isnan(theta))&(~isnan(magn));
  OrientInfoPresent(1:boxRadius,         :) = 0;
  OrientInfoPresent(end-boxRadius+1:end, :) = 0;
  OrientInfoPresent(:, 1:boxRadius        ) = 0;
  OrientInfoPresent(:, end-boxRadius+1:end) = 0;
  LocalOrientIsReliable =    requiredFracOfOrientInfo * conv2( ones(size(theta)) , ones( 2*boxRadius_LocalAlignment_downSampled+1), 'same'  ) ...
                             <=   conv2( OrientInfoPresent                       , ones( 2*boxRadius_LocalAlignment_downSampled+1), 'same'  )   ;
  LocalOrientIsReliably_fg = requiredFracOfOrientInfo * conv2( ones(size(theta)) , ones( 2*boxRadius_LocalAlignment_downSampled+1), 'same'  ) ...
                              <=   conv2( OrientInfoPresent.*fg_closed_downSampl , ones( 2*boxRadius_LocalAlignment_downSampled+1), 'same'  )   ;
  
  
  [overallOrient_reliable,    overallCoherency_reliable]    = ComputeQTensor(theta, magn.*LocalOrientIsReliable, powerOfMagn_LocalAlignment);
  [overallOrient_reliably_fg, overallCoherency_reliably_fg] = ComputeQTensor(theta, magn.*LocalOrientIsReliably_fg, powerOfMagn_LocalAlignment);
  
  
  
  %% compute alignment of orientations with axis of anisotropy in the data
  % alignment1 = linear = absolute value of scalar product between two vectors 
  % alignment2 = quadratic like the nematic order parameter, but with the (projected) axis of anisotropy as reference
  % s = average over (3/2 cos^2 beta - 1/2)
  % where beta = angle between orientation and (projected) axis of anisotropy, hence
  % cos beta = scalar product of orientation with (projected) axis of anisotropy (when both are expressed as normalised vectors)
  % values of s range from -0.5 (all detected orientations perpendicular to axis of anisotropy)
  % to +1.0 (all detected orientations along the axis of anisotropy), but
  % should be around zero (indicating little bias of the orientations by the anisotropy of imaging in the original z-axis)
  alignment1          = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) )                         )) / nansum(nansum( isfinite(theta)                        )) ;
  alignment1_fg       = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* fg_closed_downSampl  )) / nansum(nansum( isfinite(theta) .* fg_closed_downSampl )) ;
  alignment1_fg_inner = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* fg_inner_downSampl   )) / nansum(nansum( isfinite(theta) .* fg_inner_downSampl  )) ;
  
  alignment2          = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2                         )) / nansum(nansum( isfinite(theta)                        )) ;
  alignment2_fg       = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* fg_closed_downSampl  )) / nansum(nansum( isfinite(theta) .* fg_closed_downSampl )) ;
  alignment2_fg_inner = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* fg_inner_downSampl   )) / nansum(nansum( isfinite(theta) .* fg_inner_downSampl  )) ;
  
  % versions that are weighted by magnitude of the orientation information and/or image intensity at that pixel
  alignment1_magn          = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* magn                         )) / nansum(nansum( isfinite(theta) .* magn                        )) ;
  alignment1_magn_fg       = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* magn .* fg_closed_downSampl  )) / nansum(nansum( isfinite(theta) .* magn .* fg_closed_downSampl )) ;
  alignment1_magn_fg_inner = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* magn .* fg_inner_downSampl   )) / nansum(nansum( isfinite(theta) .* magn .* fg_inner_downSampl  )) ;
  
  alignment2_magn          = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* magn                         )) / nansum(nansum( isfinite(theta) .* magn                        )) ;
  alignment2_magn_fg       = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* magn .* fg_closed_downSampl  )) / nansum(nansum( isfinite(theta) .* magn .* fg_closed_downSampl )) ;
  alignment2_magn_fg_inner = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* magn .* fg_inner_downSampl   )) / nansum(nansum( isfinite(theta) .* magn .* fg_inner_downSampl  )) ;
  
  
  alignment1_intens          = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* GrayImageStack_downSamplTwice                         )) / nansum(nansum( isfinite(theta) .* GrayImageStack_downSamplTwice                        )) ;
  alignment1_intens_fg       = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* GrayImageStack_downSamplTwice .* fg_closed_downSampl  )) / nansum(nansum( isfinite(theta) .* GrayImageStack_downSamplTwice .* fg_closed_downSampl )) ;
  alignment1_intens_fg_inner = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* GrayImageStack_downSamplTwice .* fg_inner_downSampl   )) / nansum(nansum( isfinite(theta) .* GrayImageStack_downSamplTwice .* fg_inner_downSampl  )) ;
  
  alignment2_intens          = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* GrayImageStack_downSamplTwice                         )) / nansum(nansum( isfinite(theta) .* GrayImageStack_downSamplTwice                        )) ;
  alignment2_intens_fg       = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* GrayImageStack_downSamplTwice .* fg_closed_downSampl  )) / nansum(nansum( isfinite(theta) .* GrayImageStack_downSamplTwice .* fg_closed_downSampl )) ;
  alignment2_intens_fg_inner = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* GrayImageStack_downSamplTwice .* fg_inner_downSampl   )) / nansum(nansum( isfinite(theta) .* GrayImageStack_downSamplTwice .* fg_inner_downSampl  )) ;
  
  
  alignment1_magn_intens          = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* magn .* GrayImageStack_downSamplTwice                         )) / nansum(nansum( isfinite(theta) .* magn .* GrayImageStack_downSamplTwice                        )) ;
  alignment1_magn_intens_fg       = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* magn .* GrayImageStack_downSamplTwice .* fg_closed_downSampl  )) / nansum(nansum( isfinite(theta) .* magn .* GrayImageStack_downSamplTwice .* fg_closed_downSampl )) ;
  alignment1_magn_intens_fg_inner = -2/pi+ nansum(nansum(  abs( cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ) .* magn .* GrayImageStack_downSamplTwice .* fg_inner_downSampl   )) / nansum(nansum( isfinite(theta) .* magn .* GrayImageStack_downSamplTwice .* fg_inner_downSampl  )) ;
  
  alignment2_magn_intens          = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* magn .* GrayImageStack_downSamplTwice                         )) / nansum(nansum( isfinite(theta) .* magn .* GrayImageStack_downSamplTwice                        )) ;
  alignment2_magn_intens_fg       = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* magn .* GrayImageStack_downSamplTwice .* fg_closed_downSampl  )) / nansum(nansum( isfinite(theta) .* magn .* GrayImageStack_downSamplTwice .* fg_closed_downSampl )) ;
  alignment2_magn_intens_fg_inner = -0.5 +  nansum(nansum(  (cos(theta).* cos(AngleAnisotropy_inThePlane_NORMALcoords) + sin(theta) .* sin(AngleAnisotropy_inThePlane_NORMALcoords) ).^2 .* magn .* GrayImageStack_downSamplTwice .* fg_inner_downSampl   )) / nansum(nansum( isfinite(theta) .* magn .* GrayImageStack_downSamplTwice .* fg_inner_downSampl  )) ;
  
  
  
  %% collect results in structs with descriptions and values
  %start with info on size of foreground
  vals  = struct();
  descr = struct();
  
  vals. size_x = size(theta,2);
  descr.size_x = 'image size x';
  
  vals. size_y = size(theta,1);
  descr.size_y = 'image size y';
  
  vals .pixels = sum(~isnan(theta(:)));
  descr.pixels = 'pixels with orientation';
  
  assert(all(unique(fg_closed) == [ 0; 1]))
  vals .pixels_fg = sum(fg_closed(:));
  descr.pixels_fg = 'foreground pixels';
  
  assert(all(unique(fg_inner) == [ 0; 1]))
  vals. pixels_fg_inner = sum(fg_inner(:));
  descr.pixels_fg_inner = 'pixels for wich box was within foreground';
  
  %% add results of coherencies in structs with descriptions and values
  vals. overallCoherency = overallCoherency;
  descr.overallCoherency = 'coherency - all orientations';
  vals. overallTheta = atan2(overallOrient(2), overallOrient(1));
  descr.overallTheta = 'theta - all orientations';
  
  vals. overallCoherency_fg = overallCoherency_fg;
  descr.overallCoherency_fg = 'coherency - all orientations on foreground';
  vals. overallTheta_fg = atan2(overallOrient_fg(2), overallOrient_fg(1));
  descr.overallTheta_fg = 'theta - all orientations on foreground';
  
  vals. overallCoherency_fg_inner = overallCoherency_fg_inner;
  descr.overallCoherency_fg_inner = 'coherency - all orientations for which box was within foreground';
  vals. overallTheta_fg_inner = atan2(overallOrient_fg_inner(2), overallOrient_fg_inner(1));
  descr.overallTheta_fg_inner = 'theta - all orientations for which box was within foreground';
  
  vals. overallCoherency_reliab = overallCoherency_reliable;
  descr.overallCoherency_reliab = 'coherency - all orientations for which box had mostly orientation information';
  vals. overallTheta_reliab = atan2(overallOrient_reliable(2), overallOrient_reliable(1));
  descr.overallTheta_reliab= 'theta - all orientations for which box box had mostly orientation information';
  
  vals. overallCoherency_reliab_fg = overallCoherency_reliably_fg;
  descr.overallCoherency_reliab_fg = 'coherency - all orientations for which box had mostly foreground and orientation information';
  vals. overallTheta_reliab_fg = atan2(overallOrient_reliably_fg(2), overallOrient_reliably_fg(1));
  descr.overallTheta_reliab_fg = 'theta - all orientations for which box had mostly foreground and orientation information';
  
  vals. localCoherency = nanmean(localCoherency(:));
  descr.localCoherency = 'mean local coherency (in boxes)';
  vals. localCoherency_fg = nanmean(localCoherency_fg(:));
  descr.localCoherency_fg = 'mean local coherency (in boxes) over foreground';
  vals. localCoherency_fg_inner = nanmean(localCoherency_fg_inner(:));
  descr.localCoherency_fg_inner = 'mean local coherency (in boxes) where box was within foreground';
  
  vals. localCoherencyGaussian = nanmean(magn_smoothed(:));
  descr.localCoherencyGaussian = 'mean local coherency (Gaussian)';
  vals. localCoherencyGaussian_fg = nanmean(magn_smoothed_fg(:));
  descr.localCoherencyGaussian_fg = 'mean local coherency (Gaussian) over foreground';
  vals. localCoherencyGaussian_fg_inner = nanmean(magn_smoothed_fg_inner(:));
  descr.localCoherencyGaussian_fg_inner = 'mean local coherency (Gaussian) where box was within foreground';
  
  
  %% add info on anisotropy of data
  vals. normalVec_x = NormalVecOrig(1);
  descr.normalVec_x = 'normal vector in original data n_x';
  vals. normalVec_y = NormalVecOrig(2);
  descr.normalVec_y = 'normal vector in original data n_y';
  vals. normalVec_z = NormalVecOrig(3);
  descr.normalVec_z = 'normal vector in original data n_z';
  
  vals. AngleAnisotropyProjection = AngleAnisotropyProjection;
  descr.AngleAnisotropyProjection = 'angle between projection axis and anisotropy (radians)';
  
  vals. orientationBias_linear = alignment1_fg_inner;
  descr.orientationBias_linear = 'bias of orientations by anisotropy - linear, where box was within foreground';
  vals. orientationBias_quadratic = alignment2_fg_inner;
  descr.orientationBias_quadratic = 'bias of orientations by anisotropy - quadratic (as for nematic order parameter), where box was within foreground';
  
  
  %% prepare writing of data
  vals_mat = struct2array(vals);
  all_vals_mat = [all_vals_mat; vals_mat] ;
  
  folder_to_subfolders = strsplit(input_folder, filesep);
  
  folders_arr = [ folders_arr  ; ...
      {input_folder} folder_to_subfolders(end-4), folder_to_subfolders(end-3), folder_to_subfolders(end-2), folder_to_subfolders(end-1), ...
      leading_ROIinfo_inFilename];
  %recall: the code assumes that folder_to_subfolders
  % contains  <Stage>/<Embryo>/<RegionOfTheEye>/<DerotationConditions>/
  %  and the different ROIs within one folder are distinguished by leading_ROIinfo_inFilename
  
  %% write results to text file
  filename_intermediate_output2 = [filename_intermediate_output(1:end-4) ...
      '_coherencyRadius' num2str(boxRadius_LocalAlignment) 'Power' num2str(powerOfMagn_LocalAlignment) '.txt'];
  
  dlmwrite( filename_intermediate_output2 , ['coherencies obtained from'],     'delimiter','')  %header
  dlmwrite( filename_intermediate_output2 , [filename_intermediate_output2] ,  '-append', 'delimiter','')  % origin of data
  dlmwrite( filename_intermediate_output2 , ['Parameters used'] ,  '-append', 'delimiter','')
  dlmwrite( filename_intermediate_output2 , [num2str(boxRadius) , ' BoxRadius'], '-append', 'delimiter','')  % parameters used
  dlmwrite( filename_intermediate_output2 , [num2str(boxRadius_LocalAlignment) , ' BoxRadius for Local Alignment'], '-append', 'delimiter','')  %  parameters used
  dlmwrite( filename_intermediate_output2 , [num2str(boxRadius_LocalAlignment_downSampled) , ' BoxRadius for Local Alignment in downsampled pixels'], '-append', 'delimiter','')  %  parameters used
  dlmwrite( filename_intermediate_output2 , [num2str(powerOfMagn_LocalAlignment), ' Power of Magnitude for Local Alignment'], '-append', 'delimiter','')  %  parameters used
  dlmwrite( filename_intermediate_output2 , [num2str(mySigmaForDirectySmoothing) , ' sigma for smoothing'], '-append', 'delimiter','')  %  parameters used
  dlmwrite( filename_intermediate_output2 , [num2str(myStrengthExponent), ' Power of Magnitude for smoothing'], '-append', 'delimiter','')  %  parameters used
  dlmwrite( filename_intermediate_output2 , [' '], '-append', 'delimiter','')  %  empty line
  
  
  fieldnameslist = fieldnames(vals);
  for kk = 1:numel(fieldnameslist)
      dlmwrite( filename_intermediate_output2 , [num2str(vals.(fieldnameslist{kk})), ' ', descr.(fieldnameslist{kk})], '-append', 'delimiter','')
  end
  
  
  
  
  %% plotting
  %% Color and other conventions for plotting
  
  col_director     = [0.4 0.4 1.0];  %NOT 'blue', as this is too dark
  col_localOrient = [1.0 0.4 0.4];
  show_localOrient = false; % show locally averaged orientation that corresponds to the computed alignment
  
  col_anisotropy = [0.2 1 0.2] ;
  show_anisotropy = true;
  
  centerQuiverOnPixels = true; %show orientations centered over their location
  
  savePlots = false;
  useMagnForPlotting = false; % scale quiver plots by orientation magnitude
  
  %spacing12 between plotted orientation [integer, =n plots only every n-th orientation]
  spacing12 = ceil(4.0/downSamplTotal);  % a reasonable start for ROIs of few hundred pixels
  %spacing12 = 1
  % {
  %% plotting of orig image with orientation field
  myData = GrayImageStack_orig(:,:,useSlice);
  myData = myData/ max(GrayImageStack_orig(:)); %rescale for plotting
  myData = 0.2+0.8*  myData;  %make the data more bright in plotting
  
  assert(max(myData(:)) <= 1.0, 'Rescale data!')
  theta_slice = theta(:,:,useSlice);
  magn_slice  = GrayImageStack_downSamplTwice .* magn (:,:,useSlice);
  
  [x12, y12] = meshgrid( 0.5+downSamplTotal*(-0.5+(1:size(theta_slice,2)) ), ...
                         0.5+downSamplTotal*(-0.5+(1:size(theta_slice,1)) )  );
  % rescale positions that are given as row/column in the arrays of data
  % after downsampling twice, back to their positions in the original data
  scaleToOrig_x = @(xInDownSamplTwice) 0.5+(xInDownSamplTwice-0.5)*downSamplTotal;
  scaleToOrig_y = @(yInDownSamplTwice) 0.5+(yInDownSamplTwice-0.5)*downSamplTotal;
  
  u12 = magn_slice.^useMagnForPlotting .*  cos( theta_slice ) ; 
  v12 = magn_slice.^useMagnForPlotting .* -sin( theta_slice ) ; %negative sign to transform from theta in normal coord layout to coord layout used by MATLAB (y downwards in plotting that is started by imshow() )
  u_localOrient = localOrient(:,:,1) .* localCoherency;
  v_localOrient = localOrient(:,:,2) .* localCoherency;
  
  %% plotting itself
  Fig12 = figure('PaperOrientation', 'landscape', 'Position', [0, 0, 1200, 700]);
  [axesHandles12, positions12] = tight_subplot(1,1,[.08 .05],[.05 .01],[.05 .02]) ;   %gap, margin_height, margin_width)
  fig12A = axesHandles12(1);
  
  axes(fig12A)   %make the existing axes current
  
  OrigData12 = imshow(myData);  % (myData * 2 - 3);
  hold on
  
  % add overlay to indicate the foreground - see https://de.mathworks.com/company/newsletters/articles/image-overlay-using-transparency.html
  % show true-color all-red image on top
  red = cat(3, ones(size(myData)), zeros(size(myData)), zeros(size(myData)));
  foreground_plot = imshow(red);
  % but set its transparency according to fg_closed_orig
  set(foreground_plot, 'AlphaData', 0.1* fg_closed_orig)
  
  % orientations plotted as head-less vectors
  quiv_onOrigData1 = quiver(x12(1:spacing12:end,1:spacing12:end), y12(1:spacing12:end,1:spacing12:end), u12(1:spacing12:end,1:spacing12:end), v12(1:spacing12:end,1:spacing12:end));
  if centerQuiverOnPixels
      %duplicate quiver (with opposite sign) to have coherent appearance
      quiv_onOrigData2 = quiver(x12(1:spacing12:end,1:spacing12:end), y12(1:spacing12:end,1:spacing12:end), -u12(1:spacing12:end,1:spacing12:end), -v12(1:spacing12:end,1:spacing12:end));
  end
  
  % modify quiver plot(s)
  quiv_onOrigData1.ShowArrowHead = 'off';
  quiv_onOrigData1.LineWidth  = 1.5;  %(default: 0.5)
  quiv_onOrigData1.AutoScaleFactor = 1.0 ;
  quiv_onOrigData1.Color = col_director;
  if centerQuiverOnPixels
      %give the duplicate quiver the same properties, especially the same color
      quiv_onOrigData2.ShowArrowHead   = quiv_onOrigData1.ShowArrowHead   ;
      quiv_onOrigData2.LineWidth       = quiv_onOrigData1.LineWidth       ;
      quiv_onOrigData2.AutoScaleFactor = quiv_onOrigData1.AutoScaleFactor ;
      quiv_onOrigData2.Color           = quiv_onOrigData1.Color           ;
  end
  
  
  if show_localOrient
      quiv_localOrient1 = quiver(x12(1:spacing12:end,1:spacing12:end), y12(1:spacing12:end,1:spacing12:end), u_localOrient(1:spacing12:end,1:spacing12:end), -v_localOrient(1:spacing12:end,1:spacing12:end));
      %Note negative sign on y-component to transform from theta in normal coord layout to coord layout used by MATLAB (y downwards in plotting that is started by imshow() )
      if centerQuiverOnPixels
          %duplicate quiver (with opposite sign) to have coherent appearance
          quiv_localOrient2 = quiver(x12(1:spacing12:end,1:spacing12:end), y12(1:spacing12:end,1:spacing12:end), -u_localOrient(1:spacing12:end,1:spacing12:end), v_localOrient(1:spacing12:end,1:spacing12:end));
      end
      
      % modify quiver plot(s)
      quiv_localOrient1.ShowArrowHead = 'off';
      quiv_localOrient1.LineWidth  = 0.5;  %(default: 0.5)
      quiv_localOrient1.AutoScaleFactor = 1.0 ;
      quiv_localOrient1.Color = col_localOrient;
      if centerQuiverOnPixels
          %give the duplicate quiver the same properties, especially the same color
          quiv_localOrient2.ShowArrowHead   = quiv_localOrient1.ShowArrowHead   ;
          quiv_localOrient2.LineWidth       = quiv_localOrient1.LineWidth       ;
          quiv_localOrient2.AutoScaleFactor = quiv_localOrient1.AutoScaleFactor ;
          quiv_localOrient2.Color           = quiv_localOrient1.Color           ;
      end
  end
  
  
  if show_anisotropy
      % plot projection of the anisotropy outside of the plot area
      quiv_anisotropy = quiver( (size_x_orig+15)*[1 1], 0.5*(size_y_orig)*[1 1], ...
          [AnisotropyAxis_inThePlane_MATLABcoords(1), -AnisotropyAxis_inThePlane_MATLABcoords(1)], ...
          [AnisotropyAxis_inThePlane_MATLABcoords(2), -AnisotropyAxis_inThePlane_MATLABcoords(2)] );
      quiv_anisotropy.ShowArrowHead = 'off';
      quiv_anisotropy.LineWidth  = 5;
      quiv_anisotropy.AutoScaleFactor = 10;
      quiv_anisotropy.Color = col_anisotropy;
      
      text( (size_x_orig+15), 0.5*(size_y_orig)-20, ...
          'anisotropy axis (projected)')
      text( (size_x_orig+15), 0.5*(size_y_orig)+20, [{'bias into anisotropy direction' , 'among orientations where box was within foreground', ['linear = ',  num2str(alignment1_magn_intens_fg_inner)],  ['quadratic = ',  num2str(alignment2_magn_intens_fg_inner)]  }])
      
      text( (size_x_orig+15), 0.5*(size_y_orig)+60, ...
          {'angle between', 'anisotropy and projection', ['= ' num2str(AngleAnisotropyProjection*180/pi) ' degree']} )
      
      
  end
  
  axis equal
  
  title(['orig image with foreground highlighted, orientations'] )
  
  drawnow
  pause(0.1)
  if show_anisotropy
      %make more space for title and annotations on the right side
      currAx = gca();
      currAx.Position = currAx.Position - [0 0 0.05 0.05];
  end
  
  print(Fig12, [filename_intermediate_output(1:end-4) '_Orientations'],'-dpdf', '-r0', '-fillpage')
  print(Fig12, [filename_intermediate_output(1:end-4) '_Orientations'],'-dpng', '-r0')
  
  close all
  
  
  %% plotting the distribution of orientations to check for impact of anisotropy in imaging
  %% binning
  % bin the data explicitly to allow for weights being considered
  % bin into interval [0,pi], then duplicate data from theta to pi+theta to represent nematics
  NbinsPolar = 128;
  theta_for_hists = mod(theta, pi);
  assert ( min(theta_for_hists(:))>= 0  &  max(theta_for_hists(:))<=pi, 'theta falls  out of range [0, pi]' )
  EdgesPolar = linspace(0, pi, NbinsPolar+1);
  % this ensures to have all data fall into the bins (NaN bins are only due to theta value NaN)
  assert ( EdgesPolar(1) == 0  &  EdgesPolar(end) == pi , 'Incorrect bins')
  EdgesNematic = [EdgesPolar(1:end-1)  pi+EdgesPolar];
  
  whichBin = discretize(theta_for_hists, EdgesPolar);
  
  Freq              = zeros([1 numel(EdgesPolar)-1]);
  Freq_fg           = zeros([1 numel(EdgesPolar)-1]);
  Freq_fg_inner     = zeros([1 numel(EdgesPolar)-1]);
  %Freq_fg_innermost = zeros([1 numel(EdgesPolar)-1]);
  % _innermost does not add information, because it makes sure that coherency estimation is within the foreground
  
  Freq_magn           = zeros([1 numel(EdgesPolar)-1]);
  Freq_magn_fg        = zeros([1 numel(EdgesPolar)-1]);
  Freq_magn_fg_inner  = zeros([1 numel(EdgesPolar)-1]);
  
  Freq_intens               = zeros([1 numel(EdgesPolar)-1]);
  Freq_intens_fg            = zeros([1 numel(EdgesPolar)-1]);
  Freq_intens_fg_inner      = zeros([1 numel(EdgesPolar)-1]);
  
  Freq_magn_intens          = zeros([1 numel(EdgesPolar)-1]);
  Freq_magn_intens_fg       = zeros([1 numel(EdgesPolar)-1]);
  Freq_magn_intens_fg_inner = zeros([1 numel(EdgesPolar)-1]);
  
  
  for kk=1:numel(EdgesPolar)-1
      Freq(kk)             =    sum(   sum( (whichBin==kk) ));
      Freq_fg(kk)          =    sum(   sum( (whichBin==kk) .* fg_closed_downSampl));
      Freq_fg_inner(kk)    =    sum(   sum( (whichBin==kk) .* fg_inner_downSampl));
      %Freq_fg_innermost(kk)=    sum(   sum( (whichBin==kk) .* fg_innermost_downSampl));
      
      Freq_magn(kk)                 = nansum(nansum( (whichBin==kk) .* magn                                   ));
      Freq_magn_fg(kk)              = nansum(nansum( (whichBin==kk) .* magn                                   .* fg_closed_downSampl));
      Freq_magn_fg_inner(kk)        = nansum(nansum( (whichBin==kk) .* magn                                   .* fg_inner_downSampl ));
      
      Freq_intens(kk)               =    sum(   sum( (whichBin==kk)          .* GrayImageStack_downSamplTwice));
      Freq_intens_fg(kk)            =    sum(   sum( (whichBin==kk)          .* GrayImageStack_downSamplTwice .* fg_closed_downSampl));
      Freq_intens_fg_inner(kk)      =    sum(   sum( (whichBin==kk)          .* GrayImageStack_downSamplTwice .* fg_inner_downSampl ));
      
      Freq_magn_intens(kk)          = nansum(nansum( (whichBin==kk) .* magn .* GrayImageStack_downSamplTwice));
      Freq_magn_intens_fg(kk)       = nansum(nansum( (whichBin==kk) .* magn .* GrayImageStack_downSamplTwice .* fg_closed_downSampl));
      Freq_magn_intens_fg_inner(kk) = nansum(nansum( (whichBin==kk) .* magn .* GrayImageStack_downSamplTwice .* fg_inner_downSampl ));
  end
  
  %% plotting of orientation distribution itself
  colUse_All = 'black' ;
  colUse_fg  = [0 0.8 0]; %dark green
  colUse_fg_inner  = 'blue';
  colUse_fg_innermost  = 'red';
  
  Fig13 = figure(13);  %'PaperSize', [11 8.5]) %'Position', [100 100 300 500]);
  Fig13.Position = [0, 0, 1200, 900] ;
  Fig13A   = polaraxes('Units', 'normalized', 'OuterPosition', [     0  1/2  1/2  1/2]) ;
  Fig13B   = polaraxes('Units', 'normalized', 'OuterPosition', [   1/2  1/2  1/2  1/2]) ;
  Fig13C   = polaraxes('Units', 'normalized', 'OuterPosition', [     0    0  1/2  1/2]) ;
  Fig13D   = polaraxes('Units', 'normalized', 'OuterPosition', [   1/2    0  1/2  1/2]) ;
  
  subplot(Fig13A); %makes the existing subplot current
  hold on
  pHist             = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq             Freq            ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_All);
  pHist_fg          = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_fg          Freq_fg         ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_fg);
  pHist_fg_inner    = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_fg_inner    Freq_fg_inner   ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_fg_inner);
  %    pHist_fg_innermost= polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_fg_innermost Freq_fg_innermost], ...
  %        'DisplayStyle','stairs', 'EdgeColor', colUse_fg_innermost);
  
  % add projection of the anisotropy
  pAnisotropyAxis = polarplot( [AngleAnisotropy_inThePlane_NORMALcoords, AngleAnisotropy_inThePlane_NORMALcoords+pi], [0.8, 0.8]* max(Freq), 'Color', 'red' );
  
  legend([pHist, pHist_fg, pHist_fg_inner, ... %, pHist_fg_innermost,...
      pAnisotropyAxis], ...
      {'all orientations', 'orientations in foreground', 'where box was within foreground', ... % 'where coherency box was within foreground', ...
      'axis of anisotropy (projected)'}, ...
      'Location', 'southwest')
  
  text(-pi/4, max(Freq), [{'bias into anisotropy direction' , 'among orientations where box was within foreground', ['linear = ',  num2str(alignment1_fg_inner)],  ['quadratic = ',  num2str(alignment2_fg_inner)]  }])
  
  text( pi/4, 1.5*max(Freq), ...
      {'angle between', 'anisotropy and projection', ['= ' num2str(AngleAnisotropyProjection*180/pi) ' degree']} )
  
  title('frequency of orientations')
  
  
  subplot(Fig13B); %makes the existing subplot current
  hold on
  pHist_magn             = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_magn           Freq_magn            ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_All);
  pHist_magn_fg          = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_magn_fg        Freq_magn_fg         ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_fg);
  pHist_magn_fg_inner    = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_magn_fg_inner  Freq_magn_fg_inner   ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_fg_inner);
  
  pAnisotropyAxis = polarplot( [AngleAnisotropy_inThePlane_NORMALcoords, AngleAnisotropy_inThePlane_NORMALcoords+pi], [0.8, 0.8]* max(Freq_magn), 'Color', 'red' );
  
  %legend([pHist_magn, pHist_magn_fg, pHist_magn_fg_inner, ...
  %    pAnisotropyAxis], ...
  %    {'all orientations', 'orientations in foreground', 'where box was within foreground', ...
  %    'axis of anisotropy (projected)'}, ...
  %    'Location', 'southwest')
  
  text(-pi/4, max(Freq_magn), [{'bias into anisotropy direction' , 'among orientations where box was within foreground', ['linear = ',  num2str(alignment1_magn_fg_inner)],  ['quadratic = ',  num2str(alignment2_magn_fg_inner)]  }])
  
  title('frequency of orientations - magnitude weighted')
  
  
  subplot(Fig13C); %makes the existing subplot current
  hold on
  pHist_intens             = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_intens           Freq_intens            ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_All);
  pHist_intens_fg          = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_intens_fg        Freq_intens_fg         ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_fg);
  pHist_intens_fg_inner    = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_intens_fg_inner  Freq_intens_fg_inner   ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_fg_inner);
  
  pAnisotropyAxis = polarplot( [AngleAnisotropy_inThePlane_NORMALcoords, AngleAnisotropy_inThePlane_NORMALcoords+pi], [0.8, 0.8]* max(Freq_intens), 'Color', 'red' );
  
  %legend([pHist_intens, pHist_intens_fg, pHist_intens_fg_inner, ...
  %    pAnisotropyAxis], ...
  %    {'all orientations', 'orientations in foreground', 'where box was within foreground', ...
  %    'axis of anisotropy (projected)'}, ...
  %    'Location', 'southwest')
  
  text(-pi/4, max(Freq_intens), [{'bias into anisotropy direction' , 'among orientations where box was within foreground', ['linear = ',  num2str(alignment1_intens_fg_inner)],  ['quadratic = ',  num2str(alignment2_intens_fg_inner)]  }])
  
  title('frequency of orientations - intensity weighted')
  
  
  subplot(Fig13D); %makes the existing subplot current
  hold on
  pHist_magn_intens             = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_magn_intens           Freq_magn_intens            ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_All);
  pHist_magn_intens_fg          = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_magn_intens_fg        Freq_magn_intens_fg         ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_fg);
  pHist_magn_intens_fg_inner    = polarhistogram('BinEdges',EdgesNematic,'BinCounts',[Freq_magn_intens_fg_inner  Freq_magn_intens_fg_inner   ], ...
      'DisplayStyle','stairs', 'EdgeColor', colUse_fg_inner);
  
  pAnisotropyAxis = polarplot( [AngleAnisotropy_inThePlane_NORMALcoords, AngleAnisotropy_inThePlane_NORMALcoords+pi], [0.8, 0.8]* max(Freq_magn_intens), 'Color', 'red' );
  
  %legend([pHist_magn_intens, pHist_magn_intens_fg, pHist_magn_intens_fg_inner, ...
  %    pAnisotropyAxis], ...
  %    {'all orientations', 'orientations in foreground', 'where box was within foreground', ...
  %    'axis of anisotropy (projected)'}, ...
  %    'Location', 'southwest')
  
  text(-pi/4, max(Freq_magn_intens), [{'bias into anisotropy direction' , 'among orientations where box was within foreground', ['linear = ',  num2str(alignment1_magn_intens_fg_inner)],  ['quadratic = ',  num2str(alignment2_magn_intens_fg_inner)]  }])
  
  title('frequency of orientations - magnitude and intensity weighted')
  
  
  drawnow
  pause(0.1)
  
  print(Fig13, [filename_intermediate_output(1:end-4) '_Anisotropy' ],'-dpdf', '-r0', '-fillpage')
  print(Fig13, [filename_intermediate_output(1:end-4) '_Anisotropy' ],'-dpng', '-r0')
  
  close all
  
  
  %% plotting of local coherency and locally dominant orientation
  % NOTE: this plot is only possible at the resolution of the orientation
  %  estimate, that is after the downsampling
  [x14, y14] = meshgrid(1:size(localCoherency,2), 1:size(localCoherency,1) );
  u_localOrient = localOrient(:,:,1) .* localCoherency;
  v_localOrient = localOrient(:,:,2) .* localCoherency;
  
  Fig14 = figure('PaperOrientation', 'landscape', 'Position', [0, 0, 1200, 700]);
  [axesHandles14, positions14] = tight_subplot(1,1,[.08 .05],[.05 .01],[.05 .02]) ;   %gap, margin_height, margin_width)
  fig14A = axesHandles14(1);
  
  axes(fig14A)   %make the existing axes current
  
  
  OrigData14 = imshow(localCoherency);  % (myData * 2 - 3);
  hold on
  
  if true  % if show_localOrient
      quiv_localOrient1 = quiver(x14(1:spacing12:end,1:spacing12:end), y14(1:spacing12:end,1:spacing12:end), u_localOrient(1:spacing12:end,1:spacing12:end), -v_localOrient(1:spacing12:end,1:spacing12:end));
      %Note negative sign on y-component to transform from theta in normal coord layout to coord layout used by MATLAB (y downwards in plotting that is started by imshow() )
      if centerQuiverOnPixels
          %duplicate quiver (with opposite sign) to have coherent appearance
          quiv_localOrient2 = quiver(x14(1:spacing12:end,1:spacing12:end), y14(1:spacing12:end,1:spacing12:end), -u_localOrient(1:spacing12:end,1:spacing12:end), v_localOrient(1:spacing12:end,1:spacing12:end));
      end
      
      % modify quiver plot(s)
      quiv_localOrient1.ShowArrowHead = 'off';
      quiv_localOrient1.LineWidth  = 0.5;  %(default: 0.5)
      quiv_localOrient1.AutoScaleFactor = 1.0 ;
      quiv_localOrient1.Color = col_localOrient;
      if centerQuiverOnPixels
          %give the duplicate quiver the same properties, especially the same color
          quiv_localOrient2.ShowArrowHead   = quiv_localOrient1.ShowArrowHead   ;
          quiv_localOrient2.LineWidth       = quiv_localOrient1.LineWidth       ;
          quiv_localOrient2.AutoScaleFactor = quiv_localOrient1.AutoScaleFactor ;
          quiv_localOrient2.Color           = quiv_localOrient1.Color           ;
      end
  end
  
  
  axis equal
  
  %{
    cbar = colorbar;
    cbar.Label.String = 'Local Coherency';
  %}
  
  title(['locally dominant orientation (within radius = ' num2str(boxRadius_LocalAlignment) '(orig pixels) at power ' num2str(powerOfMagn_LocalAlignment) ')' ])
  
  drawnow
  pause(0.1)
  
  print(Fig14, [filename_intermediate_output(1:end-4) '_coherencyRadius' num2str(boxRadius_LocalAlignment) 'Power' num2str(powerOfMagn_LocalAlignment) ],'-dpdf', '-r0', '-fillpage')
  print(Fig14, [filename_intermediate_output(1:end-4) '_coherencyRadius' num2str(boxRadius_LocalAlignment) 'Power' num2str(powerOfMagn_LocalAlignment) ],'-dpng', '-r0')
  
  close all
  
  
end



%% save collected coherency for each ROI to file  output_collected  as .mat and as .csv for subsequent statistical analysis
% basically write all_vals_mat with descr as its header, but precede by
% information on the respective path stored in folders_arr
%recall: the code assumes that folder_to_subfolders
% contains  <Stage>/<Embryo>/<RegionOfTheEye>/<DerotationConditions>/
%  and the different ROIs within one folder are distinguished by leading_ROIinfo_inFilename

% save as .mat file
save([ output_collected(1:end-4) '.mat'], 'descr', 'folders_arr', 'all_vals_mat')

% save as .csv
% write the header
fileID = fopen(output_collected,'w');
fprintf(fileID, ['full path' '\t' 'stage' '\t' 'embryo' '\t' 'region of the eye' '\t' ...
    'derotation conditions' '\t' 'ROI'] );
fieldnameslist = fieldnames(descr);
for kk = 1:numel(fieldnameslist)
    fprintf(fileID, [descr.(fieldnameslist{kk}) '\t'] );
end
fprintf(fileID, '\n');
fclose(fileID);

% write the content
for kk=1:size(folders_arr,1)
    %write filenames
    fileID = fopen(output_collected,'a');
    fprintf(fileID, [ folders_arr{kk,1} '\t' ...
                      folders_arr{kk,2} '\t' ...
                      folders_arr{kk,3} '\t' ...
                      folders_arr{kk,4} '\t' ...
                      folders_arr{kk,5} '\t' ...
                      folders_arr{kk,6} '\t' ]);
    fclose(fileID);
    %write statistics
    dlmwrite( output_collected , all_vals_mat(kk,:),  '-append', 'delimiter','\t') %collected data
end