classdef IMG % Methods for imaging
    % 10092023, delete non-grid stitching

    methods (Static)    % Current settings ================================
        %% Function:    getSysSetting
        % Description:  system settings
        function sysSetting = getSysSetting
            % Output:  sysSetting, struct

            sysSetting = [];

            % Image setting general
            sysSetting.imFormat = '.tif';

            % Imaging position info
            sysSetting.xyElement = 1;
            sysSetting.zElement = 2;
            sysSetting.cElement = 3;

            % maxProj/temporary stitching folderName
            sysSetting.maxProjFolderName = '_maxProj';
            sysSetting.temStitchFolderName = '_temporaryStitching';
        end

        %% Function:    getImageSetting
        % Description:  image setting
        function imageSetting = getImageSetting
            % Output:   imageSetting, struct, image setting

            imageSetting = [];

            % Get channel number, z-stack number, pixel number form
            % workspace
            imageSetting.nCh = evalin('base','nCh');
            imageSetting.nZ = evalin('base','nZ');

            % between 0-100%
            imageSetting.overlapFraction = evalin('base','overlapFraction');
        end

    end

    methods (Static)    % General methods =================================
        %% Function:    getStack
        % Description:  Get stacks of an image
        function stack = getStack(fileName,stackNumber)
            % Input:        fileName, string
            %               chNum, num/mat, specific stack number
            % Output:       stack, M*N*O matrix

            info = imfinfo(fileName);

            sz = [info(1).Height,info(1).Width];

            imBits = info(1).BitDepth;
            if imBits == 16
                imBits = 'uint16';
            elseif imBits == 8
                imBits = 'uint8';
            else
                error('Function getStack: do not support the image format.');
            end

            % This will have error for large tif file (?)
            if nargin == 1 || isempty(stackNumber)
                stackNumber = 1:size(info,1);
            end

            nZ = numel(stackNumber);

            stack = zeros([sz nZ],imBits);
            for iStack = 1:nZ
                stack(:,:,iStack)=imread(fileName,stackNumber(iStack));
            end
        end

        %% Funciton:    saveStack
        % Description:  save image stack
        function saveStack(stack,fileName,appendTF)
            % Input:        stack, 3d mat
            %               fileName, str, w/ or w/o path
            %               appendTF, logical, whether only save as append
            % Output:       void. image will be saved on the disk
            % 07202021: combine with saveStackAppend

            if numel(size(stack)) > 3
                stack = reshape(stack,size(stack,1),size(stack,2),[]);
                warning('Stack will be saved as 3-D stack.')
            end

            if nargin == 2
                appendTF = false;
            end

            for i = 1:size(stack,3)
                % Not append only
                if i == 1 && ~appendTF
                    imwrite(stack(:,:,i),fileName);
                else
                    imwrite(stack(:,:,i),fileName,'WriteMode','append')
                end
            end
        end

        %% Function:    fileNameDate
        function [fileName,fileDate] = fileNameDate(name)
            % Input:    name, str, folder or file name
            % Output:   fileName, cell
            %           fileDate, cell of datetime

            listing = dir(name);
            fileName = {listing(:).name};
            fileDate = {listing(:).date};
            fileDate = cellfun(@datetime,fileDate,'UniformOutput',false);
        end

    end

    methods (Static)    % Functions for Nikon-Ti2
        %% Function:    getXYZCfileName
        % Description:  get the xy/z/c info from the file name
        function [xy,z,c] = getXYZCfileName(fileName,sysSetting)
            % Input:    fileName, cell/char
            %           sysSetting, struct,
            % Output:   xy/z/c, vector, the xy/z/c number form file names
            
            % Elmenet postions in the file name
            xyElement = sysSetting.xyElement;
            zElement = sysSetting.zElement;
            cElement = sysSetting.cElement;

            isCellTF = iscell(fileName);
            if ~isCellTF
                fileName = {fileName};
            end

            fileName = cellfun(@(X) strsplit(X,{'xy','z','c','.'}),fileName,'UniformOutput',false);
            fileName = vertcat(fileName{:});

            % The file name begins with 'xy'/'z'/'c', so the first column
            % is empty
            fileName = fileName(:,2:end);
            xy = fileName(:,xyElement);
            z = fileName(:,zElement);
            c = fileName(:,cElement);

            % Change from string to double, outpuyt as vector
            xy = cellfun(@str2double,xy);
            z = cellfun(@str2double,z);
            c = cellfun(@str2double,c);
        end

        %% Function:    im2stack4
        % Description:  get 4D-image stack using file name
        function stack = im2stack4(fileName,sysSetting)
            % Input:    fileName, cell, file names belong to the same stack
            %           sysSetting, struct
            % Output:   stack, mat, image stack. dimentsion: xy,z,c

            [~,z,c] = IMG.getXYZCfileName(fileName,sysSetting);

            % Sort file names using z & c
            [~,I] = sortrows([c z],'ascend');
            fileName = fileName(I);

            for i = 1:numel(fileName)
                if i == 1
                    stack = imread(fileName{i});
                else
                    stack(:,:,i) = imread(fileName{i});
                end
            end
            
            % Reshape to 4D-stack
            nZ = numel(unique(z));
            nC = numel(unique(c));
            sz = size(stack);
            stack = reshape(stack,sz(1),sz(2),nZ,nC);
        end

    end

    methods (Static)    % Max projection on the fly =======================
        %% Function:    maxProj
        % Description:  max projection of raw data
        function maxProj(directory,maxDirectory,scaleRatio,imageSetting,sysSetting)
            % Input:    directory, str, directory for raw data
            %           maxDirectory, str, directory for maxProjection
            %           scaleRatio, num, number to scale down the maxProj
            %           to boost speed
            %           imageSetting/sysSetting, struct
            % Output:   scaled max proj image on disk

            % Settings ----------------------------------------------------
            % Image format, i.e. .tif
            imFormat = sysSetting.imFormat;

            nZ = imageSetting.nZ;
            nCh = imageSetting.nCh;

            if isempty(scaleRatio)
                scaleRatio = 1;
            end

            % Folder for maxProj
            if ~exist(maxDirectory)
                mkdir(maxDirectory);
            end

            % Code --------------------------------------------------------
            % Get image(stack) names & date
            cd(directory);
            [fileName,fileDate] = IMG.fileNameDate(['*',imFormat]);
            fileName = reshape(fileName,[],1);

            % Find the file name with the same xy
            [xy,~,~] = IMG.getXYZCfileName(fileName,sysSetting);
            [xy,~,ic] = unique(xy);

            for i = 1:numel(xy) % parfor

                TF = ic == i;
                if sum(TF) ~= nZ*nCh
                    continue
                end

                % Output file name, D-with directory
                iFileNameOut = ['xy',num2str(xy(i),'%03d'),imFormat];
                iFileNameOutD = fullfile(maxDirectory,iFileNameOut);

                % Skip the image if max projection is already exist after the last
                % image acqired
                if exist(iFileNameOutD)
                    fileDate2 = dir(iFileNameOutD);
                    fileDate2 = fileDate2.date;

                    % The most recent date/time for the image
                    iFileDate = fileDate(TF);
                    iFileDate = max(vertcat(iFileDate{:}));

                    if fileDate2 > iFileDate
                        continue
                    end
                end

                % Get the stack
                iFileName = fileName(TF);
                im = IMG.im2stack4(iFileName,sysSetting);

                % Scale down the stack for faster processing
                if scaleRatio ~= 1
                    im = imresize(im,scaleRatio);
                end

                % Max projection
                im = max(im,[],3);
                im = squeeze(im);

                % Save in max projection folder
                IMG.saveStack(im,iFileNameOutD);

                disp(['Max projection done: ',iFileNameOutD]);
            end
        end

    end
end
