Skip to content
Snippets Groups Projects
AnalyseOpticalCupLamininOrientation.m 59 KiB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
% 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);